Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Table
//***************************************************************
//* Table object 3.0
//* ------------
//*
//* set t=Table.create() - instanceates a new table object
//* call t.destroy() - destroys it
//* t[1234567] - Get value for key 1234567
//* (zero if not assigned previously)
//* set t[12341]=32 - Assigning it.
//* call t.flush(12341) - Flushes the stored value, so it
//* doesn't use any more memory
//* t.exists(32) - Was key 32 assigned? Notice
//* that flush() unassigns values.
//* call t.reset() - Flushes the whole contents of the
//* Table.
//*
//* call t.destroy() - Does reset() and also recycles the id.
//*
//* If you use HandleTable instead of Table, it is the same
//* but it uses handles as keys, the same with StringTable.
//*
//* You can use Table on structs' onInit if the struct is
//* placed in a library that requires Table or outside a library.
//*
//* You can also do 2D array syntax if you want to touch
//* mission keys directly, however, since this is shared space
//* you may want to prefix your mission keys accordingly:
//*
//* set Table["thisstring"][ 7 ] = 2
//* set Table["thisstring"][ 5 ] = Table["thisstring"][7]
//*
//***************************************************************
//=============================================================
globals
private constant integer MAX_INSTANCES=8100 //400000
//Feel free to change max instances if necessary, it will only affect allocation
//speed which shouldn't matter that much.
//=========================================================
private hashtable ht
endglobals
private struct GTable[MAX_INSTANCES]
method reset takes nothing returns nothing
call FlushChildHashtable(ht, integer(this) )
endmethod
private method onDestroy takes nothing returns nothing
call this.reset()
endmethod
//=============================================================
// initialize it all.
//
private static method onInit takes nothing returns nothing
set ht = InitHashtable()
endmethod
endstruct
//Hey: Don't instanciate other people's textmacros that you are not supposed to, thanks.
//! textmacro Table__make takes name, type, key
struct $name$ extends GTable
method operator [] takes $type$ key returns integer
return LoadInteger(ht, integer(this), $key$)
endmethod
method operator []= takes $type$ key, integer value returns nothing
call SaveInteger(ht, integer(this) ,$key$, value)
endmethod
method flush takes $type$ key returns nothing
call RemoveSavedInteger(ht, integer(this), $key$)
endmethod
method exists takes $type$ key returns boolean
return HaveSavedInteger( ht, integer(this) ,$key$)
endmethod
static method flush2D takes string firstkey returns nothing
call $name$(- StringHash(firstkey)).reset()
endmethod
static method operator [] takes string firstkey returns $name$
return $name$(- StringHash(firstkey) )
endmethod
endstruct
//! endtextmacro
//! runtextmacro Table__make("Table","integer","key" )
//! runtextmacro Table__make("StringTable","string", "StringHash(key)" )
//! runtextmacro Table__make("HandleTable","handle","GetHandleId(key)" )
endlibrary
//TESH.scrollpos=12
//TESH.alwaysfold=0
library TerrainPathability initializer Initialization
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This can be used to detect the type of pathing a specific point of terrain
//* is, whether land, shallow water, or deep water. This type of detection
//* should have been easy to do using natives, but the IsTerrainPathable(...)
//* native is very counterintuitive and does not permit easy detection in one
//* call. For that reason, this library was developed.
//*
//* The system requires a dummy unit of some sort. There are no real
//* requirements upon the dummy unit, but it needs a non-zero movement speed.
//* More importantly than the dummy unit, though, it needs a custom windwalk
//* based unit ability with a 0.00 duration and no fade time. Both of those
//* raw id's (for the unit and windwalk dummy) need to be configured below.
//*
//* There is an objectmerger call available for those of you too lazy to build
//* your own windwalk based ability. Simply uncomment it below and save once,
//* then close and reopen your map and recomment the line.
//*
globals
constant integer TERRAIN_PATHING_DEEP = 1
constant integer TERRAIN_PATHING_SHALLOW = 2
constant integer TERRAIN_PATHING_LAND = 3
constant integer TERRAIN_PATHING_WALKABLE = 4
private unit Dummy = null
private constant integer DUMMY_UNIT_ID = 'hfoo'
private constant integer DUMMY_WINDWALK_ID = 'A001'
private constant player OWNING_PLAYER = Player(15)
//* These variables shouldn't be adjusted
private real WorldMinX = 0.
private real WorldMinY = 0.
endglobals
////! external ObjectMerger w3a ANwk win& anam "Collision Ability" ansf "" Owk3 1 0.0 Owk4 1 0 Owk2 1 0.0 Owk1 1 0.0 acdn 1 0.0 ahdu 1 0.0 adur 1 0. aher 0 amcs 1 0
function IsTerrainPathingType takes real x, real y, integer terrainPathingType returns boolean
local boolean b = false
if terrainPathingType == TERRAIN_PATHING_DEEP then
set b = not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
elseif terrainPathingType == TERRAIN_PATHING_SHALLOW then
set b = not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
elseif terrainPathingType == TERRAIN_PATHING_LAND then
set b = IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY)
elseif terrainPathingType == TERRAIN_PATHING_WALKABLE then
call SetUnitPosition(Dummy, x, y)
set b = GetUnitX(Dummy) == x and GetUnitY(Dummy) == y and not (not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY))
call SetUnitX(Dummy, WorldMinX)
call SetUnitY(Dummy, WorldMinY)
endif
return b
endfunction
private function Initialization takes nothing returns nothing
set WorldMinX = GetRectMinX(bj_mapInitialPlayableArea)
set WorldMinY = GetRectMinY(bj_mapInitialPlayableArea)
set Dummy = CreateUnit(OWNING_PLAYER, DUMMY_UNIT_ID, 0., 0., 0.)
call UnitAddAbility(Dummy, DUMMY_WINDWALK_ID)
call UnitAddAbility(Dummy, 'Avul')
call IssueImmediateOrderById(Dummy, 852129)
call SetUnitX(Dummy, WorldMinX)
call SetUnitY(Dummy, WorldMinY)
endfunction
endlibrary
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
library GroupUtils
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a simple implementation of a stack for groups that need to
//* be in the user's control for greater than an instant of time. Additionally,
//* this library provides a single, global group variable for use with user-end
//* enumerations. It is important to note that users should not be calling
//* DestroyGroup() on the global group, since then it may not exist for when it
//* it is next needed.
//*
//* The group stack removes the need for destroying groups and replaces it with
//* a recycling method.
//* function NewGroup takes nothing returns group
//* function ReleaseGroup takes group g returns boolean
//* function GroupRefresh takes group g returns nothing
//*
//* NewGroup grabs a currently unused group from the stack or creates one if the
//* stack is empty. You can use this group however you'd like, but always
//* remember to call ReleaseGroup on it when you are done with it. If you don't
//* release it, it will 'leak' and your stack may eventually overflow if you
//* keep doing that.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hash table. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it.
//*
globals
//* Group for use with all instant enumerations
group ENUM_GROUP = CreateGroup()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Assorted constants
private constant integer MAX_HANDLE_COUNT = 408000
private constant integer MIN_HANDLE_ID = 0x100000
//* Arrays and counter for the group stack
private group array Groups
private integer array Status[MAX_HANDLE_COUNT]
private integer Count = 0
endglobals
private function AddEx takes nothing returns nothing
if Flag then
call GroupClear(Refr)
set Flag = false
endif
call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
set Flag = true
set Refr = g
call ForGroup(Refr, function AddEx)
if Flag then
call GroupClear(g)
endif
endfunction
function NewGroup takes nothing returns group
if Count == 0 then
set Groups[0] = CreateGroup()
else
set Count = Count - 1
endif
set Status[GetHandleId(Groups[Count])-MIN_HANDLE_ID] = 1
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer stat = Status[GetHandleId(g)-MIN_HANDLE_ID]
local boolean b = true
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+" Error: Null groups cannot be released")
set b = false
elseif stat == 0 then
debug call BJDebugMsg(SCOPE_PREFIX+" Error: Group not part of stack")
set b = false
elseif stat == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+" Error: Groups cannot be multiply released")
set b = false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+" Error: Max groups achieved, destroying group")
call DestroyGroup(g)
set b = false
else
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
set Status[GetHandleId(g)-MIN_HANDLE_ID] = 2
endif
return b
endfunction
endlibrary
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=191
//TESH.alwaysfold=0
//******************************************************************************
//* *
//* K N O C K B A C K *
//* Actual Code *
//* v1.07 *
//* *
//* By: Rising_Dusk *
//* *
//******************************************************************************
library Knockback initializer Init needs TerrainPathability, GroupUtils, UnitIndexingUtils, LastOrder
globals
//*********************************************************
//* These are the configuration constants for the system
//*
//* EFFECT_ATTACH_POINT: Where on the unit the effect attaches
//* EFFECT_PATH_WATER: What special effect to attach over water
//* EFFECT_PATH_GROUND: What special effect to attach over ground
//* DEST_RADIUS: Radius around which destructs die
//* DEST_RADIUS_SQUARED: Radius squared around which destructs die
//* ADJACENT_RADIUS: Radius for knocking back adjacent units
//* ADJACENT_FACTOR: Factor for collision speed transfers
//* 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_WATER = "MDX\\KnockbackWater.mdx"
private constant string EFFECT_PATH_GROUND = "MDX\\KnockbackDust.mdx"
private constant real DEST_RADIUS = 180.
private constant real DEST_RADIUS_SQUARED = DEST_RADIUS*DEST_RADIUS
private constant real ADJACENT_RADIUS = 180.
private constant real ADJACENT_FACTOR = 0.75
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
//* DesBoolexpr: The check used for finding destructables
//* AdjBoolexpr: The check for picking adjacent units to knockback
//* DestRect: The rect used to check for destructables
//*
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 DesBoolexpr = null
private boolexpr AdjBoolexpr = null
private rect DestRect = Rect(0,0,1,1)
//* Temporary variables used by the system
private real TempX = 0.
private real TempY = 0.
private unit TempUnit1 = null
private unit TempUnit2 = null
endglobals
//* Boolean for whether or not to display effects on a unit
private function ShowEffects takes unit u returns boolean
return not IsUnitType(u, UNIT_TYPE_FLYING)
endfunction
//* Functions for the destructable destruction
private function KillDests_Check takes nothing returns boolean
local real x = GetDestructableX(GetFilterDestructable())
local real y = GetDestructableY(GetFilterDestructable())
return (TempX-x)*(TempX-x) + (TempY-y)*(TempY-y) <= DEST_RADIUS_SQUARED
endfunction
private function KillDests takes nothing returns nothing
call KillDestructable(GetEnumDestructable())
endfunction
//* Functions for knocking back adjacent units
private function KnockAdj_Check takes nothing returns boolean
return TempUnit2 != GetFilterUnit() and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(TempUnit1)) and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL) and GetWidgetLife(GetFilterUnit()) > 0.405 and GetUnitAbilityLevel(GetFilterUnit(), 'Avul') <= 0
endfunction
//******************************************************************************
//* 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
integer FXMode = 0
boolean KillDest = false
boolean KnockAdj = false
boolean ChainAdj = false
boolean ShowEff = false
real Decrement = 0.
real Displace = 0.
real CosA = 0.
real SinA = 0.
public method checkterrain takes knocker n returns integer
local integer i = 0
local real x = GetUnitX(n.Target)
local real y = GetUnitY(n.Target)
if IsTerrainPathingType(x, y, TERRAIN_PATHING_LAND) then
set i = 1
elseif IsTerrainPathingType(x, y, TERRAIN_PATHING_SHALLOW) then
set i = 2
endif
return i
endmethod
static method create takes unit source, unit targ, real angle, real disp, real dec, boolean killDestructables, boolean knockAdjacent, boolean chainAdjacent returns knocker
local knocker n = knocker.allocate()
set n.Target = targ
set n.Source = source
set n.FXMode = n.checkterrain(n)
set n.HitGroup = NewGroup()
set n.KillDest = killDestructables
set n.KnockAdj = knockAdjacent
set n.ChainAdj = chainAdjacent
set n.ShowEff = ShowEffects(targ)
set n.Decrement = dec
set n.Displace = disp
set n.CosA = Cos(angle)
set n.SinA = Sin(angle)
if n.ShowEff then
if n.FXMode == 1 then
set n.KBEffect = AddSpecialEffectTarget(EFFECT_PATH_GROUND, n.Target, EFFECT_ATTACH_POINT)
elseif n.FXMode == 2 then
set n.KBEffect = AddSpecialEffectTarget(EFFECT_PATH_WATER, n.Target, EFFECT_ATTACH_POINT)
debug else
debug call BJDebugMsg(SCOPE_PREFIX+" Error (On Create): Unknown Terrain Type")
endif
endif
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
//* Issue last order if activated
call IssueLastOrder(this.Target)
endif
if this.ShowEff then
//* Destroy effect if it exists
call DestroyEffect(this.KBEffect)
endif
call ReleaseGroup(this.HitGroup)
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 knocker m = 0
local integer i = Counter - 1
local integer j = 0
local integer mode = 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 mode = n.FXMode
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
//* Propagate the knockback in space and time
set xf = xi + n.Displace*n.CosA
set yf = yi + n.Displace*n.SinA
call SetUnitPosition(u, xf, yf)
set n.FXMode = n.checkterrain(n)
//* Modify the special effect if necessary
if n.ShowEff then
if n.FXMode == 1 and mode == 2 then
call DestroyEffect(n.KBEffect)
set n.KBEffect = AddSpecialEffectTarget(EFFECT_PATH_GROUND, n.Target, EFFECT_ATTACH_POINT)
elseif n.FXMode == 2 and mode == 1 then
call DestroyEffect(n.KBEffect)
set n.KBEffect = AddSpecialEffectTarget(EFFECT_PATH_WATER, n.Target, EFFECT_ATTACH_POINT)
debug elseif n.FXMode == 0 then
debug call BJDebugMsg(SCOPE_PREFIX+" Error (In Update): Unknown Terrain Type")
endif
endif
//* Decrement displacement left to go
set n.Displace = n.Displace - n.Decrement
//* Destroy destructables if desired
if n.KillDest then
set TempX = GetUnitX(u)
set TempY = GetUnitY(u)
call MoveRectTo(DestRect, TempX, TempY)
call EnumDestructablesInRect(DestRect, DesBoolexpr, function KillDests)
endif
//* Knockback nearby units if desired
if n.KnockAdj then
set xi = GetUnitX(u)
set yi = GetUnitY(u)
set TempUnit1 = n.Source
set TempUnit2 = u
call GroupEnumUnitsInRange(ENUM_GROUP, xi, yi, ADJACENT_RADIUS, AdjBoolexpr)
loop
set s = FirstOfGroup(ENUM_GROUP)
exitwhen s == null
if not IsUnitInGroup(s, n.HitGroup) then
set xf = GetUnitX(s)
set yf = GetUnitY(s)
call GroupAddUnit(n.HitGroup, s)
set m = knocker.create(n.Source, s, Atan2(yf-yi, xf-xi), n.Displace*ADJACENT_FACTOR, n.Decrement, n.KillDest, n.ChainAdj, n.ChainAdj)
call GroupAddUnit(m.HitGroup, u)
set Knockers[Counter] = m
set Counter = Counter + 1
endif
call GroupRemoveUnit(ENUM_GROUP, s)
endloop
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, boolean killDestructables, boolean knockAdjacent, boolean chainAdjacent 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
else
//* Can't chain if you don't knockback adjacent units
if not knockAdjacent and chainAdjacent then
set chainAdjacent = false
endif
set n = knocker.create(source, targ, angle*bj_DEGTORAD, startspeed*TIMER_INTERVAL, decrement*TIMER_INTERVAL*TIMER_INTERVAL, killDestructables, knockAdjacent, chainAdjacent)
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
endif
return b
endfunction
private function Init takes nothing returns nothing
call SetRect(DestRect, -DEST_RADIUS, -DEST_RADIUS, DEST_RADIUS, DEST_RADIUS)
set DesBoolexpr = Condition(function KillDests_Check)
set AdjBoolexpr = Condition(function KnockAdj_Check)
endfunction
endlibrary
//TESH.scrollpos=91
//TESH.alwaysfold=0
library ListModule
//===========================================================================
// Information:
//==============
//
// This module allows you to create a linked list containing all of the allocated
// instances of a struct. Iterating through a linked list is slightly faster than the
// typical method of looping through an array containing all of the instances. However,
// getting a random struct from a list is an O(n) operation instead of O(1) as with
// SetModule. This should be a good trade as most systems don't need random access.
//
// There is no speed loss while iterating through structs compared to doing
// it yourself, since all of method calls used while iterating get inlined. The best
// use for this module is to hide a lot of ugly low-level code from your systems.
//
// How to use List:
//==================
//
// Implement the List module in your struct. Your struct will gain all of the
// following methods:
//
// (static) getLength: Returns an integer indicating the number of structs in the list.
//
// (static) getRandom: Returns a random struct from the list. Note that this is rather
// slow due to the fact that this is a linked list implementation.
//
// (static) getFirst: Returns the first struct in the list.
//
// (static) getLast: Returns the last struct in the list.
//
// getNext: Returns the next struct in the list.
//
// getPrev: Returns the previous struct in the list.
//
// inList: Returns a boolean indicating whether this struct is in the list.
//
// addList: Adds this struct to the list. Returns false if adding failed because the
// list is being destroyed or the struct was already in the list.
//
// removeList: Removes this struct from the list. Returns false if removal failed
// because the struct was not in the list.
//
// (static) destroyList: Destroys all of the structs in the list.
//
// Example of List usage:
//========================
//
// struct YourStruct
// implement List
//
// static method create takes nothing returns YourStruct
// local YourStruct ys = .allocate()
// call ys.addList()
// return ys
// endmethod
//
// method onDestroy takes nothing returns nothing
// call .removeList()
// endmethod
//
// static method GetRandomStruct takes nothing returns YourStruct
// return .getRandom() //That was easy... but slow.
// endmethod
//
// static method LoopThroughAllYourStructs takes nothing returns nothing
// local YourStruct ys = .getFirst() //This is inlined to a variable read.
// loop
// exitwhen ys == 0
// //Do something with the YourStruct instance here.
// set ys = ys.getNext() //This is inlined to an array read.
// endloop
// endmethod
// endstruct
//
//===========================================================================
module List
private static boolean destroyinglist = false
private static thistype length = 0
private static thistype first = 0
private static thistype last = 0
private thistype prev = 0
private thistype next = 0
private boolean inlist = false
static method getLength takes nothing returns integer
return .length
endmethod
static method getFirst takes nothing returns thistype
return .first
endmethod
static method getLast takes nothing returns thistype
return .last
endmethod
static method getRandom takes nothing returns thistype
local thistype s = .first
local thistype array list
local integer n = -1
loop
exitwhen s == 0
set n = n + 1
set list[n] = s
set s = s.next
endloop
if n == -1 then
return 0
endif
return list[GetRandomInt(0, n)]
endmethod
method getNext takes nothing returns thistype
return .next
endmethod
method getPrev takes nothing returns thistype
return .prev
endmethod
method inList takes nothing returns boolean
return .inlist
endmethod
method addList takes nothing returns boolean
if not .inlist and not .destroyinglist then
set .inlist = true
if .first == 0 then
set .first = this
else
set .prev = .last
set .prev.next = this
endif
set .last = this
set .length = .length + 1
return true
endif
return false
endmethod
method removeList takes nothing returns boolean
if .inlist then
if .destroyinglist then
return true
endif
set .inlist = false
if .first == this then
set .first = .next
endif
if .last == this then
set .last = .prev
endif
set .prev.next = .next
set .next.prev = .prev
set .length = .length - 1
return true
endif
return false
endmethod
static method destroyList takes nothing returns nothing
local thistype s = .first
if not .destroyinglist then
loop
exitwhen s == 0
set .destroyinglist = false
call s.removeList()
set .destroyinglist = true
call s.destroy()
set s = s.next
endloop
set .destroyinglist = false
endif
endmethod
endmodule
endlibrary
//TESH.scrollpos=146
//TESH.alwaysfold=0
//////////////////////////////////////////////////////////////////////////
// Gravity Stone //
// A spell by: //
// Element of Water //
//////////////////////////////////////////////////////////////////////////
library GravityStone initializer Init requires Knockback, ListModule, Table
/////////////////////////////////////////
// EDITABLE DATA //
// Change these to suit your needs //
/////////////////////////////////////////
globals
//how far per second units are knocked back at the end of the spell
private constant real KNOCKBACK_SPEED = 500.
//how much the knockback speed is reduced by each second
private constant real KNOCKBACK_DECREMENT = 250.
//the special effect which plays when the spell ends
private constant string SFX = "Objects\\Spawnmodels\\NightElf\\NEDeathMedium\\NEDeath.mdl"
//this is the rawcode of the dummy unit
private constant integer DUMMY_ID = 'dum0'
//this is the rawcode of the spell
private constant integer SPELL_ID = 'A000'
//the maximum number of units the spell can ever affect, regardless of the below MaxUnits function
private constant integer MAX_UNITS = 100
//the maximum distance units can be from the ball
private constant real MAX_DIST = 250.0
//how fast the units spin round the ball
private constant real ORBIT_SPEED = 150.0
//the maximum height of the units when pulled into the ball
private constant real MAX_HEIGHT = 750.
//the duration of the timer, AKA how often the places the units move to are updated
private constant real TIMER_INTERVAL = 0.1
//the interval at which units' positions are updated when being pulled into the Gravity Stone
//lower values give smoother movement, whilst higher values give better performance
private constant real PULL_INTERVAL = 0.035
//the higher this is, the more slowly units will be pulled into the gravity stone
private constant real PULL_WEIGHT = 15
//the speed the units are pulled into the gravity stone is multiplied by this, giving an effect like friction
private constant real PULL_DRAG = 0.975
endglobals
//A helper function - SpellLevel(u) returns the spell level of Gravity Stone for the unit
private function SpellLevel takes unit u returns integer
return GetUnitAbilityLevel(u, SPELL_ID)
endfunction
//How long the spell lasts
private function Duration takes unit u returns real
return 2.5 + SpellLevel(u) * 2.5
endfunction
//how much damage the spell deals
private function Damage takes unit u returns real
return 250. + SpellLevel(u) * 250.
endfunction
//how many units the gravity ball can pull in at once
private function MaxUnits takes unit u returns integer
return 5 + SpellLevel(u) * 15
endfunction
//the range at which the gravity stone pulls in enemy units
private function AoE takes unit u returns real
return 250. + SpellLevel(u) * 250.
endfunction
/////////////////////////////////////////
// END EDITABLE DATA //
/////////////////////////////////////////
globals
private constant integer FLY_HACK = 'Amrf'
private location TempLoc = Location(0., 0.)
endglobals
private struct PullUnit
implement List
private static timer tim = CreateTimer()
//Pulled unit's position vector
real xU
real yU
real zU
//Target point's (or unit's) position vector
real xT = 0.
real yT = 0.
real zT = 0.
//Offset vector
real xO = 0.
real yO = 0.
real zO = 0.
//Velocity vector
real xV = 0.
real yV = 0.
real zV = 0.
//Pulled unit
unit u
real startHeight = 0.
//Private stuff
private boolean running = false
integer data
integer index
static HandleTable tbl
static method create takes unit whichUnit returns PullUnit
local PullUnit p = 0
if .tbl[whichUnit] == 0 then
set p = PullUnit.allocate()
set p.u = whichUnit
set p.xU = GetUnitX(p.u)
set p.yU = GetUnitY(p.u)
set p.zU = GetUnitFlyHeight(p.u)
call UnitAddAbility(p.u, FLY_HACK)
call UnitRemoveAbility(p.u, FLY_HACK)
set .tbl[whichUnit] = p
endif
return p
endmethod
static method execute takes nothing returns nothing
local PullUnit this = PullUnit.getFirst()
loop
exitwhen this == 0
set this.xV = this.xV + (this.xT+this.xO-this.xU)/(PULL_WEIGHT/PULL_INTERVAL)
set this.yV = this.yV + (this.yT+this.yO-this.yU)/(PULL_WEIGHT/PULL_INTERVAL)
set this.zV = this.zV + (this.zT+this.zO-this.zU)/(PULL_WEIGHT/PULL_INTERVAL)
set this.xV = this.xV * PULL_DRAG
set this.yV = this.yV * PULL_DRAG
set this.zV = this.zV * PULL_DRAG
set this.xU = this.xU + this.xV
set this.yU = this.yU + this.yV
set this.zU = this.zU + this.zV
call MoveLocation(TempLoc, this.xU, this.yU)
call SetUnitX(this.u, this.xU)
call SetUnitY(this.u, this.yU)
call SetUnitFlyHeight(this.u, this.zU + .startHeight - GetLocationZ(TempLoc), 0.00)
set this = this.getNext()
endloop
endmethod
method start takes nothing returns nothing
if not .running then
set .running = true
if .getLength() == 0 then
call TimerStart(.tim, PULL_INTERVAL, true, function PullUnit.execute)
endif
call .addList()
call PauseUnit(.u, true)
call MoveLocation(TempLoc, .xT, .yT)
set .startHeight = GetLocationZ(TempLoc)
endif
endmethod
method stop takes nothing returns nothing
if .running then
set .running = false
call .removeList()
if .getLength() == 0 then
call PauseTimer(.tim)
endif
call PauseUnit(.u, false)
endif
endmethod
private method onDestroy takes nothing returns nothing
call .stop()
set .tbl[.u] = 0
endmethod
static method getData takes unit u returns PullUnit
return .tbl[u]
endmethod
private static method onInit takes nothing returns nothing
set .tbl = HandleTable.create()
endmethod
endstruct
globals
private timer tim = CreateTimer()
private boolexpr UnitEnumFilter = null
private player TempPlayer = null
private group TempGroup = CreateGroup()
endglobals
private function PolarProjectionX takes real startX, real dist, real angle returns real
return startX + dist * Cos(angle * bj_DEGTORAD)
endfunction
private function PolarProjectionY takes real startY, real dist, real angle returns real
return startY + dist * Sin(angle * bj_DEGTORAD)
endfunction
private function UnitEnum takes nothing returns boolean
local unit u = GetFilterUnit()
local boolean array b
set b[0] = IsUnitEnemy(u, TempPlayer)
set b[1] = not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)
set b[2] = not IsUnitType(u, UNIT_TYPE_STRUCTURE)
set b[3] = GetWidgetLife(u) > 0
set b[4] = PullUnit.getData(u) == 0
return b[0] and b[1] and b[2] and b[3] and b[4]
endfunction
private struct Data
implement List
real x
real y
real time
real maxdist
real maxspeed
real maxheight
real damage
unit dummy
unit caster
real aoe
integer maxUnits
PullUnit array puds [MAX_UNITS]
real array pud_angles[MAX_UNITS]
integer pud_index = 0
boolean ended = false
private method pullMore takes nothing returns nothing
local unit u
if .pud_index < .maxUnits then
set TempPlayer = GetOwningPlayer(.caster)
call GroupEnumUnitsInRange(TempGroup, .x, .y, .aoe, UnitEnumFilter)
loop
set u = FirstOfGroup(TempGroup)
exitwhen u == null or .pud_index == .maxUnits
set .puds[.pud_index] = PullUnit.create(u)
if .puds[.pud_index] != 0 then
set .puds[.pud_index].data = this
set .puds[.pud_index].index = .pud_index
set .puds[.pud_index].xT = .x
set .puds[.pud_index].yT = .y
call .puds[.pud_index].start()
set .pud_angles[.pud_index] = 0
set .pud_index = .pud_index + 1
endif
call GroupRemoveUnit(TempGroup, u)
endloop
call GroupClear(TempGroup)
endif
set u = null
endmethod
private static method execute takes nothing returns nothing
local integer i = 0
local Data this = .getFirst()
loop
exitwhen this == 0
call this.pullMore()
set i = 0
loop
exitwhen i == this.pud_index
set this.pud_angles[i] = this.pud_angles[i]+GetRandomReal(-this.maxspeed/2, this.maxspeed)
if this.pud_angles[i] >= 360 then
set this.pud_angles[i] = this.pud_angles[i]-360
elseif this.pud_angles[i] <= 360 then
set this.pud_angles[i] = this.pud_angles[i]+360
endif
set this.puds[i].xO = PolarProjectionX(0, GetRandomReal(0, this.maxdist), this.pud_angles[i])
set this.puds[i].yO = PolarProjectionY(0, GetRandomReal(0, this.maxdist), this.pud_angles[i])
set this.puds[i].zO = GetRandomReal(0.0, this.maxheight)
set i = i + 1
endloop
set this.time = this.time - TIMER_INTERVAL
if this.time <= 0 then
call this.destroy()
endif
set this = this.getNext()
endloop
endmethod
static method create takes unit caster, real duration, real x, real y, real damage, integer maxUnits, real range, real dist, real orbitspeed, real height returns Data
local Data this = Data.allocate()
set this.x = x
set this.y = y
set this.time = duration
set this.damage = damage
set this.aoe = range
set this.dummy = CreateUnit(Player(12), DUMMY_ID, x, y, 0.00)
set this.caster = caster
set this.maxUnits = IMinBJ(maxUnits, MAX_UNITS)
call this.pullMore()
set this.maxdist = dist
set this.maxspeed = orbitspeed
set this.maxheight = height
if .getLength() == 0 then
call TimerStart(tim, TIMER_INTERVAL, true, function Data.execute)
endif
call this.addList()
return this
endmethod
method releasePud takes PullUnit p returns nothing
if not .ended then
set .pud_index = .pud_index - 1
set .puds[p.index] = .puds[.pud_index]
set .puds[p.index].index = p.index
call SetUnitFlyHeight(p.u, GetUnitDefaultFlyHeight(p.u), KNOCKBACK_SPEED * KNOCKBACK_SPEED / KNOCKBACK_DECREMENT)
call p.destroy()
endif
endmethod
method onDestroy takes nothing returns nothing
set .ended = true
call .removeList()
if this.getLength() == 0 then
call PauseTimer(tim)
endif
if .pud_index > 0 then
loop
set .pud_index = .pud_index - 1
call KnockbackTarget(this.caster, .puds[.pud_index].u, .pud_angles[.pud_index], KNOCKBACK_SPEED, KNOCKBACK_DECREMENT, true, false, false)
call UnitDamageTarget(.caster, .puds[.pud_index].u, .damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
call SetUnitFlyHeight(.puds[.pud_index].u, GetUnitDefaultFlyHeight(.puds[.pud_index].u), KNOCKBACK_SPEED * KNOCKBACK_SPEED / KNOCKBACK_DECREMENT)
call .puds[.pud_index].destroy()
exitwhen .pud_index == 0
endloop
endif
call DestroyEffect(AddSpecialEffect(SFX, GetUnitX(.dummy), GetUnitY(.dummy)))
call KillUnit(.dummy)
set this.dummy = null
set this.caster = null
endmethod
endstruct
private function SpellActions takes nothing returns boolean
local unit u
local real x
local real y
local real dur
local real rad
local real dmg
local integer units
if GetSpellAbilityId() == SPELL_ID then
set u = GetTriggerUnit()
set x = GetSpellTargetX()
set y = GetSpellTargetY()
set dur = Duration(u)
set rad = AoE(u)
set dmg = Damage(u)
set units = MaxUnits(u)
call Data.create(GetTriggerUnit(), dur, x, y, dmg, units, rad, MAX_DIST, ORBIT_SPEED*TIMER_INTERVAL, MAX_HEIGHT)
endif
return false
endfunction
private function DeathActions takes nothing returns boolean
local PullUnit p = PullUnit.getData(GetTriggerUnit())
if p != 0 then
call Data(p.data).releasePud(p)
endif
return false
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 SpellActions))
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(t, Condition(function DeathActions))
call Preload(SFX)
set UnitEnumFilter = Condition(function UnitEnum)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
'CREDITS:'
RightField
//RuneGem model
Grim_001
//ListModule code
Rising_Dusk
//Knockback code + all requirements
Vexorian
//Table code
Element of Water
//System code
//TESH.scrollpos=18
//TESH.alwaysfold=0
'HOW TO IMPORT:'
follow the following instructions:
'#1 - The model'
//This step isn't necessary, but it makes the spell look better.
//You will have to use another model if you don't do this step.
1. Go into the import manager, find "ForceGem.blp" and click export. Save it to somewhere
sensible, such as your desktop.
2. Do the same for "ForceGem.mdx".
3. In your map, go to the import manager and import "ForceGem.blp". Double click on the
imported model and check "Use custom path". In the text box that becomes available, type
(or CnP :D) "Objects\InventoryItems\ForceGem\ForceGem.blp". Press OK.
4. Do the same for "ForceGem.mdx", but change the path to
"Objects\InventoryItems\ForceGem\ForceGem.mdx" instead.
Done!
'#2 - The ability'
//This is the spell's ability. You can choose to make your own ability rather than doing
//this if you want. Follow only 3-4 if you choose to make your own ability.
1. Go to the object editor, click the "Abilities" tab and find "Gravity Ball".
Right-click and copy it.
2. Go into the object editor in your map and paste it in.
3. Do the same for ability "Collision Ability".
4. Press ctrl>D. This should show the raw data. Look at the first four characters in the name
of the new spells. Write them down somewhere as we will need them later.
5. Give the first spell to your hero.
'#3 - The dummy unit'
//This is the dummy unit used for the ball in the spell. You can make your own, but it
//is recommended to follow these instructions...
1. Go to the object editor, make sure you are on the "Units" tab and find "Dummy".
Right-click and copy it.
2. Go into the object editor in your map and paste it.
3. Press ctrl>D. This should show the raw data. Look at the first four characters in the name
of the new unit. Write them down somewhere as we will need them later.
'#4 - The code'
//You must do 1-2 in this step or the spell will not work. You should complete 3.
//if you are going to make your map public.
1. Go into the trigger editor and find the folder "GravityStone". Right-click and copy it.
2. Go into the trigger editor in your map and paste the folder.
3. Copy the trigger "Credits" in this folder and paste it into the "GravityStone" folder in
your map.
'#5 - Configuration'
//The final step. This helps you to use your own values in the spell.
1. Go to the trigger "TerrainPathability". Change "DUMMY_WINDWALK_ID" to the second spell rawcode
2. Go to the trigger "GravityStone" in the folder "GravityStone" and scroll down to the block
"EDITABLE DATA".
3. Edit the values and functions you see there. They are all commented to tell you what they do.
'DO NOT' change 'ANYTHING' else you see in that trigger as it may mess up the spell.
Congratulations! You have successfully imported Gravity Stone into your map!