Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TNTK requires TNTI
globals
private constant real OFFSET = 64.
private group MOTION_GRP
private real MAX
private real MAY
private real MIX
private real MIY
endglobals
struct TNTKnock extends Indexable
unit knocker = null
unit target = null
integer data = 0x0
boolean collision = false
boolean inuse = false
private real runtime = 0.02
private real k_radiants = .0
private real k_speed = .0
readonly real distance = 10.
readonly real velx = .0
readonly real vely = .0
readonly real x = .0
readonly real y = .0
readonly real time = .0
static method IsInMotion takes unit u returns boolean
return IsUnitInGroup(u,MOTION_GRP)
endmethod
method IsInMap takes nothing returns boolean
return .x <= MAX and .x >= MIX and .y <= MAY and .y >= MIY
endmethod
stub method onKnock takes nothing returns nothing
if .collision then
call SetUnitPosition(.knocker,.x,.y)
else
call SetUnitX(.knocker,.x)
call SetUnitY(.knocker,.y)
endif
endmethod
method onBreak takes nothing returns boolean
return not .IsInMap() or .distance <= .k_speed+1. /*
*/ or ( .target != null and IsUnitType(.target ,UNIT_TYPE_DEAD)) /*
*/ or ( .knocker != null and IsUnitType(.knocker,UNIT_TYPE_DEAD))
endmethod
method onLoop takes nothing returns nothing
if .inuse then
if .target == null then
set .x = .x + velx
set .y = .y + vely
set .time = .time - .runtime
set .distance = .distance - .k_speed
else
set .velx = GetWidgetX(.target) - GetWidgetX(.knocker)
set .vely = GetWidgetY(.target) - GetWidgetY(.knocker)
set .k_radiants = Atan2(.vely,.velx)
set .distance = SquareRoot(.velx*.velx+.vely*.vely) - .k_speed
set .x = .x + Cos(.k_radiants) * .speed
set .y = .y + Sin(.k_radiants) * .speed
set .time = .time + .runtime
endif
call .onKnock()
endif
endmethod
method onDestroy takes nothing returns nothing
call GroupRemoveUnit(MOTION_GRP,.knocker)
set .knocker = null
set .target = null
set .inuse = false
endmethod
method operator speed takes nothing returns real
return .k_speed
endmethod
method operator speed= takes real newspeed returns nothing
local real sign = 1.0
if R2I(newspeed) == 0 then
return
endif
if newspeed < 0 then
set sign = -1.0
endif
set velx = Cos(.k_radiants) * newspeed
set vely = Sin(.k_radiants) * newspeed
set .k_speed = newspeed * sign
set .time = (.distance*.runtime)/(newspeed * sign)
endmethod
method operator radiants takes nothing returns real
return .k_radiants
endmethod
method operator radiants= takes real newrad returns nothing
set velx = Cos(newrad) * .k_speed
set vely = Sin(newrad) * .k_speed
set .k_radiants = newrad
endmethod
method operator angle takes nothing returns real
return .k_radiants*bj_RADTODEG
endmethod
method operator angle= takes real newrad returns nothing
set newrad = newrad * bj_DEGTORAD
set velx = Cos(newrad) * .k_speed
set vely = Sin(newrad) * .k_speed
set .k_radiants = newrad
endmethod
method Stop takes nothing returns nothing
set .inuse = true
set .distance = -1.
endmethod
method StartLinear takes unit u, real sx, real sy, real rad, real dis, real speed returns nothing
if speed > 1 and not .inuse then
set .knocker = u
set .target = null
set .x = sx
set .y = sy
set .distance = dis
set .k_radiants = rad
set .time = (dis*.runtime) / speed
set .k_speed = speed
set .velx = Cos(rad)*speed
set .vely = Sin(rad)*speed
set .inuse = true
call GroupAddUnit(MOTION_GRP,u)
endif
endmethod
method StartLinearTimed takes unit u, real sx, real sy, real rad, real dis, real time returns nothing
if time > .runtime and not .inuse then
set .knocker = u
set .target = null
set .x = sx
set .y = sy
set .distance = dis
set .k_radiants = rad
set .time = time
set .k_speed = (dis*.runtime) / time
set velx = Cos(rad) * .speed
set vely = Sin(rad) * .speed
set .inuse = true
call GroupAddUnit(MOTION_GRP,u)
endif
endmethod
method StartLinearTimedLoc takes unit u, location from, location to, real time, boolean noleak returns nothing
local real x = GetLocationX(to) - GetLocationX(from)
local real y = GetLocationY(to) - GetLocationY(from)
local real rad = Atan2(y,x)
local real dis = SquareRoot(x*x+y*y)
call .StartLinearTimed(u,GetLocationX(from),GetLocationY(from),rad,dis,time)
if noleak then
call RemoveLocation(from)
call RemoveLocation(to)
endif
endmethod
method StartLinearLoc takes unit u, location from, location to, real speed, boolean noleak returns nothing
local real x = GetLocationX(to) - GetLocationX(from)
local real y = GetLocationY(to) - GetLocationY(from)
local real rad = Atan2(y,x)
local real dis = SquareRoot(x*x+y*y)
call .StartLinear(u,GetLocationX(from),GetLocationY(from),rad,dis,speed)
if noleak then
call RemoveLocation(from)
call RemoveLocation(to)
endif
endmethod
method StartHoming takes unit u, unit targ, real sx, real sy, real speed returns nothing
if not (IsUnitType(targ,UNIT_TYPE_DEAD) or targ == null) and speed > 0. then
set .knocker = u
set .target = targ
set .x = sx
set .y = sy
set .k_speed = speed
set .time = 0.
set .distance = speed+3.
set .inuse = true
call GroupAddUnit(MOTION_GRP,u)
endif
endmethod
private static method onInit takes nothing returns nothing
set MAX = GetRectMaxX(bj_mapInitialPlayableArea) - OFFSET
set MAY = GetRectMaxY(bj_mapInitialPlayableArea) - OFFSET
set MIX = GetRectMinX(bj_mapInitialPlayableArea) + OFFSET
set MIY = GetRectMinY(bj_mapInitialPlayableArea) + OFFSET
set MOTION_GRP = CreateGroup()
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TNTI
private interface Ext
static integer Total = 0
static integer Current = 0
static constant real Intervall = .01
static timer TIM = null
real runtime = .01
method onBreak takes nothing returns boolean defaults true
method onLoop takes nothing returns nothing defaults nothing
endinterface
struct Indexable extends Ext
private static Ext array Index
private real timing = .0
/*stub method onBreak takes nothing returns boolean
call BJDebugMsg(SCOPE_PREFIX+": |c00FF0000onBreak() from struct with id: "+I2S(.getType())+" is not defined !")
return true
endmethod
stub method onLoop takes nothing returns nothing
call BJDebugMsg(SCOPE_PREFIX+": |c00FF0000onLoop() from struct with id: "+I2S(.getType())+" is not defined !")
endmethod*/
private method onDestroy takes nothing returns nothing
set Ext.Total = Ext.Total -1
set thistype.Index[Ext.Current] = thistype.Index[Ext.Total]
set Ext.Current = Ext.Current-1
if Ext.Total == 0 then
call PauseTimer(Ext.TIM)
endif
endmethod
private static method Loop takes nothing returns nothing
local Ext this
set Ext.Current = 0
loop
exitwhen Ext.Current == Ext.Total
set this = thistype.Index[Ext.Current]
set .timing = .timing + thistype.Intervall
if .timing >= .runtime then
if .onBreak() then
call .destroy()
else
call .onLoop()
set .timing = 0
endif
endif
set Ext.Current = Ext.Current+1
endloop
endmethod
static method create takes nothing returns thistype
local Ext this = thistype.allocate()
set thistype.Index[Ext.Total] = this
set Ext.Total = Ext.Total +1
if Ext.Total == 1 then
call TimerStart(Ext.TIM,Ext.Intervall,true, function thistype.Loop)
endif
return this
endmethod
private static method onInit takes nothing returns nothing
set Ext.TIM = CreateTimer()
endmethod
endstruct
endlibrary
//TESH.scrollpos=69
//TESH.alwaysfold=0
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