Name | Type | is_array | initial_value |
//TESH.scrollpos=36
//TESH.alwaysfold=0
library_once TimerUtils initializer redInit
//*********************************************************************
//* TimerUtils (Red flavor)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3campaigns.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Red flavor: Fastest, method in existence for timer attaching,
//* only takes an array lookup, H2I and subtraction.
//* However, all the code in your map requires extra care
//* not to forget to call ReleaseTimer. It also requires
//* to preload a lot of timers at map init, they use
//* memory and handle ids.
//*
//* I recommend you run your map in debug mode the first
//* time after adding it, make sure you can see map init messages
//* if nothing appears, all is done, if an error appears, it might
//* suggest you a value with OFFSET, in that case, update that value
//* if it still does not work after updating (rare), try a bigger
//* OFFSET by 1000 for example. (Sounds hard? Then use blue or purple
//* timerutils that are friendlier though not as fast)
//*
//********************************************************************
//================================================================
globals
private constant integer OFFSET = 0x100000
private constant integer QUANTITY = 256
private constant integer ARRAY_SIZE = 8191 //changing this to a higher value would effectively
//cripple the performance making this thing irrelevant
endglobals
//=================================================================================================
private function H2I takes handle h returns integer
return GetHandleId( h )
endfunction
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
debug if(H2I(t)-OFFSET<0) then
debug call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
debug endif
set data[H2I(t)-OFFSET]=value
endfunction
function GetTimerData takes timer t returns integer
debug if(H2I(t)-OFFSET<0) then
debug call BJDebugMsg("GetTimerData: Wrong handle id, only use GetTimerData on timers created by NewTimer")
debug endif
return data[H2I(t)-OFFSET]
endfunction
//==========================================================================================
globals
private timer array tT
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, please increase it for your map, fix your map's timer leaks or switch to blue flavor when applicable")
set tT[0]=CreateTimer()
if (H2I(tT[0])-OFFSET<0) or (H2I(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably switch to the blue flavor or fix timer leaks.")
return null
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==8191) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function redInit takes nothing returns nothing
local integer i=0
local integer bestoffset=-1
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set bestoffset=H2I(tT[i])
endif
if (H2I(tT[i])-OFFSET>=ARRAY_SIZE) then
debug call BJDebugMsg("TimerUtils_redInit: Failed a initializing attempt")
debug call BJDebugMsg("The timer limit is "+I2S(i))
debug call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low, to change OFFSET to "+I2S(bestoffset) )
exitwhen true
endif
if (H2I(tT[i])-OFFSET>=0) then
set i=i+1
endif
endloop
set tN=i
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library IsTerrainWalkable initializer Init
//*****************************************************************
//* IsTerrainWalkable
//*
//* rewritten in vJass by: Anitarf
//* original implementation: Vexorian
//*
//* A function for checking if a point is pathable for ground
//* units (it does so by attempting to move an item there and
//* checking where it ended up), typically used to stop sliding
//* units before they end up stuck in trees. If the point is not
//* pathable, the function will also determine the nearest point
//* that is (the point where the item ends up).
//*****************************************************************
globals
// this value is how far from a point the item may end up for the point to be considered pathable
private constant real MAX_RANGE = 10.0
// the following two variables are set to the position of the item after each pathing check
// that way, if a point isn't pathable, these will be the coordinates of the nearest point that is
public real X = 0.0
public real Y = 0.0
// END OF CALIBRATION SECTION
// ================================================================
private rect r
private item check
private item array hidden
private integer hiddenMax = 0
endglobals
private function Init takes nothing returns nothing
set check = CreateItem('ciri',0,0)
call SetItemVisible(check,false)
set r = Rect(0.0,0.0,128.0,128.0)
endfunction
private function HideBothersomeItem takes nothing returns nothing
if IsItemVisible(GetEnumItem()) then
set hidden[hiddenMax]=GetEnumItem()
call SetItemVisible(hidden[hiddenMax],false)
set hiddenMax=hiddenMax+1
endif
endfunction
// ================================================================
function IsTerrainWalkable takes real x, real y returns boolean
// first, hide any items in the area so they don't get in the way of our item
call MoveRectTo(r, x,y)
call EnumItemsInRect(r,null,function HideBothersomeItem)
// try to move the check item and get it's coordinates
call SetItemPosition(check,x,y)//this unhides the item...
set X = GetItemX(check)
set Y = GetItemY(check)
call SetItemVisible(check,false)//...so we must hide it again
// before returning, unhide any items that got hidden at the start
loop
exitwhen hiddenMax<=0
set hiddenMax=hiddenMax-1
call SetItemVisible(hidden[hiddenMax],true)
set hidden[hiddenMax]=null
endloop
// return pathability status
return (x-X)*(x-X)+(y-Y)*(y-Y) < MAX_RANGE*MAX_RANGE
endfunction
endlibrary
//TESH.scrollpos=44
//TESH.alwaysfold=0
library BoundSentinel initializer init
//*************************************************
//* BoundSentinel
//* -------------
//* Don't leave your units unsupervised, naughty
//* them may try to get out of the map bounds and
//* crash your game.
//*
//* To implement, just get a vJass compiler and
//* copy this library/trigger to your map.
//*
//*************************************************
//==================================================
globals
// High enough so the unit is no longer visible, low enough so the
// game doesn't crash...
//
// I think you need 0.0 or soemthing negative prior to patch 1.22
//
private constant real EXTRA = 500.0
endglobals
//=========================================================================================
globals
private real maxx
private real maxy
private real minx
private real miny
endglobals
//=======================================================================
private function dis takes nothing returns nothing
local unit u=GetTriggerUnit()
local real x=GetUnitX(u)
local real y=GetUnitY(u)
if(x>maxx) then
set x=maxx
elseif(x<minx) then
set x=minx
endif
if(y>maxy) then
set y=maxy
elseif(y<miny) then
set y=miny
endif
call SetUnitX(u,x)
call SetUnitY(u,y)
set u=null
endfunction
private function init takes nothing returns nothing
local trigger t=CreateTrigger()
local region r=CreateRegion()
local rect rc
set minx=GetCameraBoundMinX() - EXTRA
set miny=GetCameraBoundMinY() - EXTRA
set maxx=GetCameraBoundMaxX() + EXTRA
set maxy=GetCameraBoundMaxY() + EXTRA
set rc=Rect(minx,miny,maxx,maxy)
call RegionAddRect(r, rc)
call RemoveRect(rc)
call TriggerRegisterLeaveRegion(t,r, null)
call TriggerAddAction(t, function dis)
//this is not necessary but I'll do it anyway:
set t=null
set r=null
set rc=null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library UnitIndexingUtils initializer Init
//******************************************************************************
//* BY: Rising_Dusk
//*
//* -: RED FLAVOR :-
//*
//* This can be used to index units with a unique integer for use with arrays
//* and things like that. This has a limit of 8191 indexes allocated at once in
//* terms of actually being usable in arrays. It won't give you an error if you
//* exceed 8191, but that is an unrealistic limit anyways.
//*
//* The red flavor uses a periodic timer to automatically recycle a unit's index
//* when UnitUserData goes to 0 for that unit (when it is removed from the game).
//* This can be slower than the blue flavor if you have many units on the map at
//* a time because it uses an O(n) search, but it automatically recycles indexes
//* for units that get removed from the game by decaying or RemoveUnit. It will
//* run the timer for COUNT_PER_ITERATION units before ending. The timer will
//* pick up where it left off on the next run-through.
//*
//* To use, call GetUnitId on a unit to retrieve its unique integer id. This
//* library allocates a unique index to a unit the instant it is created, which
//* means you can call GetUnitId immediately after creating the unit with no
//* worry.
//*
//* Function Listing --
//* function GetUnitId takes unit u returns integer
//*
globals
private constant real TIMER_PERIODICITY = 5.
private constant integer COUNT_PER_ITERATION = 64
private integer POSITION = 0
private integer array STACK
private unit array UNIT_STACK
private integer STACK_SIZE = 0
private integer ASSIGNED = 1
private integer MAX_INDEX = 0
endglobals
//Function to get the unit's unique integer id, inlines to getting its userdata
function GetUnitId takes unit u returns integer
return GetUnitUserData(u)
endfunction
//Filter for units to index
private function UnitFilter takes nothing returns boolean
return true
endfunction
private function Clear takes nothing returns nothing
local integer i = POSITION
loop
exitwhen (POSITION > MAX_INDEX or POSITION > i+COUNT_PER_ITERATION)
if UNIT_STACK[POSITION] != null and GetUnitUserData(UNIT_STACK[POSITION]) == 0 then
set STACK[STACK_SIZE] = POSITION
set STACK_SIZE = STACK_SIZE + 1
set UNIT_STACK[POSITION] = null
endif
set POSITION = POSITION + 1
endloop
if POSITION > MAX_INDEX then
set POSITION = 0
endif
endfunction
private function Add takes nothing returns boolean
local integer id = 0
if STACK_SIZE > 0 then
set STACK_SIZE = STACK_SIZE - 1
set id = STACK[STACK_SIZE]
else
set id = ASSIGNED
set ASSIGNED = ASSIGNED + 1
if ASSIGNED > MAX_INDEX then
set MAX_INDEX = ASSIGNED
endif
endif
call SetUnitUserData(GetFilterUnit(), id)
set UNIT_STACK[id] = GetFilterUnit()
return true
endfunction
private function GroupAdd takes nothing returns nothing
local integer id = 0
if STACK_SIZE > 0 then
set STACK_SIZE = STACK_SIZE - 1
set id = STACK[STACK_SIZE]
else
set id = ASSIGNED
set ASSIGNED = ASSIGNED + 1
if ASSIGNED > MAX_INDEX then
set MAX_INDEX = ASSIGNED
endif
endif
call SetUnitUserData(GetEnumUnit(), id)
set UNIT_STACK[id] = GetEnumUnit()
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
local region r = CreateRegion()
local group g = CreateGroup()
local integer i = 0
//Use a filterfunc so units are indexed immediately
call RegionAddRect(r, bj_mapInitialPlayableArea)
call TriggerRegisterEnterRegion(t, r, And(Condition(function UnitFilter), Condition(function Add)))
//Start the timer to recycle indexes
call TimerStart(CreateTimer(), TIMER_PERIODICITY, true, function Clear)
//Loop and group per player to grab all units, including those with locust
loop
exitwhen i > 15
call GroupEnumUnitsOfPlayer(g, Player(i), Condition(function UnitFilter))
call ForGroup(g, function GroupAdd)
set i = i + 1
endloop
call DestroyGroup(g)
set r = null
set t = null
set g = null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library LastOrder initializer Init needs UnitIndexingUtils
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library has a lot of usefulness for when you want to interface with the
//* last order a unit was given. This can be useful for simulating spell errors
//* and where you'd want to give them back the order they had prior to the spell
//* cast (whereas without this library, they'd just forget their orders).
//*
//* There are some handy interfacing options for your use here --
//* function GetLastOrderId takes unit u returns integer
//* function GetLastOrderString takes unit u returns string
//* function GetLastOrderType takes unit u returns integer
//* function GetLastOrderX takes unit u returns real
//* function GetLastOrderY takes unit u returns real
//* function GetLastOrderTarget takes unit u returns widget
//* function AbortOrder takes unit u returns boolean
//*
//* There are also some order commands that can be useful --
//* function IssueLastOrder takes unit u returns boolean
//* function IssueSecondLastOrder takes unit u returns boolean
//* function IsLastOrderFinished takes unit u returns boolean
//*
//* You can access any information you'd like about the orders for your own
//* order handling needs.
//*
globals
//* Storage for last order
private integer array Order
private integer array Type
private widget array Targ
private boolean array Flag
private real array X
private real array Y
//* Storage for second last order
private integer array P_Order
private integer array P_Type
private widget array P_Targ
private boolean array P_Flag
private real array P_X
private real array P_Y
//* Order type variables
constant integer ORDER_TYPE_TARGET = 1
constant integer ORDER_TYPE_POINT = 2
constant integer ORDER_TYPE_IMMEDIATE = 3
//* Trigger for the order catching
private trigger OrderTrg = CreateTrigger()
endglobals
//**********************************************************
function GetLastOrderId takes unit u returns integer
return Order[GetUnitId(u)]
endfunction
function GetLastOrderString takes unit u returns string
return OrderId2String(Order[GetUnitId(u)])
endfunction
function GetLastOrderType takes unit u returns integer
return Type[GetUnitId(u)]
endfunction
function GetLastOrderX takes unit u returns real
return X[GetUnitId(u)]
endfunction
function GetLastOrderY takes unit u returns real
return Y[GetUnitId(u)]
endfunction
function GetLastOrderTarget takes unit u returns widget
return Targ[GetUnitId(u)]
endfunction
//**********************************************************
private function OrderExclusions takes unit u, integer id returns boolean
//* Excludes specific orders or unit types from registering with the system
//*
//* 851972: stop
//* Stop is excluded from the system, but you can change it by
//* adding a check for it below. id == 851972
//*
//* 851971: smart
//* 851986: move
//* 851983: attack
//* 851984: attackground
//* 851990: patrol
//* 851993: holdposition
//* These are the UI orders that are passed to the system.
//*
//* >= 852055, <= 852762
//* These are all spell IDs from defend to incineratearrowoff with
//* a bit of leeway at the ends for orders with no strings.
//*
return id == 851971 or id == 851986 or id == 851983 or id == 851984 or id == 851990 or id == 851993 or (id >= 852055 and id <= 852762)
endfunction
private function LastOrderFilter takes unit u returns boolean
//* Some criteria for whether or not a unit's last order should be given
//*
//* INSTANT type orders are excluded because generally, reissuing an instant
//* order doesn't make sense. You can remove that check below if you'd like,
//* though.
//*
//* The Type check is really just to ensure that no spell recursion can
//* occur with IssueLastOrder. The problem with intercepting the spell cast
//* event is that it happens after the order is 'caught' and registered to
//* this system. Therefore, to just IssueLastOrder tells it to recast the
//* spell! That's a problem, so we need a method to eliminate it.
//*
local integer id = GetUnitId(u)
return u != null and GetWidgetLife(u) > 0.405 and Type[id] != ORDER_TYPE_IMMEDIATE
endfunction
private function SecondLastOrderFilter takes unit u returns boolean
//* Same as above but with regard to the second last order issued
local integer id = GetUnitId(u)
return u != null and GetWidgetLife(u) > 0.405 and P_Type[id] != ORDER_TYPE_IMMEDIATE and P_Order[id] != Order[id]
endfunction
//**********************************************************
function IsLastOrderFinished takes unit u returns boolean
return (GetUnitCurrentOrder(u) == 0 and Order[GetUnitId(u)] != 851972) or Flag[GetUnitId(u)]
endfunction
function IssueLastOrder takes unit u returns boolean
local integer id = GetUnitId(u)
local boolean b = false
if LastOrderFilter(u) and Order[id] != 0 and not Flag[id] then
if Type[id] == ORDER_TYPE_TARGET then
set b = IssueTargetOrderById(u, Order[id], Targ[id])
elseif Type[id] == ORDER_TYPE_POINT then
set b = IssuePointOrderById(u, Order[id], X[id], Y[id])
elseif Type[id] == ORDER_TYPE_IMMEDIATE then
set b = IssueImmediateOrderById(u, Order[id])
endif
endif
return b
endfunction
function IssueSecondLastOrder takes unit u returns boolean
//* This function has to exist because of spell recursion
local integer id = GetUnitId(u)
local boolean b = false
if SecondLastOrderFilter(u) and P_Order[id] != 0 and not P_Flag[id] then
if P_Type[id] == ORDER_TYPE_TARGET then
set b = IssueTargetOrderById(u, P_Order[id], P_Targ[id])
elseif P_Type[id] == ORDER_TYPE_POINT then
set b = IssuePointOrderById(u, P_Order[id], P_X[id], P_Y[id])
elseif P_Type[id] == ORDER_TYPE_IMMEDIATE then
set b = IssueImmediateOrderById(u, P_Order[id])
endif
endif
return b
endfunction
function AbortOrder takes unit u returns boolean
local boolean b = true
if IsUnitPaused(u) then
set b = false
else
call PauseUnit(u, true)
call IssueImmediateOrder(u, "stop")
call PauseUnit(u, false)
endif
return b
endfunction
//**********************************************************
private function Conditions takes nothing returns boolean
return OrderExclusions(GetTriggerUnit(), GetIssuedOrderId())
endfunction
private function Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local integer id = GetUnitId(u)
//* Store second to last order to eliminate spell recursion
set P_Order[id] = Order[id]
set P_Targ[id] = Targ[id]
set P_Type[id] = Type[id]
set P_Flag[id] = Flag[id]
set P_X[id] = X[id]
set P_Y[id] = Y[id]
set Flag[id] = false
set Order[id] = GetIssuedOrderId()
if GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER then
set Targ[id] = GetOrderTarget()
set Type[id] = ORDER_TYPE_TARGET
set X[id] = GetWidgetX(GetOrderTarget())
set Y[id] = GetWidgetY(GetOrderTarget())
elseif GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER then
set Targ[id] = null
set Type[id] = ORDER_TYPE_POINT
set X[id] = GetOrderPointX()
set Y[id] = GetOrderPointY()
elseif GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_ORDER then
set Targ[id] = null
set Type[id] = ORDER_TYPE_IMMEDIATE
set X[id] = GetUnitX(u)
set Y[id] = GetUnitY(u)
debug else
debug call BJDebugMsg(SCOPE_PREFIX+" Error: Order Doesn't Exist")
endif
set u = null
endfunction
//**********************************************************
private function SpellActions takes nothing returns nothing
set Flag[GetUnitId(GetTriggerUnit())] = true
endfunction
//**********************************************************
private function Init takes nothing returns nothing
local trigger trg = CreateTrigger()
call TriggerAddAction(OrderTrg, function Actions)
call TriggerAddCondition(OrderTrg, Condition(function Conditions))
call TriggerRegisterAnyUnitEventBJ(OrderTrg, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
call TriggerRegisterAnyUnitEventBJ(OrderTrg, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
call TriggerRegisterAnyUnitEventBJ(OrderTrg, EVENT_PLAYER_UNIT_ISSUED_ORDER)
call TriggerAddAction(trg, function SpellActions)
call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_SPELL_EFFECT)
set trg = null
endfunction
endlibrary
//TESH.scrollpos=4
//TESH.alwaysfold=0
//******************************************************************************
//* *
//* K N O C K B A C K *
//* Actual Code *
//* v1.06 *
//* *
//* By: Rising_Dusk *
//* *
//******************************************************************************
library Knockback requires IsTerrainWalkable, LastOrder
globals
//*********************************************************
//* These are the configuration constants for the system
//*
//* EFFECT_ATTACH_POINT: Where on the unit the effect attaches
//* EFFECT_PATH_GROUND: What special effect to attach over ground
//* TIMER_INTERVAL: The interval for the timer that gets run
//* ISSUE_LAST_ORDER: A boolean to issue last orders or not
//*
private constant string EFFECT_ATTACH_POINT = "origin"
private constant string EFFECT_PATH = "Objects\\Effects\\KnockbackDust.mdl"
private constant real TIMER_INTERVAL = 0.05
private constant boolean ISSUE_LAST_ORDER = true
//*********************************************************
//* These are static constants used by the system and shouldn't be changed
//*
//* Timer: The timer that runs all of the effects for the spell
//* Counter: The counter for how many KB instances exist
//* HitIndex: Indexes for a given unit's knockback
//* Knockers: The array of all struct instances that exist
//* Entries: Counters for specific unit instances in system
//* ToClear: How many instances to remove on next run
//* AdjBoolexpr: The check for picking adjacent units to knockback
//*
private timer Timer = CreateTimer()
private integer Counter = 0
private integer array HitIndex
private integer array Knockers
private integer array Entries
private integer array ToClear
private boolexpr AdjBoolexpr = null
//* Temporary variables used by the system
private real TempX = 0.
private real TempY = 0.
private unit TempUnit1 = null
private unit TempUnit2 = null
endglobals
//******************************************************************************
//* Some additional functions that can be used
function KnockbackStop takes unit targ returns boolean
local integer id = GetUnitId(targ)
set ToClear[id] = Entries[id]
return ToClear[id] > 0
endfunction
function IsKnockedBack takes unit targ returns boolean
return Entries[GetUnitId(targ)] > 0
endfunction
//* Struct for the system, I recommend leaving it alone
private struct knocker
unit Source = null
unit Target = null
group HitGroup = null
effect KBEffect = null
real Decrement = 0.
real Displace = 0.
real CosA = 0.
real SinA = 0.
static method create takes unit source, unit targ, real angle, real disp, real dec returns knocker
local knocker n = knocker.allocate()
set n.Target = targ
set n.Source = source
set n.Decrement = dec
set n.Displace = disp
set n.CosA = Cos(angle)
set n.SinA = Sin(angle)
set n.KBEffect = AddSpecialEffectTarget(EFFECT_PATH, n.Target, EFFECT_ATTACH_POINT)
return n
endmethod
private method onDestroy takes nothing returns nothing
local integer id = GetUnitId(this.Target)
set Entries[id] = Entries[id] - 1
if GetWidgetLife(this.Target) > 0.405 and Entries[id] <= 0 and ISSUE_LAST_ORDER then
call IssueLastOrder(this.Target)
endif
call DestroyEffect(this.KBEffect)
endmethod
endstruct
private function Update takes nothing returns nothing
local unit u = null
local unit s = null
local rect r = null
local knocker n = 0
local integer i = Counter - 1
local integer j = 0
local integer id = 0
local real xi = 0.
local real yi = 0.
local real xf = 0.
local real yf = 0.
loop
exitwhen i < 0
set n = Knockers[i]
set u = n.Target
set id = GetUnitId(u)
set xi = GetUnitX(u)
set yi = GetUnitY(u)
if n.Displace <= 0 or ToClear[id] > 0 then
//* Clean up the knockback when it is over
if ToClear[id] > 0 then
set ToClear[id] = ToClear[id] - 1
endif
call n.destroy()
set Counter = Counter - 1
if Counter < 0 then
call PauseTimer(Timer)
set Counter = 0
else
set Knockers[i] = Knockers[Counter]
endif
else
set xf = xi + n.Displace*n.CosA
set yf = yi + n.Displace*n.SinA
if IsTerrainWalkable(xf, yf) then
call SetUnitPosition(u, xf, yf)
set n.Displace = n.Displace - n.Decrement
else
call KnockbackStop(u)
endif
endif
set i = i - 1
endloop
set u = null
set s = null
endfunction
//******************************************************************************
//* How to knockback a unit
function KnockbackTarget takes unit source, unit targ, real angle, real startspeed, real decrement returns boolean
local knocker n = 0
local integer id = GetUnitId(targ)
local boolean b = true
//* Protect users from themselves
if decrement <= 0. or startspeed <= 0. or targ == null then
debug call BJDebugMsg(SCOPE_PREFIX+" Error (On Call): Invalid Starting Conditions")
set b = false
endif
set n = knocker.create(source, targ, angle, startspeed*TIMER_INTERVAL, decrement)
if Counter == 0 then
call TimerStart(Timer, TIMER_INTERVAL, true, function Update)
endif
set Entries[id] = Entries[id] + 1
set HitIndex[id] = Counter + 1
set Knockers[Counter] = n
set Counter = Counter + 1
return b
endfunction
endlibrary
//TESH.scrollpos=390
//TESH.alwaysfold=0
scope FiryStones initializer init
//*************************************************************************************************************//
// Firy Stones //
// by //
// cedi //
// //
// needs: TimerUtils by Vexorian //
// Bound Sentinel by Vexorian //
// IsTerrainWalkable by Antitarf //
// Dummy Model by //
// Knockback by Rising_Dusk //
// LastOrder by Rising_Dusk //
// UnitIndexingUtils by Rising_Dusk //
//*************************************************************************************************************//
//For use, copy the trigger to your map, copy the dummy create a spell and adjust the values below.
globals
//ID of the dummy spell
private constant integer SPELL_ID = 'A000'
//ID of the DoT spell
private constant integer DOT_ID = 'A001'
//ID of the enstrenghten spell
private constant integer ENSTRENGHT_ID = 'A002'
//ID of the dummy
private constant integer DUMMY_ID = 'h000'
//How many levels does your spell have?
private constant integer SPELL_LEVEL_COUNT = 4
//Amount of missiles
private constant integer MISSILE_COUNT = 8
//How much armor should each missile increase? Beware Max of 40 / MISSILE COUNT.
private constant integer ARMOR_PER_MISSILE = 1
//The move timer interval
private constant real TIMER_INTERVAL = 0.02
//Interval of the unit check
private constant real PICK_INTERVAL = 0.10
//Missile speed in ms
private constant real SPEED = 700.00
//Damage decrease per collision in percent
private constant real DAMAGE_LOOSE = 0.15
//Range decrease per collision in percent
private constant real RANGE_LOOSE = 0.15
//Knockback speed decrease per collision in percent
private constant real KNOCKBACK_LOOSE = 0.15
//Start turn speed.
private constant real SPEED_ANGLE = 3.00
//Increase of the turn speed.
private constant real SPEED_ANGLE_INC = 0.01
//Maximal turn speed.
private constant real MAX_SPEED_ANGLE = 6.00
//Distance between missiles and caster.
private constant real START_DISTANCE = 150.00
//Delay of the missiles.
private constant real MAX_SHOOT_DELAY = 5.00
//Size of the missiles 1.00 == 100%
private constant real SIZE = 1.00
//Collisions size of the missiles.
private constant real COLLISIONS_SIZE = 75.00
//Multipler of the damage for the end aoe damage.
private constant real AOE_DAMAGE_INC = 3.00
//Decrement of the knockback speed.
private constant real DECREMENT = 2.00
//Fly height of the missiles.
private constant real HEIGHT = 50.00
//Model of the missiles.
private constant string STONE_MODEL = "Abilities\\Weapons\\BallsOfFireMissile\\BallsOfFireMissile.mdl"
//Order string of the DoT spell.
private constant string DOT_ORDER_STRING = "acidbomb"
//Model when a missile is created.
private constant string CREATE_EFFECT = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"
//Model when a missile dies.
private constant string DESTROY_EFFECT = "Objects\\Spawnmodels\\Other\\NeutralBuildingExplosion\\NeutralBuildingExplosion.mdl"
//model when the missiles flies away.
private constant string SHOOT_EFFECT = ""
//Model when the missiles deal damage
private constant string DAMAGE_EFFECT = ""
//Should the missiles turn to when they fly away?
private constant boolean TURN = false
// SYSTEM // SYSTEM // SYSTEM // SYSTEM // SYSTEM // SYSTEM // SYSTEM // SYSTEM // SYSTEM //
private integer array MAX_UNIT_HITTED[SPELL_LEVEL_COUNT]
private real array RANGE[SPELL_LEVEL_COUNT]
private real array AOE[SPELL_LEVEL_COUNT]
private real array DAMAGE[SPELL_LEVEL_COUNT]
private real array KNOCKBACK_RANGE[SPELL_LEVEL_COUNT]
private real array ROTATE_TIME[SPELL_LEVEL_COUNT]
private real REAL_SPEED = SPEED * TIMER_INTERVAL
private real TEMP_REAL
private group TEMP_GROUP = CreateGroup()
private player TEMP_PLAYER
endglobals
//Max amount of units hit by one missile.
private function SET_MAX_UNIT_HITTED takes nothing returns nothing
set MAX_UNIT_HITTED[1] = 2
set MAX_UNIT_HITTED[2] = 3
set MAX_UNIT_HITTED[3] = 4
set MAX_UNIT_HITTED[4] = 5
endfunction
//Max range.
private function SET_RANGE takes nothing returns nothing
set RANGE[1] = 700.00
set RANGE[2] = 750.00
set RANGE[3] = 800.00
set RANGE[4] = 850.00
endfunction
//AoE of the end damage
private function SET_AOE takes nothing returns nothing
set AOE[1] = 150.00
set AOE[2] = 200.00
set AOE[3] = 250.00
set AOE[4] = 300.00
endfunction
//Damage dealt by the missiles.
private function SET_DAMAGE takes nothing returns nothing
set DAMAGE[1] = 150.00
set DAMAGE[2] = 200.00
set DAMAGE[3] = 250.00
set DAMAGE[4] = 300.00
endfunction
//Not real, its the knockback speed in ms.
private function SET_KNOCKBACK_RANGE takes nothing returns nothing
set KNOCKBACK_RANGE[1] = 350.00
set KNOCKBACK_RANGE[2] = 400.00
set KNOCKBACK_RANGE[3] = 550.00
set KNOCKBACK_RANGE[4] = 600.00
endfunction
//The missiles rotate x seconds around the caster.
private function SET_ROTATE_TIME takes nothing returns nothing
set ROTATE_TIME[1] = 5.00
set ROTATE_TIME[2] = 6.00
set ROTATE_TIME[3] = 7.00
set ROTATE_TIME[4] = 8.00
endfunction
//*************************************************************************************************************//
// SYSTEM //
//*************************************************************************************************************//
private function IsPossibleTarget takes nothing returns boolean
return GetWidgetLife( GetFilterUnit() ) > 0.405 and IsUnitEnemy( GetFilterUnit(), TEMP_PLAYER ) and IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) == true and IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) == false
endfunction
struct Stone
unit missile = null
effect model = null
integer hitcounter = 0
integer place = 0
real dist = START_DISTANCE
real rotatetime = 0.00
real range = 0.00
real angle = 0.00
real speedangle = SPEED_ANGLE
real vx = 0.00
real vy = 0.00
real dmg = 0.00
real kbrange = 0.00
real ptime = 0.00
group hits = null
boolean rotate = true
FiryStone root = 0
private method Cast takes unit u returns nothing
local unit dummy = CreateUnit( GetOwningPlayer( .missile ), DUMMY_ID, GetUnitX( u ), GetUnitY( u ), 0.00 )
call UnitAddAbility( dummy, DOT_ID )
call SetUnitAbilityLevel( dummy, DOT_ID, .root.level )
call IssueTargetOrder( dummy, DOT_ORDER_STRING, u )
call UnitApplyTimedLife( dummy, 'BTLF', 2.00 )
set dummy = null
set u = null
endmethod
private method Dealdamage takes unit u, real x, real y returns nothing
local real angle = Atan2(GetUnitY( u ) - y, GetUnitX( u ) - x)
call .Cast( u )
call UnitDamageTarget( .missile, u, .dmg, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null )
call KnockbackTarget( .missile, u, angle, .kbrange, DECREMENT )
call DestroyEffect( AddSpecialEffect( DAMAGE_EFFECT, x, y ) )
set .dmg = .dmg * ( 1.00 - DAMAGE_LOOSE )
set .range = .range * ( 1.00 - RANGE_LOOSE )
set .kbrange = .kbrange * ( 1.00 - KNOCKBACK_LOOSE )
set .hitcounter = .hitcounter - 1
if .hitcounter <= 0 then
call .destroy()
endif
set u = null
endmethod
static method create takes FiryStone root, real angle, integer i returns Stone
local Stone data = Stone.allocate()
local real x = GetUnitX( root.caster ) + Cos( angle * bj_DEGTORAD ) * START_DISTANCE
local real y = GetUnitY( root.caster ) + Sin( angle * bj_DEGTORAD ) * START_DISTANCE
set data.root = root
set data.angle = angle
set data.missile = CreateUnit( GetOwningPlayer( root.caster ), DUMMY_ID, x, y, angle )
set data.model = AddSpecialEffectTarget( STONE_MODEL, data.missile, "origin" )
set data.hits = CreateGroup()
set data.rotatetime = ROTATE_TIME[root.level] + GetRandomReal( -MAX_SHOOT_DELAY, MAX_SHOOT_DELAY )
set data.range = RANGE[root.level]
set data.dmg = DAMAGE[root.level]
set data.kbrange = KNOCKBACK_RANGE[root.level]
set data.hitcounter = MAX_UNIT_HITTED[root.level]
set data.place = i
call SetUnitFlyHeight( data.missile, HEIGHT, 0.00 )
call SetUnitScale( data.missile, SIZE, SIZE, SIZE )
call DestroyEffect( AddSpecialEffect( CREATE_EFFECT, x, y ) )
return data
endmethod
method onDestroy takes nothing returns nothing
local unit u
local real x = GetUnitX( .missile )
local real y = GetUnitY( .missile )
local integer i = 0
set TEMP_PLAYER = GetOwningPlayer( .missile )
call GroupEnumUnitsInRange( TEMP_GROUP, x, y, AOE[.root.level], Condition( function IsPossibleTarget ) )
loop
set u = FirstOfGroup( TEMP_GROUP )
exitwhen u == null
call .Cast( u )
set TEMP_REAL = Atan2(GetUnitY( u ) - y, GetUnitX( u ) - x)
call UnitDamageTarget( .missile, u, .dmg * AOE_DAMAGE_INC, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null )
call KnockbackTarget( .missile, u, TEMP_REAL, .kbrange, DECREMENT )
call DestroyEffect( AddSpecialEffect( DAMAGE_EFFECT, x, y ) )
call GroupRemoveUnit( TEMP_GROUP, u )
endloop
call GroupClear( TEMP_GROUP )
call DestroyEffect( AddSpecialEffect( DESTROY_EFFECT, x, y ) )
call DestroyEffect( .model )
call KillUnit( .missile )
call GroupClear( .hits )
call DestroyGroup( .hits )
set i = GetUnitAbilityLevel( .root.caster, ENSTRENGHT_ID )
if i <= ARMOR_PER_MISSILE then
call UnitRemoveAbility( .root.caster, ENSTRENGHT_ID )
else
call SetUnitAbilityLevel( .root.caster, ENSTRENGHT_ID, i - ARMOR_PER_MISSILE )
endif
set .root.There[.place] = false
set .root.stonecount = .root.stonecount - 1
if .root.stonecount <= 0 then
set .root.destroyMe = true
endif
endmethod
private method Move takes nothing returns nothing
local real x = GetUnitX( .missile ) + .vx
local real y = GetUnitY( .missile ) + .vy
set .dist = .dist + REAL_SPEED
set .range = .range - REAL_SPEED
call SetUnitX( .missile, x )
call SetUnitY( .missile, y )
if IsTerrainWalkable( x, y ) == false or .range <= 0.00 then
call .destroy()
endif
endmethod
private method Rotate takes nothing returns nothing
if .speedangle >= MAX_SPEED_ANGLE then
set .speedangle = MAX_SPEED_ANGLE
else
set .speedangle = .speedangle + SPEED_ANGLE_INC
endif
set .angle = .angle + .speedangle
if .angle > 360.00 then
set .angle = .angle - 360.00
elseif .angle < 0.00 then
set .angle = .angle + 360.00
endif
call SetUnitX( .missile, GetUnitX( .root.caster ) + Cos( .angle * bj_DEGTORAD ) * START_DISTANCE )
call SetUnitY( .missile, GetUnitY( .root.caster ) + Sin( .angle * bj_DEGTORAD ) * START_DISTANCE )
endmethod
private method RotateEx takes nothing returns nothing
set .speedangle = .speedangle - SPEED_ANGLE_INC * 3.00
set .angle = .angle + .speedangle
if .angle > 360.00 then
set .angle = .angle - 360.00
elseif .angle < 0.00 then
set .angle = .angle + 360.00
endif
call SetUnitX( .missile, GetUnitX( .root.caster ) + Cos( .angle * bj_DEGTORAD ) * .dist )
call SetUnitY( .missile, GetUnitY( .root.caster ) + Sin( .angle * bj_DEGTORAD ) * .dist )
endmethod
method Control takes nothing returns nothing
local unit u
local real x
local real y
if GetWidgetLife( .root.caster ) <= 0.405 then
call .destroy()
endif
if .rotate == true then
set .rotatetime = .rotatetime - TIMER_INTERVAL
if .rotatetime <= 0.00 then
call DestroyEffect( AddSpecialEffect( SHOOT_EFFECT, GetUnitX( .missile ), GetUnitY( .missile ) ) )
set .rotate = false
set TEMP_REAL = Atan2(GetUnitY( .missile ) - GetUnitY( .root.caster ), GetUnitX( .missile ) - GetUnitX( .root.caster ) )
set .vx = Cos( TEMP_REAL ) * REAL_SPEED
set .vy = Sin( TEMP_REAL ) * REAL_SPEED
else
call .Rotate()
endif
else
call .Move()
if TURN then
call .RotateEx()
endif
endif
set .ptime = .ptime + TIMER_INTERVAL
if .ptime >= PICK_INTERVAL then
set .ptime = 0.00
set x = GetUnitX( .missile )
set y = GetUnitY( .missile )
set TEMP_PLAYER = GetOwningPlayer( .missile )
call GroupEnumUnitsInRange( TEMP_GROUP, x, y, COLLISIONS_SIZE, Condition( function IsPossibleTarget ) )
loop
set u = FirstOfGroup( TEMP_GROUP )
exitwhen u == null
if IsUnitInGroup( u, .hits ) == false then
call GroupAddUnit( .hits, u )
call .Dealdamage( u, x, y )
endif
call GroupRemoveUnit( TEMP_GROUP, u )
endloop
call GroupClear( TEMP_GROUP )
endif
endmethod
endstruct
private function OutControl takes nothing returns nothing
local FiryStone data = GetTimerData( GetExpiredTimer() )
call data.Main()
endfunction
struct FiryStone
unit caster = null
timer time = null
integer stonecount = MISSILE_COUNT
integer level = 0
boolean destroyMe = false
boolean array There[MISSILE_COUNT]
Stone array stones[MISSILE_COUNT]
method onDestroy takes nothing returns nothing
call PauseTimer( .time )
call ReleaseTimer( .time )
endmethod
method Main takes nothing returns nothing
local integer i = 0
if .destroyMe == true then
call .destroy()
endif
loop
exitwhen i >= MISSILE_COUNT
if .There[i] == true then
call .stones[i].Control()
endif
set i = i + 1
endloop
endmethod
static method create takes unit caster, integer level returns FiryStone
local FiryStone data = FiryStone.allocate()
local integer i = 0
local real angle = 360.00 / I2R( MISSILE_COUNT )
set data.caster = caster
set data.time = NewTimer()
set data.level = level
call UnitAddAbility( caster, ENSTRENGHT_ID )
call SetUnitAbilityLevel( caster, ENSTRENGHT_ID, MISSILE_COUNT * ARMOR_PER_MISSILE )
loop
exitwhen i == MISSILE_COUNT
set data.There[i] = true
set data.stones[i] = Stone.create( data, angle * I2R( i ), i )
set i = i + 1
endloop
call SetTimerData( data.time, data )
call TimerStart( data.time, TIMER_INTERVAL, true, function OutControl )
return data
endmethod
endstruct
private function Action takes nothing returns nothing
local unit u = GetTriggerUnit()
local integer lvl = GetUnitAbilityLevel( u, SPELL_ID )
local FiryStone data = FiryStone.create( u, lvl )
set u = null
endfunction
private function IsSpell takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( t, Condition( function IsSpell ) )
call TriggerAddAction( t, function Action )
call SET_MAX_UNIT_HITTED()
call SET_RANGE()
call SET_AOE()
call SET_DAMAGE()
call SET_KNOCKBACK_RANGE()
call SET_ROTATE_TIME()
set t = null
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
function Trig_Revive_Units_Conditions takes nothing returns boolean
return IsUnitType(GetTriggerUnit(), UNIT_TYPE_HERO) == false
endfunction
function Trig_Revive_Units_Actions takes nothing returns nothing
call TriggerSleepAction( 10.00 )
call CreateUnit( GetOwningPlayer( GetTriggerUnit() ), GetUnitTypeId( GetTriggerUnit() ), GetUnitX( GetTriggerUnit() ), GetUnitY( GetTriggerUnit() ), GetUnitFacing( GetTriggerUnit() ) )
endfunction
//===========================================================================
function InitTrig_Revive_Units takes nothing returns nothing
set gg_trg_Revive_Units = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_Revive_Units, EVENT_PLAYER_UNIT_DEATH )
call TriggerAddCondition( gg_trg_Revive_Units, Condition( function Trig_Revive_Units_Conditions ) )
call TriggerAddAction( gg_trg_Revive_Units, function Trig_Revive_Units_Actions )
endfunction