//*******************************************************************************************************************
//* Touch of Nature by xD.Schurke (Zephyr Contest #7 entry)
//* -------------------------------------------------------
//*
//* Requires:
//* - JassHelper (Latest Version)
//* - Table 3.0 By Vexorian
//* - TimerUtils (Blue Flavour) by Vexorian
//* - IntuitiveDamageSystem 1.13 by Rising_Dusk
//* - Basis Jass-Knowledge to setup the spell
//*
//*
//* Other Credits:
//* - SkriK for the icon
//*
//*
//* About the spell:
//* Touches an allied unit with the power of nature. The unit regenerates life
//* and takes 5% less physical damage. In addition blossoms are spawned for
//* 5 seconds which can be consumed by anyone. Those blossoms increase the armor
//* by 3 and heal the unit over 7.5 seconds for 65 hitpoints.
//* Level 1 - The touched unit regenerates 100 health points over 10 seconds.
//* Level 2 - The touched unit regenerates 175 health points over 11 seconds.
//* Level 3 - The touched unit regenerates 250 health points over 12 seconds.
//*
//* Cooldown: 15 seconds
//*
//*
//* - The spell requires some micro to have the optimal use in a battle.
//* - The spell is balanced for melee games, only issue is that it uses Inner-Fire buff
//*
//*
//*
//*******************************************************************************************************************
scope TouchOfNature
globals
//************************************************************************************************
//* You can change the following variables to fit your needs, or to copy this spell to your map
//************************************************************************************************
//*Rawcodes*\\
private constant integer DUMMY_ID = 'n001' //Dummy
private constant integer SPELL_ID = 'A000' //Spell
private constant integer SPELL_BUFF_ID = 'B000' //Touch of Nature buff
private constant integer DUMMY_SPELL_ID = 'A001' //Dummy Spell (Blossoms Gift)
private constant integer DUMMY_SPELL_BUFF_ID = 'B001' //Blossoms Gift buff
//*Time-related variables*\\
private constant real INTERVAL = 0.03125 //Standard timer interval of 32 FPS
private constant real DURATION_BASIS = 10.00000 //The basis duration of the Touch of Nature buff
private constant real DURATION_BASIS_INC = 1.00000 //Level duration increase of touch of nature
private constant real DURATION_FLOWER_BASIS = 5.00000 //Duration of the blossoms (flowers)
private constant real DURATION_FLOWER_BASIS_INC = 0.00000 //Actually this variable is not needed, but if you want an increase per level, you can change this variable
private constant real GROWTH_DURATION = 2.00000 //The time in which the flower should grow
private constant real BLOSSOM_DURATION = 7.50000 //Blossoms Gift buff duration
//*Spell effect variables*\\
private constant real LIFE_REGENERATION = 100.00000 //The basis life healed of Touch of nature
private constant real LIFE_REGENERATION_INC = 75.00000 //The level increase of life healed by the spell
private constant real DAMAGE_REDUCE = 5.00000 //The damage reduction of the spell in percent
private constant real BLOSSOM_HEAL = 65.00000 //The hitpoints healed by the blossom buff
//*Flower growth stuff*\\
private constant real GROWTH_MAX_SIZE = 1.00000 //Maximal size of the flower
private constant real GROWTH_START_SIZE = 0.00 //The start size of the flower
//!!DO NOT CHANGE!!\\
private constant real GROWTH_VALUE = GROWTH_MAX_SIZE/(GROWTH_DURATION/INTERVAL) //How much the flower should grow per interval
//!!!!!!!!!!!!!!!!!\\
//*Range check variables*\\
private constant real GROWTH_CHECK_RANGE = 50.00000 //Is used to check if other flowers are grown near the spot
private constant real FLOWER_CHECK_RANGE = 50.00000 //Is used to find the unit which should be buffed
private constant real GROWTH_ANGLE_RADIUS = 360.00000 //0 - this angle is used to spawn new flowsers
//*Effect Strings'\\
private constant string FLOWER_DEATH_EFFECT = "Abilities\\Spells\\NightElf\\FaerieDragonInvis\\FaerieDragon_Invis.mdl"
private constant string FLOWER_PICK_UP_EFFECT = "Abilities\\Spells\\Human\\DispelMagic\\DispelMagicTarget.mdl"
private string array FLOWER_VARIATION //This array contains the flower model strings , for more information see setupModelStrings
private constant integer VARIATION_COUNT = 5 //If u want to add any flower model, or remove any change this variable to the count
//DO NOT change anything bellow this line without any knowledge!
//Those variables are used for group issues, so the whole spell uses just one group which is only temporaly used
private player tmpP
private unit tmpU
private group tmpG
private boolexpr FILTER_FLOWER
private boolexpr FILTER_CHECK_FLOWER
endglobals
//This function is used to setup the flower model strings
private function setupModelStrings takes nothing returns nothing
set FLOWER_VARIATION[0] = "Doodads\\Ruins\\Plants\\Ruins_Flower\\Ruins_Flower0.mdl"
set FLOWER_VARIATION[1] = "Doodads\\Ruins\\Plants\\Ruins_Flower\\Ruins_Flower1.mdl"
set FLOWER_VARIATION[2] = "Doodads\\Ruins\\Plants\\Ruins_Flower\\Ruins_Flower2.mdl"
set FLOWER_VARIATION[3] = "Doodads\\Ruins\\Plants\\Ruins_Flower\\Ruins_Flower3.mdl"
set FLOWER_VARIATION[4] = "Doodads\\Ruins\\Plants\\Ruins_Flower\\Ruins_Flower4.mdl"
endfunction
//Function for getting the duration of the spell
private function getDuration takes integer lvl returns real
return DURATION_BASIS+(DURATION_BASIS_INC*(lvl-1))
endfunction
//Function for getting the duration of the flowers
private function getFlowerDuration takes integer lvl returns real
return DURATION_FLOWER_BASIS+(DURATION_FLOWER_BASIS_INC*(lvl-1))
endfunction
//Function for calculating the healed amount of the main spell
private function getHealMain takes integer lvl returns real
return (LIFE_REGENERATION+(LIFE_REGENERATION_INC*(lvl-1)))/(getDuration(lvl)/INTERVAL)
endfunction
//Function for calculating the healed amount of the blossoms
private function getHealBlossom takes nothing returns real
return BLOSSOM_HEAL/(BLOSSOM_DURATION/INTERVAL)
endfunction
//Filter function used to find a unit which can be buffed
private function GroupFilterFlower takes nothing returns boolean
return GetUnitAbilityLevel(GetFilterUnit(),DUMMY_SPELL_BUFF_ID) == 0 and (GetUnitState(GetFilterUnit(),UNIT_STATE_LIFE)>0) and IsUnitType(GetFilterUnit(),UNIT_TYPE_GROUND) and (GetUnitTypeId(GetFilterUnit())!= DUMMY_ID) and GetFilterUnit() != tmpU
endfunction
//Filter Function used to check if other flowers are arround
private function GroupFilterFlowerCheck takes nothing returns boolean
local real dx = GetUnitX(GetFilterUnit()) - GetUnitX(tmpU)
local real dy = GetUnitY(GetFilterUnit()) - GetUnitY(tmpU)
return GetUnitState(GetFilterUnit(),UNIT_STATE_LIFE) > 0 and GetUnitTypeId(GetFilterUnit())== DUMMY_ID and SquareRoot(dx * dx + dy * dy) < GROWTH_CHECK_RANGE
endfunction
//Keyword since I decided to declare the FlowerStruct after the MainStruct
private keyword FlowerStruct
//This struct is the one handeling the main part, also this struct contains the initializer method (onInit)
private struct MainStruct
private unit caster = null
private unit target = null
private real duration = 0.0
//method to check the surrounding area for other flowers
private method checkFlowers takes nothing returns boolean
local real x = GetUnitX(this.caster)
local real y = GetUnitY(this.caster)
set tmpP = GetOwningPlayer(this.target)
set tmpU = this.target
call GroupEnumUnitsOfPlayer(tmpG,tmpP,FILTER_CHECK_FLOWER)
if FirstOfGroup(tmpG) != null then
call GroupClear(tmpG)
return false
endif
return true
endmethod
//The callback method which is called each interval, heals the target of the spell and copes the flower growth
private static method callback takes nothing returns nothing
local timer tim = GetExpiredTimer()
local thistype this = GetTimerData(tim)
local real x = 0.0
local real y = 0.0
local real life = GetUnitState(this.target,UNIT_STATE_LIFE)
set this.duration = this.duration + INTERVAL
call SetUnitState(this.target,UNIT_STATE_LIFE,life+getHealMain(GetUnitAbilityLevel(this.caster,SPELL_ID)))
if this.checkFlowers() then
set x = GetUnitX(this.target) + GetRandomReal(0.0,GROWTH_CHECK_RANGE) * Cos(GetRandomReal(0.0,GROWTH_ANGLE_RADIUS) * bj_DEGTORAD)
set y = GetUnitY(this.target) + GetRandomReal(0.0,GROWTH_CHECK_RANGE) * Sin(GetRandomReal(0.0,GROWTH_ANGLE_RADIUS) * bj_DEGTORAD)
call FlowerStruct.create(this.caster,this.target,x,y)
endif
if this.duration >= getDuration(GetUnitAbilityLevel(this.caster,SPELL_ID)) or GetUnitAbilityLevel(this.target,SPELL_BUFF_ID) == 0 then
call ReleaseTimer(tim)
call this.destroy()
endif
set tim = null
endmethod
static method create takes unit caster ,unit target returns thistype
local thistype this = thistype.allocate()
local timer tim = NewTimer()
set this.caster = caster
set this.target = target
call SetTimerData(tim,this)
call TimerStart(tim,INTERVAL,true,function thistype.callback)
return this
endmethod
private method onDestroy takes nothing returns nothing
set this.caster = null
set this.target = null
endmethod
//The following part is used to setup stuff like damage reduction and general struct creation on spell cast
private static method condition takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call thistype.create(GetTriggerUnit(),GetSpellTargetUnit())
endif
return false
endmethod
private static method damageCondition takes nothing returns boolean
if GetUnitAbilityLevel(GetTriggerDamageTarget(),SPELL_BUFF_ID) > 0 and GetTriggerDamageType() == DAMAGE_TYPE_ATTACK then
call SetUnitState(GetTriggerDamageTarget(),UNIT_STATE_LIFE,GetUnitState(GetTriggerDamageTarget(),UNIT_STATE_LIFE)+GetTriggerDamage()*DAMAGE_REDUCE)
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger trig = CreateTrigger()
local trigger damageTrig = CreateTrigger()
call setupModelStrings()
set tmpG = CreateGroup()
set FILTER_FLOWER = Condition(function GroupFilterFlower)
set FILTER_CHECK_FLOWER = Condition(function GroupFilterFlowerCheck)
call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig,Condition(function thistype.condition))
call TriggerAddCondition(damageTrig,Condition(function thistype.damageCondition))
call TriggerRegisterDamageEvent(damageTrig,0)
set trig = null
endmethod
endstruct
//This is just a small struct healing the target of a blossom buff, does nothing else
private struct BlossomStruct
private unit target = null
private real duration = 0.0
private static method callback takes nothing returns nothing
local timer tim = GetExpiredTimer()
local thistype this = GetTimerData(tim)
local real life = GetUnitState(this.target,UNIT_STATE_LIFE)
set this.duration = this.duration + INTERVAL
call SetUnitState(this.target,UNIT_STATE_LIFE,life+getHealBlossom())
if this.duration >= BLOSSOM_DURATION or GetUnitAbilityLevel(this.target,DUMMY_SPELL_BUFF_ID) == 0 then
call ReleaseTimer(tim)
call this.destroy()
endif
set tim = null
endmethod
private method onDestroy takes nothing returns nothing
set this.target = null
endmethod
static method create takes unit target returns thistype
local thistype this = thistype.allocate()
local timer tim = NewTimer()
set this.target = target
call SetTimerData(tim,this)
call TimerStart(tim,INTERVAL,true,function thistype.callback)
return this
endmethod
endstruct
//The FlowerStruct represents Flower/Blossom objects, it actually checks in the callback method for units arround which can be buffed, and then
//applies the buff to them, look a bit more into it to get a basic knowledge of what I am doing here
private struct FlowerStruct
private unit caster = null
private unit target = null
private unit dummy = null
private effect attachement = null
private real duration = 0.0
private real actualSize = GROWTH_START_SIZE
private method checkRange takes nothing returns boolean
local real x = GetUnitX(this.dummy)
local real y = GetUnitY(this.dummy)
local unit pickedUnit = null
set tmpU = this.target
set tmpP = GetOwningPlayer(this.target)
call GroupEnumUnitsInRange(tmpG,x,y,FLOWER_CHECK_RANGE,FILTER_FLOWER)
if FirstOfGroup(tmpG) != null then
set pickedUnit = FirstOfGroup(tmpG)
call UnitAddAbility(this.dummy,DUMMY_SPELL_ID)
call IssueTargetOrder(this.dummy,"innerfire",pickedUnit)
call BlossomStruct.create(pickedUnit)
call GroupClear(tmpG)
return true
endif
call GroupClear(tmpG)
return false
endmethod
private static method callback takes nothing returns nothing
local timer tim = GetExpiredTimer()
local thistype this = GetTimerData(tim)
set this.duration = this.duration + INTERVAL
if this.duration < GROWTH_DURATION then
set this.actualSize = this.actualSize + GROWTH_VALUE
call SetUnitScale(this.dummy,this.actualSize,this.actualSize,this.actualSize)
endif
if this.checkRange() then
call DestroyEffect(AddSpecialEffect(FLOWER_PICK_UP_EFFECT,GetUnitX(this.dummy),GetUnitY(this.dummy)))
call ReleaseTimer(tim)
call this.destroy()
elseif this.duration >= getFlowerDuration(GetUnitAbilityLevel(this.caster,SPELL_ID)) then
call DestroyEffect(AddSpecialEffect(FLOWER_DEATH_EFFECT,GetUnitX(this.dummy),GetUnitY(this.dummy)))
call ReleaseTimer(tim)
call this.destroy()
endif
set tim = null
endmethod
static method create takes unit caster, unit target, real x, real y returns thistype
local thistype this = thistype.allocate()
local timer tim = NewTimer()
set this.caster = caster
set this.target = target
set this.dummy = CreateUnit(GetOwningPlayer(this.target),DUMMY_ID,x,y,0.0)
call SetUnitTimeScale(this.dummy,0.0)
call SetUnitScale(this.dummy,GROWTH_START_SIZE,GROWTH_START_SIZE,GROWTH_START_SIZE)
call AddSpecialEffectTarget(FLOWER_VARIATION[GetRandomInt(0,VARIATION_COUNT-1)],this.dummy,"origin")
call SetTimerData(tim,this)
call TimerStart(tim,INTERVAL,true,function thistype.callback)
return this
endmethod
private method onDestroy takes nothing returns nothing
call DestroyEffect(this.attachement)
call RemoveUnit(this.dummy)
set this.attachement = null
set this.dummy = null
set this.caster = null
set this.target = null
endmethod
endstruct
endscope