library SmartMines initializer Init requires TNTK
globals
private constant integer SID = 'smmi'
//The rawcode of the ability
private constant integer DID = 'smtm'
//The rawcode of the dummy unit (Smart Mine)
private constant real CURVE = bj_E*1.55
//The curvature for the jumps. Small values will cause a high jump
private constant real SPEED = 6.67
//The movement speed of a mine
private constant real Z_OFFSET = 5.
//The minimal height of a mine
private constant string DEATH_SFX = "Abilities\\Spells\\Human\\MarkOfChaos\\MarkOfChaosTarget.mdl"
//The special effect which is displayed when a mine explodes
private constant string MARK_SFX = "Abilities\\Spells\\Items\\VampiricPotion\\VampPotionCaster.mdl"//"Abilities\\Spells\\Orc\\CommandAura\\CommandAuraTarget.mdl"
//Special effect which indicates the spawn and return location of a mine
//Text Textag setup !
private constant boolean WANT_TEXT_TAG = true
//Set it to true if you want to have tags and
//set it so false if you don´t
//Color settings of the tags
private constant integer TAG_RED = 255
private constant integer TAG_GREEN = 185
private constant integer TAG_BLUE = 0
private constant integer TAG_ALPHA = 100
private constant real TAG_SIZE = 0.0175
//The size of a Tag
private constant boolean TESTMODE = true
//If enabled the missles will only hunt footmen (even your own)
//I´ve implemented this flag so that you can play a bit around
//with the mines and do experiments. Set it to false if you
//import this spell to your map
private constant boolean ALWAYS_CLOSEST_UNIT = true
//Enable this if you want the missiles to seek alwayis the
//closest enemy unit. If this is disabled the missiles will
//randomly pick the next best enemy unit they can find.
//Damage setup. Should be selfexplaining
private constant attacktype ATTACK = null
private constant damagetype DAMAGE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype WEAPON = WEAPON_TYPE_WHOKNOWS
//Globals i need to do several things... DON´T TOUCH !
private integer TEMP_INT
private real TEMP_DIST
private unit TEMP_UNIT = null
private filterfunc ENUM_FILTER = null
private group ENUM_GRP = null
endglobals
//The amount of mines spawned per level
private constant function GetMineAmount takes integer level returns integer
return level * 2 + 2
endfunction
//The Area of Effect in which the mines will be spawned
private constant function GetSpreadRange takes real level returns real
return level * 150 + 100
endfunction
//The range in which a mine detects enemies
private constant function GetDetectionRange takes real level returns real
return level * 0 + 330
endfunction
//If the distance between a chased enemy and a mine exceeds this value
//the mine will return to it´s spawn location
private constant function GetEscapeRange takes real level returns real
return level * 0 + 350
endfunction
//The amount of time a mine exists
private constant function GetDuration takes real level returns real
return level * 7.5 + 5
endfunction
//The damage a mine deals when it hits an enemy
private constant function GetDamage takes real level returns real
return level * 30 + 25
endfunction
private struct Jump extends TNTKnock
//Static location required to calculate the absolute Z
static location LOC = Location(.0,.0)
//Mininal distance between a mine and its target
static constant real MIN_DIST = 10.0
real maxdistance
boolean hasTarget = false //The result will be needed more than once
texttag tt
//Credits to D4RK_G4ND4LF for this function!
static constant method Parabola takes real d, real m , real c returns real
return (4*d*(m-d))/(c*m)
endmethod
//The condition which will destroy the knockback instance.
//This behaves a bit different form the parent one.
method onBreak takes nothing returns boolean
return not .IsInMap() or ( .knocker != null and IsUnitType(.knocker,UNIT_TYPE_DEAD))
endmethod
//Actions which happen when the mine is moving
method onKnock takes nothing returns nothing
local real z = Parabola(.distance,.maxdistance,CURVE) + Z_OFFSET
set .hasTarget = .target != null
//Knockback instance will be disabled when the mine is searching enemies
set .inuse = .hasTarget or .distance > thistype.MIN_DIST
call SetUnitFacing(.knocker,.radiants*bj_RADTODEG)
call MoveLocation(thistype.LOC,.x,.y)
set z = z - GetLocationZ(thistype.LOC)
call SetUnitFlyHeight(.knocker,z,.0)
call super.onKnock()
static if WANT_TEXT_TAG then
call SetTextTagPosUnit(.tt,.knocker,.0)
endif
endmethod
endstruct
//Holds relevant data for the mines
private struct Mine extends Indexable
Jump jmp //that way i can share fields
player owner
effect mark
real runtime = 0.02
real fixed_x
real fixed_y
//The spawn location
real range
real escape
real damage
real duration
//Destructor method....
method onDestroy takes nothing returns nothing
call RemoveUnit(.jmp.knocker)
call DestroyEffect(AddSpecialEffect(DEATH_SFX,.jmp.x,.jmp.y))
call DestroyEffect(.mark)
static if WANT_TEXT_TAG then
call DestroyTextTag(.jmp.tt)
endif
call .jmp.Stop()
endmethod
//Will destroy a mine-instance when the duration is 0 or the mine is dead (exploded)
method onBreak takes nothing returns boolean
set .duration = .duration - .runtime
return .duration <= 0.0 or .jmp.knocker == null or IsUnitType(.jmp.knocker,UNIT_TYPE_DEAD)
endmethod
//Peroid actions which be executed while the mine exists
method onLoop takes nothing returns nothing
//If the mine is IDLE
if not (.jmp.hasTarget or .jmp.inuse) then
set TEMP_DIST = .range*.range
set TEMP_INT = this
call GroupEnumUnitsInRange(ENUM_GRP,.fixed_x,.fixed_y,.range,ENUM_FILTER)
//Did we find something ?
if TEMP_UNIT != null then
call .jmp.StartHoming(.jmp.knocker,TEMP_UNIT,.fixed_x,.fixed_y,SPEED)
set .jmp.maxdistance = .escape
endif
//Needs to be nulled, elseway all mines would chase a found target
set TEMP_UNIT = null
endif
//When we have a target
if .jmp.hasTarget then
//Ouch ! Somene got hit by a mine
if .jmp.distance <= Jump.MIN_DIST then
call UnitDamageTarget(.jmp.knocker,.jmp.target,.damage,false,false,ATTACK,DAMAGE,WEAPON)
call .destroy()
endif
//Someone managed to escape the mine... or he/her got killed
if .jmp.distance >= .escape or IsUnitType(.jmp.target,UNIT_TYPE_DEAD) then
set .jmp.inuse = false
call .jmp.StartLinearLoc(.jmp.knocker,GetUnitLoc(.jmp.knocker),Location(.fixed_x,.fixed_y),SPEED,true)
set .jmp.maxdistance = .jmp.distance
endif
endif
static if WANT_TEXT_TAG then
if .duration <= 1. then
call SetTextTagText(.jmp.tt,R2S(.duration),TAG_SIZE)
else
call SetTextTagText(.jmp.tt,I2S(R2I(.duration)),TAG_SIZE)
endif
endif
endmethod
//creator method...
static method create takes real sx, real sy, real tx, real ty, integer lv, player p returns thistype
local thistype this = thistype.allocate()
local texttag tt = null
set .owner = p
set .fixed_x = tx
set .fixed_y = ty
set .range = GetDetectionRange(lv)
set .escape = GetEscapeRange(lv)
set .damage = GetDamage(lv)
set .duration = GetDuration(lv)
set .mark = AddSpecialEffect(MARK_SFX,tx,ty)
//Initializing the jump-instance
set .jmp = Jump.create()
call .jmp.StartLinearLoc(CreateUnit(p,DID,sx,sy,0.0),Location(sx,sy),Location(tx,ty),SPEED,true)
set .jmp.maxdistance = .jmp.distance
//Configuring the mine
call PauseUnit(.jmp.knocker,true)
call SetUnitFacing(.jmp.knocker,.jmp.radiants*bj_RADTODEG)
call UnitApplyTimedLife(.jmp.knocker,'BTLF',.duration)
static if WANT_TEXT_TAG then
set tt = CreateTextTag()
call SetTextTagColor(tt,TAG_RED,TAG_GREEN,TAG_BLUE,255 - TAG_ALPHA)
call SetTextTagPermanent(tt,false)
call SetTextTagVisibility(tt,true)
call SetTextTagLifespan(tt,.duration)
call SetTextTagFadepoint(tt,.duration-1.)
call SetTextTagPosUnit(tt,.jmp.knocker,0.0)
set .jmp.tt = tt
set tt = null
endif
//In case someone decreased the escape range below the detection
//range i swap the vaules to safe functionality
if .escape <= .range then
set sx = .escape
set .escape = .range + Jump.MIN_DIST
set .range = sx
endif
return this
endmethod
endstruct
//Filter function which will return a target
private function EnumFilterFunc takes nothing returns boolean
//Things inside this function shouldn´t be too difficult to be not selfexplaining
local unit u = GetFilterUnit()
local real x
local real y
local real d
local Mine this = TEMP_INT
static if TESTMODE then
local boolean b = GetUnitTypeId(u) == 'hfoo'
else
local boolean b = IsUnitEnemy(u,this.owner) and not ( IsUnitType(u,UNIT_TYPE_DEAD) or IsUnitType(u,UNIT_TYPE_STRUCTURE) or IsUnitType(u, UNIT_TYPE_MECHANICAL) or IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) or IsUnitType(u, UNIT_TYPE_ANCIENT) )
endif
if b then
static if ALWAYS_CLOSEST_UNIT then
set x = GetWidgetX(u) - this.fixed_x
set y = GetWidgetY(u) - this.fixed_y
set d = x*x+y*y
if d < TEMP_DIST then
set TEMP_DIST = d
set TEMP_UNIT = u
endif
else
set TEMP_UNIT = u
endif
endif
set u = null
return false
endfunction
//When the spell is casted
private function onCast takes nothing returns nothing
local unit ca = GetTriggerUnit()
local player p = GetOwningPlayer(ca)
local integer lv = GetUnitAbilityLevel(ca,SID)
local integer am = GetMineAmount(lv)
local real range = GetSpreadRange(lv)
local real cx = GetWidgetX(ca)
local real cy = GetWidgetY(ca)
local real x
local real y
//Getting some values
loop //Creating all mines
exitwhen am == 0
set am = am -1
set x = cx + GetRandomReal(-range,range)
set y = cy + GetRandomReal(-range,range)
// GetRandomReal(-range,range) is the same as range*Sin/Cos(GetRandomReal(0,2*bj_PI))
call Mine.create(cx,cy,x,y,lv,p)
endloop
set ca = null
set p = null
endfunction
//Checks if the correct spell was casted
private function onCheck takes nothing returns boolean
return GetSpellAbilityId() == SID
endfunction
//Initializer
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
set ENUM_GRP = CreateGroup()
set ENUM_FILTER = Filter( function EnumFilterFunc)
call Preload(DEATH_SFX)
call Preload(MARK_SFX)
call PreloadStart()
call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig,Filter( function onCheck))
call TriggerAddAction(trig, function onCast)
set trig = null
endfunction
endlibrary