Name | Type | is_array | initial_value |
Allone | boolean | No | false |
ArcherDefender1 | unit | No | |
ArcherDefender1Location | location | No | |
ArcherDefender2 | unit | No | |
ArcherDefender2Location | location | No | |
ArcherKillCount | integer | No | |
ArcherKillNeeded | integer | No | 10 |
Arthas | unit | No | |
Athan | unit | No | |
Athan_Destination | location | No | |
AthanSelectionEvent | integer | No | |
Blink_Caster | unit | No | |
Blink_Point | location | Yes | |
BlinkDamage | integer | No | |
CameraDistanceDefault | real | No | 1650.00 |
CameraFOVDefault | real | No | 70.00 |
CameraHeightDefault | real | No | 16.00 |
Camlion | unit | No | |
Camlion_Destination | location | No | |
Cinematic1WalkDemon | location | No | |
Cinematic1WalkPaladin | location | No | |
CinematicIsOn | boolean | No | true |
CinematicIsOnQuest1 | boolean | No | true |
CinematicIsOnQuest3 | boolean | No | true |
Clydow | unit | No | |
CreepGraveyardGroup1 | group | No | |
CreepGraveyardGroup1ZombiA | rect | No | |
CreepGraveyardGroup1ZombiC | rect | No | |
CreepGraveyardGroup2 | group | No | |
CreepGraveyardGroup3 | group | No | |
CreepGraveyardGroup4 | group | No | |
CreepSkullCamp | group | No | |
CreepUnit1 | unit | No | |
CreepUnit2 | unit | No | |
CreepUnit3 | unit | No | |
CreepUnit4 | unit | No | |
CreepUnit5 | unit | No | |
CreepUnit6 | unit | No | |
CreepUnit7 | unit | No | |
CreepUnitGroup | group | No | |
CritterDropRate | integer | No | |
Defenders | group | No | |
DefendersGroup | group | No | |
DefendersMessageWait | boolean | No | true |
DefendersNextLocation | location | No | |
DefendersStartLocation | location | No | |
DialogBool | boolean | No | |
F1_Player02_User | player | No | |
F1_Player13_Andhoral | player | No | |
FelRogue | unit | No | UnitNull |
FelRogue_NoGlow | unit | No | |
GameOver | boolean | No | |
GhostVisibility | fogmodifier | No | |
GlyphUpgradeMelee | integer | No | 2 |
GlyphUpgradeRanged | integer | No | 2 |
Guard1 | unit | No | |
Guard_Destination | location | No | |
GuardsIdle | boolean | No | |
Hawkstrider | unit | No | |
Hawkstrider_Select | integer | No | |
HawkstriderKnight | unit | No | |
HawkstriderKnight_Destination | location | No | |
HorsemanDefender1 | unit | No | |
HorsemanDefender1Location | location | No | |
HorsemanDefender2 | unit | No | |
HorsemanDefender2Location | location | No | |
Item_Penguin | item | No | |
ItemPosition | location | No | |
Lep | unit | No | UnitNull |
Lep_Attack_Destination | location | No | |
Lep_Destination | location | No | |
Lep_IfAttack | boolean | No | |
Lep_Timer | timer | No | |
Lep_Timer_Window | timerdialog | No | |
Luzran | boolean | No | true |
LuzranDeathLocation | location | No | |
LuzranRandomItem | integer | No | |
MageKillCount | integer | No | |
MainQuest | quest | No | |
MainQuestReq1 | questitem | No | |
MainQuestReq2 | questitem | No | |
MainQuestReq3 | questitem | No | |
MainQuestReq4 | questitem | No | |
MainQuestReq5 | questitem | No | |
MQuest_Burn | quest | No | |
newline | string | No | |
Nojal | unit | No | |
Nojal_Destination | location | No | |
NojalSpecialEffect | effect | No | |
NojalSpecialEffect_Marker | effect | No | |
NojalSpecialEffect_Teleport | effect | No | |
Paladin | unit | No | |
Paladin_NoGlow | unit | No | |
Parmae | unit | No | |
Parmae_Destination | location | No | |
Player01_User | player | No | |
Player02_User | player | No | Player01 |
Player03_User | player | No | Player02 |
Player2 | player | No | Player01 |
Player3 | player | No | Player02 |
PlayerKills | integer | Yes | |
PlayerUser | player | No | |
PriestDefender1 | unit | No | |
PriestDefender1Location | location | No | |
PriestDefender2 | unit | No | |
PriestDefender2Location | location | No | |
QueenOfSufferingDest | location | No | |
Quest1 | quest | No | |
Quest1_Complete | boolean | No | |
Quest1_Start | boolean | No | |
Quest1Requirement1 | questitem | No | |
Quest1Requirement2 | questitem | No | |
Quest1Requirement3 | questitem | No | |
Quest1SpecialEffect1 | effect | No | |
Quest2 | quest | No | |
Quest2Requirement1 | questitem | No | |
Quest2SpecialEffect2 | effect | No | |
Quest3 | quest | No | |
Quest3CorruptedDruid | unit | No | |
Quest3Golem | unit | No | |
Quest3HeroMove | rect | No | |
Quest3Item | item | No | |
Quest3MainGroupP1 | group | No | |
Quest3MainGroupP2 | group | No | |
Quest3Requirement1 | questitem | No | |
Quest3Requirement2 | questitem | No | |
Quest3SatyrGroup | group | No | |
Quest3SpecialEffect3 | effect | No | |
Quest3Treant | location | No | |
Quest3UnitsMove | rect | No | |
Quest4 | quest | No | |
Quest4BlizzardAttack | integer | No | |
Quest4BlizzardDummy | unit | No | |
Quest4BlizzardMarker | effect | No | |
Quest4BlizzardPoint | location | No | |
Quest4GroupMove | location | No | |
Quest4GroupSummon | location | No | |
Quest4NojalGroup | group | No | |
Quest4NojalGroupNumber | integer | No | 12 |
Quest4RandomPlayer | integer | No | |
Quest4Requirement1 | questitem | No | |
Quest4Skelet | location | No | |
Quest4SpecialEffect4 | effect | No | |
Quest4Visibility | fogmodifier | No | |
Quest5 | quest | No | |
Quest5_Complete | boolean | No | |
Quest5_Start | boolean | No | |
Quest5Req1Count | integer | No | |
Quest5Req2Count | integer | No | |
Quest5Requirement1 | questitem | No | |
Quest5Requirement2 | questitem | No | |
Quest5SpecialEffect5 | effect | No | |
QuestMultiboard | multiboard | No | |
QuestMultiboard_PlayerColor | string | Yes | |
QuestMultiboard_PlayerName | string | Yes | |
QuestMultiboard_Quest1Archer | integer | No | |
QuestMultiboard_Quest1Mage | integer | No | |
QuestMultiboard_Quest5Ghost | integer | No | |
QuestMultiboard_Quest5Wraith | integer | No | |
QuestMultiboard_RowNumber | integer | No | |
QuestMultiboard_Total | integer | No | |
RandomUnit | integer | No | |
RandomVoice | integer | No | |
RunestoneLocation | location | No | |
Saeen | unit | No | |
SatanLocation | unit | No | |
SatyrKillCount | integer | No | |
SatyrLocation | location | No | |
Shalis | unit | No | |
SkeletonDropRate | integer | No | |
SpritsA1 | boolean | No | |
SpritsA2 | boolean | No | |
SummonerAttack | location | No | |
SummonerGroup | group | No | |
SummonerStep | integer | No | |
TempLoc00 | location | No | |
Tichondrius | unit | No | |
TichondriusSelectionEvents | integer | No | |
UndeadAttackChange | integer | No | |
UndeadInTownCount | integer | No | |
UndeadSpawnPoint | location | No | |
UndeadSpawnUnits | group | No | |
UndeadSpawnUnitsInGroup | integer | No | |
UndeadTownLeaderboard | leaderboard | No | |
VillagerReturn | location | No |
do; local _, codeLoc = pcall(error, "", 2) --get line number where DebugUtils begins.
--[[
-------------------------
-- | Debug Utils 2.2 | --
-------------------------
--> https://www.hiveworkshop.com/threads/lua-debug-utils-incl-ingame-console.353720/
- by Eikonium, with special thanks to:
- @Bribe, for pretty table print, showing that xpcall's message handler executes before the stack unwinds and useful suggestions like name caching and stack trace improvements.
- @Jampion, for useful suggestions like print caching and applying Debug.try to all code entry points
- @Luashine, for useful feedback and building "WC3 Debug Console Paste Helper" (https://github.com/Luashine/wc3-debug-console-paste-helper#readme)
- @HerlySQR, for showing a way to get a stack trace in Wc3 (https://www.hiveworkshop.com/threads/lua-getstacktrace.340841/)
- @Macadamia, for showing a way to print warnings upon accessing nil globals, where this all started with (https://www.hiveworkshop.com/threads/lua-very-simply-trick-to-help-lua-users-track-syntax-errors.326266/)
-----------------------------------------------------------------------------------------------------------------------------
| Provides debugging utility for Wc3-maps using Lua. |
| |
| Including: |
| 1. Automatic ingame error messages upon running erroneous code from triggers or timers. |
| 2. Ingame Console that allows you to execute code via Wc3 ingame chat. |
| 3. Automatic warnings upon reading nil globals (which also triggers after misspelling globals) |
| 4. Debug-Library functions for manual error handling. |
| 5. Caching of loading screen print messages until game start (which simplifies error handling during loading screen) |
| 6. Overwritten tostring/print-functions to show the actual string-name of an object instead of the memory position. |
| 7. Conversion of war3map.lua-error messages to local file error messages. |
| 8. Other useful debug utility (table.print and Debug.wc3Type) |
-----------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Installation: |
| |
| 1. Copy the code (DebugUtils.lua, StringWidth.lua and IngameConsole.lua) into your map. Use script files (Ctrl+U) in your trigger editor, not text-based triggers! |
| 2. Order the files: DebugUtils above StringWidth above IngameConsole. Make sure they are above ALL other scripts (crucial for local line number feature). |
| 3. Adjust the settings in the settings-section further below to receive the debug environment that fits your needs. |
| |
| Deinstallation: |
| |
| - Debug Utils is meant to provide debugging utility and as such, shall be removed or invalidated from the map closely before release. |
| - Optimally delete the whole Debug library. If that isn't suitable (because you have used library functions at too many places), you can instead replace Debug Utils |
| by the following line of code that will invalidate all Debug functionality (without breaking your code): |
| Debug = setmetatable({try = function(...) return select(2,pcall(...)) end}, {__index = function(t,k) return DoNothing end}); try = Debug.try |
| - If that is also not suitable for you (because your systems rely on the Debug functionality to some degree), at least set ALLOW_INGAME_CODE_EXECUTION to false. |
| - Be sure to test your map thoroughly after removing Debug Utils. |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
* Documentation and API-Functions:
*
* - All automatic functionality provided by Debug Utils can be deactivated using the settings directly below the documentation.
*
* -------------------------
* | Ingame Code Execution |
* -------------------------
* - Debug Utils provides the ability to run code via chat command from within Wc3, if you have conducted step 3 from the installation section.
* - You can either open the ingame console by typing "-console" into the chat, or directly execute code by typing "-exec <code>".
* - See IngameConsole script for further documentation.
*
* ------------------
* | Error Handling |
* ------------------
* - Debug Utils automatically applies error handling (i.e. Debug.try) to code executed by your triggers and timers (error handling means that error messages are printed on screen, if anything doesn't run properly).
* - You can still use the below library functions for manual debugging.
*
* Debug.try(funcToExecute, ...) -> ...
* - Help function for debugging another function (funcToExecute) that prints error messages on screen, if funcToExecute fails to execute.
* - DebugUtils will automatically apply this to all code run by your triggers and timers, so you rarely need to apply this manually (maybe on code run in the Lua root).
* - Calls funcToExecute with the specified parameters (...) in protected mode (which means that following code will continue to run even if funcToExecute fails to execute).
* - If the call is successful, returns the specified function's original return values (so p1 = Debug.try(Player, 0) will work fine).
* - If the call is unsuccessful, prints an error message on screen (including stack trace and parameters you have potentially logged before the error occured)
* - By default, the error message consists of a line-reference to war3map.lua (which you can look into by forcing a syntax error in WE or by exporting it from your map via File -> Export Script).
* You can get more helpful references to local script files instead, see section about "Local script references".
* - Example: Assume you have a code line like "func(param1,param2)", which doesn't work and you want to know why.
* Option 1: Change it to "Debug.try(func, param1, param2)", i.e. separate the function from the parameters.
* Option 2: Change it to "Debug.try(function() return func(param1, param2) end)", i.e. pack it into an anonymous function (optionally skip the return statement).
* Debug.log(...)
* - Logs the specified parameters to the Debug-log. The Debug-log will be printed upon the next error being catched by Debug.try, Debug.assert or Debug.throwError.
* - The Debug-log will only hold one set of parameters per code-location. That means, if you call Debug.log() inside any function, only the params saved within the latest call of that function will be kept.
* Debug.throwError(...)
* - Prints an error message including document, line number, stack trace, previously logged parameters and all specified parameters on screen. Parameters can have any type.
* - In contrast to Lua's native error function, this can be called outside of protected mode and doesn't halt code execution.
* Debug.assert(condition:boolean, errorMsg:string, ...) -> ...
* - Prints the specified error message including document, line number, stack trace and previously logged parameters on screen, IF the specified condition fails (i.e. resolves to false/nil).
* - Returns ..., IF the specified condition holds.
* - This works exactly like Lua's native assert, except that it also works outside of protected mode and does not halt code execution.
* Debug.traceback() -> string
* - Returns the stack trace at the position where this is called. You need to manually print it.
* Debug.getLine([depth: integer]) -> integer?
* - Returns the line in war3map.lua, where this function is executed.
* - You can specify a depth d >= 1 to instead return the line, where the d-th function in the stack trace was called. I.e. depth = 2 will return the line of execution of the function that calls Debug.getLine.
* - Due to Wc3's limited stack trace ability, this might sometimes return nil for depth >= 3, so better apply nil-checks on the result.
* Debug.getLocalErrorMsg(errorMsg:string) -> string
* - Takes an error message containing a file and a linenumber and converts war3map.lua-lines to local document lines as defined by uses of Debug.beginFile() and Debug.endFile().
* - Error Msg must be formatted like "<document>:<linenumber><Rest>".
*
* ----------------------------
* | Warnings for nil-globals |
* ----------------------------
* - DebugUtils will print warnings on screen, if you read any global variable in your code that contains nil.
* - This feature is meant to spot any case where you forgot to initialize a variable with a value or misspelled a variable or function name, such as calling CraeteUnit instead of CreateUnit.
* - By default, warnings are disabled for globals that have been initialized with any value (including nil). I.e. you can disable nil-warnings by explicitly setting MyGlobalVariable = nil. This behaviour can be changed in the settings.
*
* Debug.disableNilWarningsFor(variableName:string)
* - Manually disables nil-warnings for the specified global variable.
* - Variable must be inputted as string, e.g. Debug.disableNilWarningsFor("MyGlobalVariable").
*
* -----------------
* | Print Caching |
* -----------------
* - DebugUtils caches print()-calls occuring during loading screen and delays them to after game start.
* - This also applies to loading screen error messages, so you can wrap erroneous parts of your Lua root in Debug.try-blocks and see the message after game start.
*
* -------------------------
* | Local File Stacktrace |
* -------------------------
* - By default, error messages and stack traces printed by the error handling functionality of Debug Utils contain references to war3map.lua (a big file just appending all your local scripts).
* - The Debug-library provides the two functions below to index your local scripts, activating local file names and line numbers (matching those in your IDE) instead of the war3map.lua ones.
* - This allows you to inspect errors within your IDE (VSCode) instead of the World Editor.
*
* Debug.beginFile(fileName: string [, depth: integer])
* - Tells the Debug library that the specified file begins exactly here (i.e. in the line, where this is called).
* - Using this improves stack traces of error messages. "war3map.lua"-references between <here> and the next Debug.endFile() will be converted to file-specific references.
* - All war3map.lua-lines located between the call of Debug.beginFile(fileName) and the next call of Debug.beginFile OR Debug.endFile are treated to be part of "fileName".
* - !!! To be called in the Lua root in Line 1 of every document you wish to track. Line 1 means exactly line 1, before any comment! This way, the line shown in the trace will exactly match your IDE.
* - Depth can be ignored, except if you want to use a custom wrapper around Debug.beginFile(), in which case you need to set the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.beginFile().
* Debug.endFile([depth: integer])
* - Ends the current file that was previously begun by using Debug.beginFile(). War3map.lua-lines after this will not be converted until the next instance of Debug.beginFile().
* - The next call of Debug.beginFile() will also end the previous one, so using Debug.endFile() is optional. Mainly recommended to use, if you prefer to have war3map.lua-references in a certain part of your script (such as within GUI triggers).
* - Depth can be ignored, except if you want to use a custom wrapper around Debug.endFile(), you need to increase the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.endFile().
*
* ----------------
* | Name Caching |
* ----------------
* - DebugUtils overwrites the tostring-function so that it prints the name of a non-primitive object (if available) instead of its memory position. The same applies to print().
* - For instance, print(CreateUnit) will show "function: CreateUnit" on screen instead of "function: 0063A698".
* - The table holding all those names is referred to as "Name Cache".
* - All names of objects in global scope will automatically be added to the Name Cache both within Lua root and again at game start (to get names for overwritten natives and your own objects).
* - New names entering global scope will also automatically be added, even after game start. The same applies to subtables of _G up to a depth of Debug.settings.NAME_CACHE_DEPTH.
* - Objects within subtables will be named after their parent tables and keys. For instance, the name of the function within T = {{bla = function() end}} is "T[1].bla".
* - The automatic adding doesn't work for objects saved into existing variables/keys after game start (because it's based on __newindex metamethod which simply doesn't trigger)
* - You can manually add names to the name cache by using the following API-functions:
*
* Debug.registerName(whichObject:any, name:string)
* - Adds the specified object under the specified name to the name cache, letting tostring and print output "<type>: <name>" going foward.
* - The object must be non-primitive, i.e. this won't work on strings, numbers and booleans.
* - This will overwrite existing names for the specified object with the specified name.
* Debug.registerNamesFrom(parentTable:table [, parentTableName:string] [, depth])
* - Adds names for all values from within the specified parentTable to the name cache.
* - Names for entries will be like "<parentTableName>.<key>" or "<parentTableName>[<key>]" (depending on the key type), using the existing name of the parentTable from the name cache.
* - You can optionally specify a parentTableName to use that for the entry naming instead of the existing name. Doing so will also register that name for the parentTable, if it doesn't already has one.
* - Specifying the empty string as parentTableName will suppress it in the naming and just register all values as "<key>". Note that only string keys will be considered this way.
* - In contrast to Debug.registerName(), this function will NOT overwrite existing names, but just add names for new objects.
* Debug.oldTostring(object:any) -> string
* - The old tostring-function in case you still need outputs like "function: 0063A698".
*
* -----------------
* | Other Utility |
* -----------------
*
* Debug.wc3Type(object:any) -> string
* - Returns the Warcraft3-type of the input object. E.g. Debug.wc3Type(Player(0)) will return "player".
* - Returns type(object), if used on Lua-objects.
* table.tostring(whichTable [, depth:integer] [, pretty_yn:boolean])
* - Creates a list of all (key,value)-pairs from the specified table. Also lists subtable entries up to the specified depth (unlimited, if not specified).
* - E.g. for T = {"a", 5, {7}}, table.tostring(T) would output '{(1, "a"), (2, 5), (3, {(1, 7)})}' (if using concise style, i.e. pretty_yn being nil or false).
* - Not specifying a depth can potentially lead to a stack overflow for self-referential tables (e.g X = {}; X[1] = X). Choose a sensible depth to prevent this (in doubt start with 1 and test upwards).
* - Supports pretty style by setting pretty_yn to true. Pretty style is linebreak-separated, uses indentations and has other visual improvements. Use it on small tables only, because Wc3 can't show that many linebreaks at once.
* - All of the following is valid syntax: table.tostring(T), table.tostring(T, depth), table.tostring(T, pretty_yn) or table.tostring(T, depth, pretty_yn).
* - table.tostring is not multiplayer-synced.
* table.print(whichTable [, depth:integer] [, pretty_yn:boolean])
* - Prints table.tostring(...).
*
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------]]
-- disable sumneko extension warnings for imported resource
---@diagnostic disable
----------------
--| Settings |--
----------------
Debug = {
--BEGIN OF SETTINGS--
settings = {
--Ingame Error Messages
SHOW_TRACE_ON_ERROR = true ---Set to true to show a stack trace on every error in addition to the regular message (msg sources: automatic error handling, Debug.try, Debug.throwError, ...)
, INCLUDE_DEBUGUTILS_INTO_TRACE = true ---Set to true to include lines from Debug Utils into the stack trace. Those show the source of error handling, which you might consider redundant.
, USE_TRY_ON_TRIGGERADDACTION = true ---Set to true for automatic error handling on TriggerAddAction (applies Debug.try on every trigger action).
, USE_TRY_ON_CONDITION = true ---Set to true for automatic error handling on boolexpressions created via Condition() or Filter() (essentially applies Debug.try on every trigger condition).
, USE_TRY_ON_TIMERSTART = true ---Set to true for automatic error handling on TimerStart (applies Debug.try on every timer callback).
, USE_TRY_ON_ENUMFUNCS = true ---Set to true for automatic error handling on ForGroup, ForForce, EnumItemsInRect and EnumDestructablesInRect (applies Debug.try on every enum callback)
, USE_TRY_ON_COROUTINES = true ---Set to true for improved stack traces on errors within coroutines (applies Debug.try on coroutine.create and coroutine.wrap). This lets stack traces point to the erroneous function executed within the coroutine (instead of the function creating the coroutine).
--Ingame Console and -exec
, ALLOW_INGAME_CODE_EXECUTION = true ---Set to true to enable IngameConsole and -exec command.
--Warnings for nil globals
, WARNING_FOR_NIL_GLOBALS = nil ---Set to true to print warnings upon accessing nil-globals (i.e. globals containing no value).
, SHOW_TRACE_FOR_NIL_WARNINGS = false ---Set to true to include a stack trace into nil-warnings.
, EXCLUDE_BJ_GLOBALS_FROM_NIL_WARNINGS = false ---Set to true to exclude bj_ variables from nil-warnings.
, EXCLUDE_INITIALIZED_GLOBALS_FROM_NIL_WARNINGS = true ---Set to true to disable warnings for initialized globals, (i.e. nil globals that held a value at some point will be treated intentionally nilled and no longer prompt warnings).
--Print Caching
, USE_PRINT_CACHE = true ---Set to true to let print()-calls during loading screen be cached until the game starts.
, PRINT_DURATION = nil ---Adjust the duration in seconds that values printed by print() last on screen. Set to nil to use default duration (which depends on string length).
--Name Caching
, USE_NAME_CACHE = true ---Set to true to let tostring/print output the string-name of an object instead of its memory location (except for booleans/numbers/strings). E.g. print(CreateUnit) will output "function: CreateUnit" instead of "function: 0063A698".
, AUTO_REGISTER_NEW_NAMES = true ---Automatically adds new names from global scope (and subtables of _G up to NAME_CACHE_DEPTH) to the name cache by adding metatables with the __newindex metamethod to ALL tables accessible from global scope.
, NAME_CACHE_DEPTH = 4 ---Set to 0 to only affect globals. Experimental feature: Set to an integer > 0 to also cache names for subtables of _G (up to the specified depth). Warning: This will alter the __newindex metamethod of subtables of _G (but not break existing functionality).
}
--END OF SETTINGS--
--START OF CODE--
, data = {
nameCache = {} ---@type table<any,string> contains the string names of any object in global scope (random for objects that have multiple names)
, nameCacheMirror = {} ---@type table<string,any> contains the (name,object)-pairs of all objects in the name cache. Used to prevent name duplicates that might otherwise occur upon reassigning globals.
, nameDepths = {} ---@type table<any,integer> contains the depth of the name used by by any object in the name cache (i.e. the depth within the parentTable).
, autoIndexedTables = {} ---@type table<table,boolean> contains (t,true), if DebugUtils already set a __newindex metamethod for name caching in t. Prevents double application.
, paramLog = {} ---@type table<string,string> saves logged information per code location. to be filled by Debug.log(), to be printed by Debug.try()
, sourceMap = {{firstLine= 1,file='DebugUtils'}} ---@type table<integer,{firstLine:integer,file:string,lastLine?:integer}> saves lines and file names of all documents registered via Debug.beginFile().
, printCache = {n=0} ---@type string[] contains the strings that were attempted to print during loading screen.
, globalWarningExclusions = {} ---@type table<string,boolean> contains global variable names that should be excluded from warnings.
}
}
--localization
local settings, paramLog, nameCache, nameDepths, autoIndexedTables, nameCacheMirror, sourceMap, printCache = Debug.settings, Debug.data.paramLog, Debug.data.nameCache, Debug.data.nameDepths, Debug.data.autoIndexedTables, Debug.data.nameCacheMirror, Debug.data.sourceMap, Debug.data.printCache
--Write DebugUtils first line number to sourceMap:
---@diagnostic disable-next-line
Debug.data.sourceMap[1].firstLine = tonumber(codeLoc:match(":\x25d+"):sub(2,-1))
-------------------------------------------------
--| File Indexing for local Error Msg Support |--
-------------------------------------------------
-- Functions for war3map.lua -> local file conversion for error messages.
---Returns the line number in war3map.lua, where this is called (for depth = 0).
---Choose a depth d > 0 to instead return the line, where the d-th function in the stack leading to this call is executed.
---@param depth? integer default: 0.
---@return number?
function Debug.getLine(depth)
depth = depth or 0
local _, location = pcall(error, "", depth + 3) ---@diagnostic disable-next-line
local line = location:match(":\x25d+") --extracts ":1000" from "war3map.lua:1000:..."
return tonumber(line and line:sub(2,-1)) --check if line is nil before applying string.sub to prevent errors (nil can result from string.match above, although it should never do so in our case)
end
---Tells the Debug library that the specified file begins exactly here (i.e. in the line, where this is called).
---
---Using this improves stack traces of error messages. Stack trace will have "war3map.lua"-references between this and the next Debug.endFile() converted to file-specific references.
---
---To be called in the Lua root in Line 1 of every file you wish to track! Line 1 means exactly line 1, before any comment! This way, the line shown in the trace will exactly match your IDE.
---
---If you want to use a custom wrapper around Debug.beginFile(), you need to increase the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.beginFile().
---@param fileName string
---@param depth? integer default: 0. Set to 1, if you call this from a wrapper (and use the wrapper in line 1 of every document).
---@param lastLine? integer Ignore this. For compatibility with Total Initialization.
function Debug.beginFile(fileName, depth, lastLine)
depth, fileName = depth or 0, fileName or '' --filename is not actually optional, we just default to '' to prevent crashes.
local line = Debug.getLine(depth + 1)
if line then --for safety reasons. we don't want to add a non-existing line to the sourceMap
table.insert(sourceMap, {firstLine = line, file = fileName, lastLine = lastLine}) --automatically sorted list, because calls of Debug.beginFile happen logically in the order of the map script.
end
end
---Tells the Debug library that the file previously started with Debug.beginFile() ends here.
---This is in theory optional to use, as the next call of Debug.beginFile will also end the previous. Still good practice to always use this in the last line of every file.
---If you want to use a custom wrapper around Debug.endFile(), you need to increase the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.endFile().
---@param depth? integer
function Debug.endFile(depth)
depth = depth or 0
local line = Debug.getLine(depth + 1)
sourceMap[#sourceMap].lastLine = line
end
---Takes an error message containing a file and a linenumber and converts both to local file and line as saved to Debug.sourceMap.
---@param errorMsg string must be formatted like "<document>:<linenumber><RestOfMsg>".
---@return string convertedMsg a string of the form "<localDocument>:<localLinenumber><RestOfMsg>"
function Debug.getLocalErrorMsg(errorMsg)
local startPos, endPos = errorMsg:find(":\x25d*") --start and end position of line number. The part before that is the document, part after the error msg.
if startPos and endPos then --can be nil, if input string was not of the desired form "<document>:<linenumber><RestOfMsg>".
local document, line, rest = errorMsg:sub(1, startPos), tonumber(errorMsg:sub(startPos+1, endPos)), errorMsg:sub(endPos+1, -1) --get error line in war3map.lua
if document == 'war3map.lua:' and line then --only convert war3map.lua-references to local position. Other files such as Blizzard.j.lua are not converted (obiously).
for i = #sourceMap, 1, -1 do --find local file containing the war3map.lua error line.
if line >= sourceMap[i].firstLine then --war3map.lua line is part of sourceMap[i].file
if not sourceMap[i].lastLine or line <= sourceMap[i].lastLine then --if lastLine is given, we must also check for it
return sourceMap[i].file .. ":" .. (line - sourceMap[i].firstLine + 1) .. rest
else --if line is larger than firstLine and lastLine of sourceMap[i], it is not part of a tracked file -> return global war3map.lua position.
break --prevent return within next step of the loop ("line >= sourceMap[i].firstLine" would be true again, but wrong file)
end
end
end
end
end
return errorMsg
end
local convertToLocalErrorMsg = Debug.getLocalErrorMsg
----------------------
--| Error Handling |--
----------------------
local concat
---Applies tostring() on all input params and concatenates them 4-space-separated.
---@param firstParam any
---@param ... any
---@return string
concat = function(firstParam, ...)
if select('#', ...) == 0 then
return tostring(firstParam)
end
return tostring(firstParam) .. ' ' .. concat(...)
end
---Returns the stack trace between the specified startDepth and endDepth.
---The trace lists file names and line numbers. File name is only listed, if it has changed from the previous traced line.
---The previous file can also be specified as an input parameter to suppress the first file name in case it's identical.
---@param startDepth integer
---@param endDepth integer
---@return string trace
local function getStackTrace(startDepth, endDepth)
local trace, separator = "", ""
local _, currentFile, lastFile, tracePiece, lastTracePiece
for loopDepth = startDepth, endDepth do --get trace on different depth level
_, tracePiece = pcall(error, "", loopDepth) ---@type boolean, string
tracePiece = convertToLocalErrorMsg(tracePiece)
if #tracePiece > 0 and lastTracePiece ~= tracePiece then --some trace pieces can be empty, but there can still be valid ones beyond that
currentFile = tracePiece:match("^.-:")
--Hide DebugUtils in the stack trace (except main reference), if settings.INCLUDE_DEBUGUTILS_INTO_TRACE is set to true.
if settings.INCLUDE_DEBUGUTILS_INTO_TRACE or (loopDepth == startDepth) or currentFile ~= "DebugUtils:" then
trace = trace .. separator .. ((currentFile == lastFile) and tracePiece:match(":\x25d+"):sub(2,-1) or tracePiece:match("^.-:\x25d+"))
lastFile, lastTracePiece, separator = currentFile, tracePiece, " <- "
end
end
end
return trace
end
---Message Handler to be used by the try-function below.
---Adds stack trace plus formatting to the message and prints it.
---@param errorMsg string
---@param startDepth? integer default: 4 for use in xpcall
local function errorHandler(errorMsg, startDepth)
startDepth = startDepth or 4 --xpcall doesn't specify this param, so it must default to 4 for this case
errorMsg = convertToLocalErrorMsg(errorMsg)
--Print original error message and stack trace.
print("|cffff5555ERROR at " .. errorMsg .. "|r")
if settings.SHOW_TRACE_ON_ERROR then
print("|cffff5555Traceback (most recent call first):|r")
print("|cffff5555" .. getStackTrace(startDepth,200) .. "|r")
end
--Also print entries from param log, if there are any.
for location, loggedParams in pairs(paramLog) do
print("|cff888888Logged at " .. convertToLocalErrorMsg(location) .. loggedParams .. "|r")
paramLog[location] = nil
end
end
---Tries to execute the specified function with the specified parameters in protected mode and prints an error message (including stack trace), if unsuccessful.
---
---Example use: Assume you have a code line like "CreateUnit(0,1,2)", which doesn't work and you want to know why.
---* Option 1: Change it to "Debug.try(CreateUnit, 0, 1, 2)", i.e. separate the function from the parameters.
---* Option 2: Change it to "Debug.try(function() return CreateUnit(0,1,2) end)", i.e. pack it into an anonymous function. You can skip the "return", if you don't need the return values.
---When no error occured, the try-function will return all values returned by the input function.
---When an error occurs, try will print the resulting error and stack trace.
---@param funcToExecute function the function to call in protected mode
---@param ... any params for the input-function
---@return ... any
function Debug.try(funcToExecute, ...)
return select(2, xpcall(funcToExecute, errorHandler,...))
end
---@diagnostic disable-next-line lowercase-global
try = Debug.try
---Prints "ERROR:" and the specified error objects on the Screen. Also prints the stack trace leading to the error. You can specify as many arguments as you wish.
---
---In contrast to Lua's native error function, this can be called outside of protected mode and doesn't halt code execution.
---@param ... any objects/errormessages to be printed (doesn't have to be strings)
function Debug.throwError(...)
errorHandler(getStackTrace(4,4) .. ": " .. concat(...), 5)
end
---Prints the specified error message, if the specified condition fails (i.e. if it resolves to false or nil).
---
---Returns all specified arguments after the errorMsg, if the condition holds.
---
---In contrast to Lua's native assert function, this can be called outside of protected mode and doesn't halt code execution (even in case of condition failure).
---@param condition any actually a boolean, but you can use any object as a boolean.
---@param errorMsg string the message to be printed, if the condition fails
---@param ... any will be returned, if the condition holds
function Debug.assert(condition, errorMsg, ...)
if condition then
return ...
else
errorHandler(getStackTrace(4,4) .. ": " .. errorMsg, 5)
end
end
---Returns the stack trace at the code position where this function is called.
---The returned string includes war3map.lua/blizzard.j.lua code positions of all functions from the stack trace in the order of execution (most recent call last). It does NOT include function names.
---@return string
function Debug.traceback()
return getStackTrace(3,200)
end
---Saves the specified parameters to the debug log at the location where this function is called. The Debug-log will be printed for all affected locations upon the try-function catching an error.
---The log is unique per code location: Parameters logged at code line x will overwrite the previous ones logged at x. Parameters logged at different locations will all persist and be printed.
---@param ... any save any information, for instance the parameters of the function call that you are logging.
function Debug.log(...)
local _, location = pcall(error, "", 3) ---@diagnostic disable-next-line: need-check-nil
paramLog[location or ''] = concat(...)
end
----------------------------------
--| Undeclared Global Warnings |--
----------------------------------
--Utility function here. _G metatable behaviour is defined in Gamestart section further below.
local globalWarningExclusions = Debug.data.globalWarningExclusions
---Disables nil-warnings for the specified variable.
---@param variableName string
function Debug.disableNilWarningsFor(variableName)
globalWarningExclusions[variableName] = true
end
------------------------------------
--| Name Caching (API-functions) |--
------------------------------------
--Help-table. The registerName-functions below shall not work on call-by-value-types, i.e. booleans, strings and numbers (renaming a value of any primitive type doesn't make sense).
local skipType = {boolean = true, string = true, number = true, ['nil'] = true}
--Set weak keys to nameCache and nameDepths and weak values for nameCacheMirror to prevent garbage collection issues
setmetatable(nameCache, {__mode = 'k'})
setmetatable(nameDepths, getmetatable(nameCache))
setmetatable(nameCacheMirror, {__mode = 'v'})
---Removes the name from the name cache, if already used for any object (freeing it for the new object). This makes sure that a name is always unique.
---This doesn't solve the
---@param name string
local function removeNameIfNecessary(name)
if nameCacheMirror[name] then
nameCache[nameCacheMirror[name]] = nil
nameCacheMirror[name] = nil
end
end
---Registers a name for the specified object, which will be the future output for tostring(whichObject).
---You can overwrite existing names for whichObject by using this.
---@param whichObject any
---@param name string
function Debug.registerName(whichObject, name)
if not skipType[type(whichObject)] then
removeNameIfNecessary(name)
nameCache[whichObject] = name
nameCacheMirror[name] = whichObject
nameDepths[name] = 0
end
end
---Registers a new name to the nameCache as either just <key> (if parentTableName is the empty string), <table>.<key> (if parentTableName is given and string key doesn't contain whitespace) or <name>[<key>] notation (for other keys in existing tables).
---Only string keys without whitespace support <key>- and <table>.<key>-notation. All other keys require a parentTableName.
---@param parentTableName string | '""' empty string suppresses <table>-affix.
---@param key any
---@param object any only call-be-ref types allowed
---@param parentTableDepth? integer
local function addNameToCache(parentTableName, key, object, parentTableDepth)
parentTableDepth = parentTableDepth or -1
--Don't overwrite existing names for the same object, don't add names for primitive types.
if nameCache[object] or skipType[type(object)] then
return
end
local name
--apply dot-syntax for string keys without whitespace
if type(key) == 'string' and not string.find(key, "\x25s") then
if parentTableName == "" then
name = key
nameDepths[object] = 0
else
name = parentTableName .. "." .. key
nameDepths[object] = parentTableDepth + 1
end
--apply bracket-syntax for all other keys. This requires a parentTableName.
elseif parentTableName ~= "" then
name = type(key) == 'string' and ('"' .. key .. '"') or key
name = parentTableName .. "[" .. tostring(name) .. "]"
nameDepths[object] = parentTableDepth + 1
end
--Stop in cases without valid name (like parentTableName = "" and key = [1])
if name then
removeNameIfNecessary(name)
nameCache[object] = name
nameCacheMirror[name] = object
end
end
---Registers all call-by-reference objects in the given parentTable to the nameCache.
---Automatically filters out primitive objects and already registed Objects.
---@param parentTable table
---@param parentTableName? string
local function registerAllObjectsInTable(parentTable, parentTableName)
parentTableName = parentTableName or nameCache[parentTable] or ""
--Register all call-by-ref-objects in parentTable
for key, object in pairs(parentTable) do
addNameToCache(parentTableName, key, object, nameDepths[parentTable])
end
end
---Adds names for all values of the specified parentTable to the name cache. Names will be "<parentTableName>.<key>" or "<parentTableName>[<key>]", depending on the key type.
---
---Example: Given a table T = {f = function() end, [1] = {}}, tostring(T.f) and tostring(T[1]) will output "function: T.f" and "table: T[1]" respectively after running Debug.registerNamesFrom(T).
---The name of T itself must either be specified as an input parameter OR have previously been registered. It can also be suppressed by inputting the empty string (so objects will just display by their own names).
---The names of objects in global scope are automatically registered during loading screen.
---@param parentTable table base table of which all entries shall be registered (in the Form parentTableName.objectName).
---@param parentTableName? string|'""' Nil: takes <parentTableName> as previously registered. Empty String: Skips <parentTableName> completely. String <s>: Objects will show up as "<s>.<objectName>".
---@param depth? integer objects within sub-tables up to the specified depth will also be added. Default: 1 (only elements of whichTable). Must be >= 1.
---@overload fun(parentTable:table, depth:integer)
function Debug.registerNamesFrom(parentTable, parentTableName, depth)
--Support overloaded definition fun(parentTable:table, depth:integer)
if type(parentTableName) == 'number' then
depth = parentTableName
parentTableName = nil
end
--Apply default values
depth = depth or 1
parentTableName = parentTableName or nameCache[parentTable] or ""
--add name of T in case it hasn't already
if not nameCache[parentTable] and parentTableName ~= "" then
Debug.registerName(parentTable, parentTableName)
end
--Register all call-by-ref-objects in parentTable. To be preferred over simple recursive approach to ensure that top level names are preferred.
registerAllObjectsInTable(parentTable, parentTableName)
--if depth > 1 was specified, also register Names from subtables.
if depth > 1 then
for _, object in pairs(parentTable) do
if type(object) == 'table' then
Debug.registerNamesFrom(object, nil, depth - 1)
end
end
end
end
-------------------------------------------
--| Name Caching (Loading Screen setup) |--
-------------------------------------------
---Registers all existing object names from global scope and Lua incorporated libraries to be used by tostring() overwrite below.
local function registerNamesFromGlobalScope()
--Add all names from global scope to the name cache.
Debug.registerNamesFrom(_G, "")
--Add all names of Warcraft-enabled Lua libraries as well:
--Could instead add a depth to the function call above, but we want to ensure that these libraries are added even if the user has chosen depth 0.
for _, lib in ipairs({coroutine, math, os, string, table, utf8, Debug}) do
Debug.registerNamesFrom(lib)
end
--Add further names that are not accessible from global scope:
--Player(i)
for i = 0, GetBJMaxPlayerSlots() - 1 do
Debug.registerName(Player(i), "Player(" .. i .. ")")
end
end
--Set empty metatable to _G. __index is added when game starts (for "attempt to read nil-global"-errors), __newindex is added right below (for building the name cache).
setmetatable(_G, getmetatable(_G) or {}) --getmetatable(_G) should always return nil provided that DebugUtils is the topmost script file in the trigger editor, but we still include this for safety-
-- Save old tostring into Debug Library before overwriting it.
Debug.oldTostring = tostring
if settings.USE_NAME_CACHE then
local oldTostring = tostring
tostring = function(obj) --new tostring(CreateUnit) prints "function: CreateUnit"
--tostring of non-primitive object is NOT guaranteed to be like "<type>:<hex>", because it might have been changed by some __tostring-metamethod.
if settings.USE_NAME_CACHE then --return names from name cache only if setting is enabled. This allows turning it off during runtime (via Ingame Console) to revert to old tostring.
return nameCache[obj] and ((oldTostring(obj):match("^.-: ") or (oldTostring(obj) .. ": ")) .. nameCache[obj]) or oldTostring(obj)
end
return Debug.oldTostring(obj)
end
--Add names to Debug.data.objectNames within Lua root. Called below the other Debug-stuff to get the overwritten versions instead of the original ones.
registerNamesFromGlobalScope()
--Prepare __newindex-metamethod to automatically add new names to the name cache
if settings.AUTO_REGISTER_NEW_NAMES then
local nameRegisterNewIndex
---__newindex to be used for _G (and subtables up to a certain depth) to automatically register new names to the nameCache.
---Tables in global scope will use their own name. Subtables of them will use <parentName>.<childName> syntax.
---Global names don't support container[key]-notation (because "_G[...]" is probably not desired), so we only register string type keys instead of using prettyTostring.
---@param t table
---@param k any
---@param v any
---@param skipRawset? boolean set this to true when combined with another __newindex. Suppresses rawset(t,k,v) (because the other __newindex is responsible for that).
nameRegisterNewIndex = function(t,k,v, skipRawset)
local parentDepth = nameDepths[t] or 0
--Make sure the parent table has an existing name before using it as part of the child name
if t == _G or nameCache[t] then
local existingName = nameCache[v]
if not existingName then
addNameToCache((t == _G and "") or nameCache[t], k, v, parentDepth)
end
--If v is a table and the parent table has a valid name, inherit __newindex to v's existing metatable (or create a new one), if that wasn't already done.
if type(v) == 'table' and nameDepths[v] < settings.NAME_CACHE_DEPTH then
if not existingName then
--If v didn't have a name before, also add names for elements contained in v by construction (like v = {x = function() end} ).
Debug.registerNamesFrom(v, settings.NAME_CACHE_DEPTH - nameDepths[v])
end
--Apply __newindex to new tables.
if not autoIndexedTables[v] then
autoIndexedTables[v] = true
local mt = getmetatable(v)
if not mt then
mt = {}
setmetatable(v, mt) --only use setmetatable when we are sure there wasn't any before to prevent issues with "__metatable"-metamethod.
end
---@diagnostic disable-next-line: assign-type-mismatch
local existingNewIndex = mt.__newindex
local isTable_yn = (type(existingNewIndex) == 'table')
--If mt has an existing __newindex, add the name-register effect to it (effectively create a new __newindex using the old)
if existingNewIndex then
mt.__newindex = function(t,k,v)
nameRegisterNewIndex(t,k,v, true) --setting t[k] = v might not be desired in case of existing newindex. Skip it and let existingNewIndex make the decision.
if isTable_yn then
existingNewIndex[k] = v
else
return existingNewIndex(t,k,v)
end
end
else
--If mt doesn't have an existing __newindex, add one that adds the object to the name cache.
mt.__newindex = nameRegisterNewIndex
end
end
end
end
--Set t[k] = v.
if not skipRawset then
rawset(t,k,v)
end
end
--Apply metamethod to _G.
local existingNewIndex = getmetatable(_G).__newindex --should always be nil provided that DebugUtils is the topmost script in your trigger editor. Still included for safety.
local isTable_yn = (type(existingNewIndex) == 'table')
if existingNewIndex then
getmetatable(_G).__newindex = function(t,k,v)
nameRegisterNewIndex(t,k,v, true)
if isTable_yn then
existingNewIndex[k] = v
else
existingNewIndex(t,k,v)
end
end
else
getmetatable(_G).__newindex = nameRegisterNewIndex
end
end
end
------------------------------------------------------
--| Native Overwrite for Automatic Error Handling |--
------------------------------------------------------
--A table to store the try-wrapper for each function. This avoids endless re-creation of wrapper functions within the hooks below.
--Weak keys ensure that garbage collection continues as normal.
local tryWrappers = setmetatable({}, {__mode = 'k'}) ---@type table<function,function>
local try = Debug.try
---Takes a function and returns a wrapper executing the same function within Debug.try.
---Wrappers are permanently stored (until the original function is garbage collected) to ensure that they don't have to be created twice for the same function.
---@param func? function
---@return function
local function getTryWrapper(func)
if func then
tryWrappers[func] = tryWrappers[func] or function(...) return try(func, ...) end
end
return tryWrappers[func] --returns nil for func = nil (important for TimerStart overwrite below)
end
--Overwrite TriggerAddAction, TimerStart, Condition, Filter and Enum natives to let them automatically apply Debug.try.
--Also overwrites coroutine.create and coroutine.wrap to let stack traces point to the function executed within instead of the function creating the coroutine.
if settings.USE_TRY_ON_TRIGGERADDACTION then
local originalTriggerAddAction = TriggerAddAction
TriggerAddAction = function(whichTrigger, actionFunc)
return originalTriggerAddAction(whichTrigger, getTryWrapper(actionFunc))
end
end
if settings.USE_TRY_ON_TIMERSTART then
local originalTimerStart = TimerStart
TimerStart = function(whichTimer, timeout, periodic, handlerFunc)
originalTimerStart(whichTimer, timeout, periodic, getTryWrapper(handlerFunc))
end
end
if settings.USE_TRY_ON_CONDITION then
local originalCondition = Condition
Condition = function(func)
return originalCondition(getTryWrapper(func))
end
Filter = Condition
end
if settings.USE_TRY_ON_ENUMFUNCS then
local originalForGroup = ForGroup
ForGroup = function(whichGroup, callback)
originalForGroup(whichGroup, getTryWrapper(callback))
end
local originalForForce = ForForce
ForForce = function(whichForce, callback)
originalForForce(whichForce, getTryWrapper(callback))
end
local originalEnumItemsInRect = EnumItemsInRect
EnumItemsInRect = function(r, filter, actionfunc)
originalEnumItemsInRect(r, filter, getTryWrapper(actionfunc))
end
local originalEnumDestructablesInRect = EnumDestructablesInRect
EnumDestructablesInRect = function(r, filter, actionFunc)
originalEnumDestructablesInRect(r, filter, getTryWrapper(actionFunc))
end
end
if settings.USE_TRY_ON_COROUTINES then
local originalCoroutineCreate = coroutine.create
---@diagnostic disable-next-line: duplicate-set-field
coroutine.create = function(f)
return originalCoroutineCreate(getTryWrapper(f))
end
local originalCoroutineWrap = coroutine.wrap
---@diagnostic disable-next-line: duplicate-set-field
coroutine.wrap = function(f)
return originalCoroutineWrap(getTryWrapper(f))
end
end
------------------------------------------
--| Cache prints during Loading Screen |--
------------------------------------------
-- Apply the duration as specified in the settings.
if settings.PRINT_DURATION then
local display, getLocalPlayer, dur = DisplayTimedTextToPlayer, GetLocalPlayer, settings.PRINT_DURATION
print = function(...) ---@diagnostic disable-next-line: param-type-mismatch
display(getLocalPlayer(), 0, 0, dur, concat(...))
end
end
-- Delay loading screen prints to after game start.
if settings.USE_PRINT_CACHE then
local oldPrint = print
--loading screen print will write the values into the printCache
print = function(...)
if bj_gameStarted then
oldPrint(...)
else --during loading screen only: concatenate input arguments 4-space-separated, implicitely apply tostring on each, cache to table
---@diagnostic disable-next-line
printCache.n = printCache.n + 1
printCache[printCache.n] = concat(...)
end
end
end
-------------------------
--| Modify Game Start |--
-------------------------
local originalMarkGameStarted = MarkGameStarted
--Hook certain actions into the start of the game.
MarkGameStarted = function()
originalMarkGameStarted()
if settings.WARNING_FOR_NIL_GLOBALS then
local existingIndex = getmetatable(_G).__index
local isTable_yn = (type(existingIndex) == 'table')
getmetatable(_G).__index = function(t, k) --we made sure that _G has a metatable further above.
--Don't show warning, if the variable name has been actively excluded or if it's a bj_ variable (and those are excluded).
if (not globalWarningExclusions[k]) and ((not settings.EXCLUDE_BJ_GLOBALS_FROM_NIL_WARNINGS) or string.sub(tostring(k),1,3) ~= 'bj_') then --prevents intentionally nilled bj-variables from triggering the check within Blizzard.j-functions, like bj_cineFadeFinishTimer.
print("Trying to read nil global at " .. getStackTrace(4,4) .. ": " .. tostring(k)
.. (settings.SHOW_TRACE_FOR_NIL_WARNINGS and "\nTraceback (most recent call first):\n" .. getStackTrace(4,200) or ""))
end
if existingIndex then
if isTable_yn then
return existingIndex[k]
end
return existingIndex(t,k)
end
return rawget(t,k)
end
if settings.EXCLUDE_INITIALIZED_GLOBALS_FROM_NIL_WARNINGS then
local existingNewIndex = getmetatable(_G).__newindex
local isTable_yn = (type(existingNewIndex) == 'table')
getmetatable(_G).__newindex = function(t,k,v)
--First, exclude the initialized global from future warnings
globalWarningExclusions[k] = true
--Second, execute existing newindex, if there is one in place.
if existingNewIndex then
if isTable_yn then
existingNewIndex[k] = v
else
existingNewIndex(t,k,v)
end
else
rawset(t,k,v)
end
end
end
end
--Add names to Debug.data.objectNames again to ensure that overwritten natives also make it to the name cache.
--Overwritten natives have a new value, but the old key, so __newindex didn't trigger. But we can be sure that objectNames[v] doesn't yet exist, so adding again is safe.
if settings.USE_NAME_CACHE then
for _,v in pairs(_G) do
nameCache[v] = nil
end
registerNamesFromGlobalScope()
end
--Print messages that have been cached during loading screen.
if settings.USE_PRINT_CACHE then
--Note that we don't restore the old print. The overwritten variant only applies caching behaviour to loading screen prints anyway and "unhooking" always adds other risks.
for _, str in ipairs(printCache) do
print(str)
end ---@diagnostic disable-next-line: cast-local-type
printCache = nil --frees reference for the garbage collector
end
--Create triggers listening to "-console" and "-exec" chat input.
if settings.ALLOW_INGAME_CODE_EXECUTION and IngameConsole then
IngameConsole.createTriggers()
end
end
---------------------
--| Other Utility |--
---------------------
do
---Returns the type of a warcraft object as string, e.g. "unit" upon inputting a unit.
---@param input any
---@return string
function Debug.wc3Type(input)
local typeString = type(input)
if typeString == 'userdata' then
typeString = tostring(input) --tostring returns the warcraft type plus a colon and some hashstuff.
return typeString:sub(1, (typeString:find(":", nil, true) or 0) -1) --string.find returns nil, if the argument is not found, which would break string.sub. So we need to replace by 0.
else
return typeString
end
end
Wc3Type = Debug.wc3Type --for backwards compatibility
local conciseTostring, prettyTostring
---Translates a table into a comma-separated list of its (key,value)-pairs. Also translates subtables up to the specified depth.
---E.g. {"a", 5, {7}} will display as '{(1, "a"), (2, 5), (3, {(1, 7)})}'.
---@param object any
---@param depth? integer default: unlimited. Unlimited depth will throw a stack overflow error on self-referential tables.
---@return string
conciseTostring = function (object, depth)
depth = depth or -1
if type(object) == 'string' then
return '"' .. object .. '"'
elseif depth ~= 0 and type(object) == 'table' then
local elementArray = {}
local keyAsString
for k,v in pairs(object) do
keyAsString = type(k) == 'string' and ('"' .. tostring(k) .. '"') or tostring(k)
table.insert(elementArray, '(' .. keyAsString .. ', ' .. conciseTostring(v, depth -1) .. ')')
end
return '{' .. table.concat(elementArray, ', ') .. '}'
end
return tostring(object)
end
---Creates a list of all (key,value)-pairs from the specified table. Also lists subtable entries up to the specified depth.
---Major differences to concise print are:
--- * Format: Linebreak-formatted instead of one-liner, uses "[key] = value" instead of "(key,value)"
--- * Will also unpack tables used as keys
--- * Also includes the table's memory position as returned by tostring(table).
--- * Tables referenced multiple times will only be unpacked upon first encounter and abbreviated on subsequent encounters
--- * As a consequence, pretty version can be executed with unlimited depth on self-referential tables.
---@param object any
---@param depth? integer default: unlimited.
---@param constTable table
---@param indent string
---@return string
prettyTostring = function(object, depth, constTable, indent)
depth = depth or -1
local objType = type(object)
if objType == "string" then
return '"'..object..'"' --wrap the string in quotes.
elseif objType == 'table' and depth ~= 0 then
if not constTable[object] then
constTable[object] = tostring(object):gsub(":","")
if next(object)==nil then
return constTable[object]..": {}"
else
local mappedKV = {}
for k,v in pairs(object) do
table.insert(mappedKV, '\n ' .. indent ..'[' .. prettyTostring(k, depth - 1, constTable, indent .. " ") .. '] = ' .. prettyTostring(v, depth - 1, constTable, indent .. " "))
end
return constTable[object]..': {'.. table.concat(mappedKV, ',') .. '\n'..indent..'}'
end
end
end
return constTable[object] or tostring(object)
end
---Creates a list of all (key,value)-pairs from the specified table. Also lists subtable entries up to the specified depth.
---Supports concise style and pretty style.
---Concise will display {"a", 5, {7}} as '{(1, "a"), (2, 5), (3, {(1, 7)})}'.
---Pretty is linebreak-separated, so consider table size before converting. Pretty also abbreviates tables referenced multiple times.
---Can be called like table.tostring(T), table.tostring(T, depth), table.tostring(T, pretty_yn) or table.tostring(T, depth, pretty_yn).
---table.tostring is not multiplayer-synced.
---@param whichTable table
---@param depth? integer default: unlimited
---@param pretty_yn? boolean default: false (concise)
---@return string
---@overload fun(whichTable:table, pretty_yn?:boolean):string
function table.tostring(whichTable, depth, pretty_yn)
--reassign input params, if function was called as table.tostring(whichTable, pretty_yn)
if type(depth) == 'boolean' then
pretty_yn = depth
depth = -1
end
return pretty_yn and prettyTostring(whichTable, depth, {}, "") or conciseTostring(whichTable, depth)
end
---Prints a list of (key,value)-pairs contained in the specified table and its subtables up to the specified depth.
---Supports concise style and pretty style. Pretty is linebreak-separated, so consider table size before printing.
---Can be called like table.print(T), table.print(T, depth), table.print(T, pretty_yn) or table.print(T, depth, pretty_yn).
---@param whichTable table
---@param depth? integer default: unlimited
---@param pretty_yn? boolean default: false (concise)
---@overload fun(whichTable:table, pretty_yn?:boolean)
function table.print(whichTable, depth, pretty_yn)
print(table.tostring(whichTable, depth, pretty_yn))
end
end
end
Debug.endFile()
if Debug and Debug.beginFile then Debug.beginFile("IngameConsole") end
--[[
--------------------------
----| Ingame Console |----
--------------------------
/**********************************************
* Allows you to use the following ingame commands:
* "-exec <code>" to execute any code ingame.
* "-console" to start an ingame console interpreting any further chat input as code and showing both return values of function calls and error messages. Furthermore, the print function will print
* directly to the console after it got started. You can still look up all print messages in the F12-log.
***********************
* -------------------
* |Using the console|
* -------------------
* Any (well, most) chat input by any player after starting the console is interpreted as code and directly executed. You can enter terms (like 4+5 or just any variable name), function calls (like print("bla"))
* and set-statements (like y = 5). If the code has any return values, all of them are printed to the console. Erroneous code will print an error message.
* Chat input starting with a hyphen is being ignored by the console, i.e. neither executed as code nor printed to the console. This allows you to still use other chat commands like "-exec" without prompting errors.
***********************
* ------------------
* |Multiline-Inputs|
* ------------------
* You can prevent a chat input from being immediately executed by preceeding it with the '>' character. All lines entered this way are halted, until any line not starting with '>' is being entered.
* The first input without '>' will execute all halted lines (and itself) in one chunk.
* Example of a chat input (the console will add an additional '>' to every line):
* >function a(x)
* >return x
* end
***********************
* Note that multiline inputs don't accept pure term evaluations, e.g. the following input is not supported and will prompt an error, while the same lines would have worked as two single-line inputs:
* >x = 5
* x
***********************
* -------------------
* |Reserved Keywords|
* -------------------
* The following keywords have a reserved functionality, i.e. are direct commands for the console and will not be interpreted as code:
* - 'help' - will show a list of all reserved keywords along very short explanations.
* - 'exit' - will shut down the console
* - 'share' - will share the players console with every other player, allowing others to read and write into it. Will force-close other players consoles, if they have one active.
* - 'clear' - will clear all text from the console, except the word 'clear'
* - 'lasttrace' - will show the stack trace of the latest error that occured within IngameConsole
* - 'show' - will show the console, after it was accidently hidden (you can accidently hide it by showing another multiboard, while the console functionality is still up and running).
* - 'printtochat' - will let the print function return to normal behaviour (i.e. print to the chat instead of the console).
* - 'printtoconsole'- will let the print function print to the console (which is default behaviour).
* - 'autosize on' - will enable automatic console resize depending on the longest string in the display. This is turned on by default.
* - 'autosize off' - will disable automatic console resize and instead linebreak long strings into multiple lines.
* - 'textlang eng' - lets the console use english Wc3 text language font size to compute linebreaks (look in your Blizzard launcher settings to find out)
* - 'textlang ger' - lets the console use german Wc3 text language font size to compute linebreaks (look in your Blizzard launcher settings to find out)
***********************
* --------------
* |Paste Helper|
* --------------
* @Luashine has created a tool that simplifies pasting multiple lines of code from outside Wc3 into the IngameConsole.
* This is particularly useful, when you want to execute a large chunk of testcode containing several linebreaks.
* Goto: https://github.com/Luashine/wc3-debug-console-paste-helper#readme
*
*************************************************/
--]]
----------------
--| Settings |--
----------------
---@class IngameConsole
IngameConsole = {
--Settings
numRows = 20 ---@type integer Number of Rows of the console (multiboard), excluding the title row. So putting 20 here will show 21 rows, first being the title row.
, autosize = true ---@type boolean Defines, whether the width of the main Column automatically adjusts with the longest string in the display.
, currentWidth = 0.5 ---@type number Current and starting Screen Share of the console main column.
, mainColMinWidth = 0.3 ---@type number Minimum Screen share of the console main column.
, mainColMaxWidth = 0.8 ---@type number Maximum Scren share of the console main column.
, tsColumnWidth = 0.06 ---@type number Screen Share of the Timestamp Column
, linebreakBuffer = 0.008 ---@type number Screen Share that is added to longest string in display to calculate the screen share for the console main column. Compensates for the small inaccuracy of the String Width function.
, maxLinebreaks = 8 ---@type integer Defines the maximum amount of linebreaks, before the remaining output string will be cut and not further displayed.
, printToConsole = true ---@type boolean defines, if the print function should print to the console or to the chat
, sharedConsole = false ---@type boolean defines, if the console is displayed to each player at the same time (accepting all players input) or if all players much start their own console.
, showTraceOnError = false ---@type boolean defines, if the console shows a trace upon printing errors. Usually not too useful within console, because you have just initiated the erroneous call.
, textLanguage = 'eng' ---@type string text language of your Wc3 installation, which influences font size (look in the settings of your Blizzard launcher). Currently only supports 'eng' and 'ger'.
, colors = {
timestamp = "bbbbbb" ---@type string Timestamp Color
, singleLineInput = "ffffaa" ---@type string Color to be applied to single line console inputs
, multiLineInput = "ffcc55" ---@type string Color to be applied to multi line console inputs
, returnValue = "00ffff" ---@type string Color applied to return values
, error = "ff5555" ---@type string Color to be applied to errors resulting of function calls
, keywordInput = "ff00ff" ---@type string Color to be applied to reserved keyword inputs (console reserved keywords)
, info = "bbbbbb" ---@type string Color to be applied to info messages from the console itself (for instance after creation or after printrestore)
}
--Privates
, numCols = 2 ---@type integer Number of Columns of the console (multiboard). Adjusting this requires further changes on code base.
, player = nil ---@type player player for whom the console is being created
, currentLine = 0 ---@type integer Current Output Line of the console.
, inputload = '' ---@type string Input Holder for multi-line-inputs
, output = {} ---@type string[] Array of all output strings
, outputTimestamps = {} ---@type string[] Array of all output string timestamps
, outputWidths = {} ---@type number[] remembers all string widths to allow for multiboard resize
, trigger = nil ---@type trigger trigger processing all inputs during console lifetime
, multiboard = nil ---@type multiboard
, timer = nil ---@type timer gets started upon console creation to measure timestamps
, errorHandler = nil ---@type fun(errorMsg:string):string error handler to be used within xpcall. We create one per console to make it compatible with console-specific settings.
, lastTrace = '' ---@type string trace of last error occured within console. To be printed via reserved keyword "lasttrace"
--Statics
, keywords = {} ---@type table<string,function> saves functions to be executed for all reserved keywords
, playerConsoles = {} ---@type table<player,IngameConsole> Consoles currently being active. up to one per player.
, originalPrint = print ---@type function original print function to restore, after the console gets closed.
}
IngameConsole.__index = IngameConsole
IngameConsole.__name = 'IngameConsole'
------------------------
--| Console Creation |--
------------------------
---Creates and opens up a new console.
---@param consolePlayer player player for whom the console is being created
---@return IngameConsole
function IngameConsole.create(consolePlayer)
local new = {} ---@type IngameConsole
setmetatable(new, IngameConsole)
---setup Object data
new.player = consolePlayer
new.output = {}
new.outputTimestamps = {}
new.outputWidths = {}
--Timer
new.timer = CreateTimer()
TimerStart(new.timer, 3600., true, nil) --just to get TimeElapsed for printing Timestamps.
--Trigger to be created after short delay, because otherwise it would fire on "-console" input immediately and lead to stack overflow.
new:setupTrigger()
--Multiboard
new:setupMultiboard()
--Create own error handler per console to be compatible with console-specific settings
new:setupErrorHandler()
--Share, if settings say so
if IngameConsole.sharedConsole then
new:makeShared() --we don't have to exit other players consoles, because we look for the setting directly in the class and there just logically can't be other active consoles.
end
--Welcome Message
new:out('info', 0, false, "Console started. Any further chat input will be executed as code, except when beginning with \x22-\x22.")
return new
end
---Creates the multiboard used for console display.
function IngameConsole:setupMultiboard()
self.multiboard = CreateMultiboard()
MultiboardSetRowCount(self.multiboard, self.numRows + 1) --title row adds 1
MultiboardSetColumnCount(self.multiboard, self.numCols)
MultiboardSetTitleText(self.multiboard, "Console")
local mbitem
for col = 1, self.numCols do
for row = 1, self.numRows + 1 do --Title row adds 1
mbitem = MultiboardGetItem(self.multiboard, row -1, col -1)
MultiboardSetItemStyle(mbitem, true, false)
MultiboardSetItemValueColor(mbitem, 255, 255, 255, 255) -- Colors get applied via text color code
MultiboardSetItemWidth(mbitem, (col == 1 and self.tsColumnWidth) or self.currentWidth )
MultiboardReleaseItem(mbitem)
end
end
mbitem = MultiboardGetItem(self.multiboard, 0, 0)
MultiboardSetItemValue(mbitem, "|cffffcc00Timestamp|r")
MultiboardReleaseItem(mbitem)
mbitem = MultiboardGetItem(self.multiboard, 0, 1)
MultiboardSetItemValue(mbitem, "|cffffcc00Line|r")
MultiboardReleaseItem(mbitem)
self:showToOwners()
end
---Creates the trigger that responds to chat events.
function IngameConsole:setupTrigger()
self.trigger = CreateTrigger()
TriggerRegisterPlayerChatEvent(self.trigger, self.player, "", false) --triggers on any input of self.player
TriggerAddCondition(self.trigger, Condition(function() return string.sub(GetEventPlayerChatString(),1,1) ~= '-' end)) --console will not react to entered stuff starting with '-'. This still allows to use other chat orders like "-exec".
TriggerAddAction(self.trigger, function() self:processInput(GetEventPlayerChatString()) end)
end
---Creates an Error Handler to be used by xpcall below.
---Adds stack trace plus formatting to the message.
function IngameConsole:setupErrorHandler()
self.errorHandler = function(errorMsg)
errorMsg = Debug.getLocalErrorMsg(errorMsg)
local _, tracePiece, lastFile = nil, "", errorMsg:match("^.-:") or "<unknown>" -- errors on objects created within Ingame Console don't have a file and linenumber. Consider "x = {}; x[nil] = 5".
local fullMsg = errorMsg .. "\nTraceback (most recent call first):\n" .. (errorMsg:match("^.-:\x25d+") or "<unknown>")
--Get Stack Trace. Starting at depth 5 ensures that "error", "messageHandler", "xpcall" and the input error message are not included.
for loopDepth = 5, 50 do --get trace on depth levels up to 50
---@diagnostic disable-next-line: cast-local-type, assign-type-mismatch
_, tracePiece = pcall(error, "", loopDepth) ---@type boolean, string
tracePiece = Debug.getLocalErrorMsg(tracePiece)
if #tracePiece > 0 then --some trace pieces can be empty, but there can still be valid ones beyond that
fullMsg = fullMsg .. " <- " .. ((tracePiece:match("^.-:") == lastFile) and tracePiece:match(":\x25d+"):sub(2,-1) or tracePiece:match("^.-:\x25d+"))
lastFile = tracePiece:match("^.-:")
end
end
self.lastTrace = fullMsg
return "ERROR: " .. (self.showTraceOnError and fullMsg or errorMsg)
end
end
---Shares this console with all players.
function IngameConsole:makeShared()
local player
for i = 0, GetBJMaxPlayers() -1 do
player = Player(i)
if (GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING) and (IngameConsole.playerConsoles[player] ~= self) then --second condition ensures that the player chat event is not added twice for the same player.
IngameConsole.playerConsoles[player] = self
TriggerRegisterPlayerChatEvent(self.trigger, player, "", false) --triggers on any input
end
end
self.sharedConsole = true
end
---------------------
--| In |--
---------------------
---Processes a chat string. Each input will be printed. Incomplete multiline-inputs will be halted until completion. Completed inputs will be converted to a function and executed. If they have an output, it will be printed.
---@param inputString string
function IngameConsole:processInput(inputString)
--if the input is a reserved keyword, conduct respective actions and skip remaining actions.
if IngameConsole.keywords[inputString] then --if the input string is a reserved keyword
self:out('keywordInput', 1, false, inputString)
IngameConsole.keywords[inputString](self) --then call the method with the same name. IngameConsole.keywords["exit"](self) is just self.keywords:exit().
return
end
--if the input is a multi-line-input, queue it into the string buffer (inputLoad), but don't yet execute anything
if string.sub(inputString, 1, 1) == '>' then --multiLineInput
inputString = string.sub(inputString, 2, -1)
self:out('multiLineInput',2, false, inputString)
self.inputload = self.inputload .. inputString .. '\n'
else --if the input is either singleLineInput OR the last line of multiLineInput, execute the whole thing.
self:out(self.inputload == '' and 'singleLineInput' or 'multiLineInput', 1, false, inputString)
self.inputload = self.inputload .. inputString
local loadedFunc, errorMsg = load("return " .. self.inputload) --adds return statements, if possible (works for term statements)
if loadedFunc == nil then
loadedFunc, errorMsg = load(self.inputload)
end
self.inputload = '' --empty inputload before execution of pcall. pcall can break (rare case, can for example be provoked with metatable.__tostring = {}), which would corrupt future console inputs.
--manually catch case, where the input did not define a proper Lua statement (i.e. loadfunc is nil)
local results = loadedFunc and table.pack(xpcall(loadedFunc, self.errorHandler)) or {false, "Input is not a valid Lua-statement: " .. errorMsg}
--output error message (unsuccessful case) or return values (successful case)
if not results[1] then --results[1] is the error status that pcall always returns. False stands for: error occured.
self:out('error', 0, true, results[2]) -- second result of pcall is the error message in case an error occured
elseif results.n > 1 then --Check, if there was at least one valid output argument. We check results.n instead of results[2], because we also get nil as a proper return value this way.
self:out('returnValue', 0, true, table.unpack(results, 2, results.n))
end
end
end
----------------------
--| Out |--
----------------------
-- split color codes, split linebreaks, print lines separately, print load-errors, update string width, update text, error handling with stack trace.
---Duplicates Color coding around linebreaks to make each line printable separately.
---Operates incorrectly on lookalike color codes invalidated by preceeding escaped vertical bar (like "||cffffcc00bla|r").
---Also operates incorrectly on multiple color codes, where the first is missing the end sequence (like "|cffffcc00Hello |cff0000ffWorld|r")
---@param inputString string
---@return string, integer
function IngameConsole.spreadColorCodes(inputString)
local replacementTable = {} --remembers all substrings to be replaced and their replacements.
for foundInstance, color in inputString:gmatch("((|c\x25x\x25x\x25x\x25x\x25x\x25x\x25x\x25x).-|r)") do
replacementTable[foundInstance] = foundInstance:gsub("(\r?\n)", "|r\x251" .. color)
end
return inputString:gsub("((|c\x25x\x25x\x25x\x25x\x25x\x25x\x25x\x25x).-|r)", replacementTable)
end
---Concatenates all inputs to one string, spreads color codes around line breaks and prints each line to the console separately.
---@param colorTheme? '"timestamp"'| '"singleLineInput"' | '"multiLineInput"' | '"result"' | '"keywordInput"' | '"info"' | '"error"' | '"returnValue"' Decides about the color to be applied. Currently accepted: 'timestamp', 'singleLineInput', 'multiLineInput', 'result', nil. (nil equals no colorTheme, i.e. white color)
---@param numIndentations integer Number of '>' chars that shall preceed the output
---@param hideTimestamp boolean Set to false to hide the timestamp column and instead show a "->" symbol.
---@param ... any the things to be printed in the console.
function IngameConsole:out(colorTheme, numIndentations, hideTimestamp, ...)
local inputs = table.pack(...)
for i = 1, inputs.n do
inputs[i] = tostring(inputs[i]) --apply tostring on every input param in preparation for table.concat
end
--Concatenate all inputs (4-space-separated)
local printOutput = table.concat(inputs, ' ', 1, inputs.n)
printOutput = printOutput:find("(\r?\n)") and IngameConsole.spreadColorCodes(printOutput) or printOutput
local substrStart, substrEnd = 1, 1
local numLinebreaks, completePrint = 0, true
repeat
substrEnd = (printOutput:find("(\r?\n)", substrStart) or 0) - 1
numLinebreaks, completePrint = self:lineOut(colorTheme, numIndentations, hideTimestamp, numLinebreaks, printOutput:sub(substrStart, substrEnd))
hideTimestamp = true
substrStart = substrEnd + 2
until substrEnd == -1 or numLinebreaks > self.maxLinebreaks
if substrEnd ~= -1 or not completePrint then
self:lineOut('info', 0, false, 0, "Previous value not entirely printed after exceeding maximum number of linebreaks. Consider adjusting 'IngameConsole.maxLinebreaks'.")
end
self:updateMultiboard()
end
---Prints the given string to the console with the specified colorTheme and the specified number of indentations.
---Only supports one-liners (no \n) due to how multiboards work. Will add linebreaks though, if the one-liner doesn't fit into the given multiboard space.
---@param colorTheme? '"timestamp"'| '"singleLineInput"' | '"multiLineInput"' | '"result"' | '"keywordInput"' | '"info"' | '"error"' | '"returnValue"' Decides about the color to be applied. Currently accepted: 'timestamp', 'singleLineInput', 'multiLineInput', 'result', nil. (nil equals no colorTheme, i.e. white color)
---@param numIndentations integer Number of greater '>' chars that shall preceed the output
---@param hideTimestamp boolean Set to false to hide the timestamp column and instead show a "->" symbol.
---@param numLinebreaks integer
---@param printOutput string the line to be printed in the console.
---@return integer numLinebreaks, boolean hasPrintedEverything returns true, if everything could be printed. Returns false otherwise (can happen for very long strings).
function IngameConsole:lineOut(colorTheme, numIndentations, hideTimestamp, numLinebreaks, printOutput)
--add preceeding greater chars
printOutput = ('>'):rep(numIndentations) .. printOutput
--Print a space instead of the empty string. This allows the console to identify, if the string has already been fully printed (see while-loop below).
if printOutput == '' then
printOutput = ' '
end
--Compute Linebreaks.
local linebreakWidth = ((self.autosize and self.mainColMaxWidth) or self.currentWidth )
local partialOutput = nil
local maxPrintableCharPosition
local printWidth
while string.len(printOutput) > 0 and numLinebreaks <= self.maxLinebreaks do --break, if the input string has reached length 0 OR when the maximum number of linebreaks would be surpassed.
--compute max printable substring (in one multiboard line)
maxPrintableCharPosition, printWidth = IngameConsole.getLinebreakData(printOutput, linebreakWidth - self.linebreakBuffer, self.textLanguage)
--adds timestamp to the first line of any output
if numLinebreaks == 0 then
partialOutput = printOutput:sub(1, numIndentations) .. ((IngameConsole.colors[colorTheme] and "|cff" .. IngameConsole.colors[colorTheme] .. printOutput:sub(numIndentations + 1, maxPrintableCharPosition) .. "|r") or printOutput:sub(numIndentations + 1, maxPrintableCharPosition)) --Colorize the output string, if a color theme was specified. IngameConsole.colors[colorTheme] can be nil.
table.insert(self.outputTimestamps, "|cff" .. IngameConsole.colors['timestamp'] .. ((hideTimestamp and ' ->') or IngameConsole.formatTimerElapsed(TimerGetElapsed(self.timer))) .. "|r")
else
partialOutput = (IngameConsole.colors[colorTheme] and "|cff" .. IngameConsole.colors[colorTheme] .. printOutput:sub(1, maxPrintableCharPosition) .. "|r") or printOutput:sub(1, maxPrintableCharPosition) --Colorize the output string, if a color theme was specified. IngameConsole.colors[colorTheme] can be nil.
table.insert(self.outputTimestamps, ' ..') --need a dummy entry in the timestamp list to make it line-progress with the normal output.
end
numLinebreaks = numLinebreaks + 1
--writes output string and width to the console tables.
table.insert(self.output, partialOutput)
table.insert(self.outputWidths, printWidth + self.linebreakBuffer) --remember the Width of this printed string to adjust the multiboard size in case. 0.5 percent is added to avoid the case, where the multiboard width is too small by a tiny bit, thus not showing some string without spaces.
--compute remaining string to print
printOutput = string.sub(printOutput, maxPrintableCharPosition + 1, -1) --remaining string until the end. Returns empty string, if there is nothing left
end
self.currentLine = #self.output
return numLinebreaks, string.len(printOutput) == 0 --printOutput is the empty string, if and only if everything has been printed
end
---Lets the multiboard show the recently printed lines.
function IngameConsole:updateMultiboard()
local startIndex = math.max(self.currentLine - self.numRows, 0) --to be added to loop counter to get to the index of output table to print
local outputIndex = 0
local maxWidth = 0.
local mbitem
for i = 1, self.numRows do --doesn't include title row (index 0)
outputIndex = i + startIndex
mbitem = MultiboardGetItem(self.multiboard, i, 0)
MultiboardSetItemValue(mbitem, self.outputTimestamps[outputIndex] or '')
MultiboardReleaseItem(mbitem)
mbitem = MultiboardGetItem(self.multiboard, i, 1)
MultiboardSetItemValue(mbitem, self.output[outputIndex] or '')
MultiboardReleaseItem(mbitem)
maxWidth = math.max(maxWidth, self.outputWidths[outputIndex] or 0.) --looping through non-defined widths, so need to coalesce with 0
end
--Adjust Multiboard Width, if necessary.
maxWidth = math.min(math.max(maxWidth, self.mainColMinWidth), self.mainColMaxWidth)
if self.autosize and self.currentWidth ~= maxWidth then
self.currentWidth = maxWidth
for i = 1, self.numRows +1 do
mbitem = MultiboardGetItem(self.multiboard, i-1, 1)
MultiboardSetItemWidth(mbitem, maxWidth)
MultiboardReleaseItem(mbitem)
end
self:showToOwners() --reshow multiboard to update item widths on the frontend
end
end
---Shows the multiboard to all owners (one or all players)
function IngameConsole:showToOwners()
if self.sharedConsole or GetLocalPlayer() == self.player then
MultiboardDisplay(self.multiboard, true)
MultiboardMinimize(self.multiboard, false)
end
end
---Formats the elapsed time as "mm: ss. hh" (h being a hundreds of a sec)
function IngameConsole.formatTimerElapsed(elapsedInSeconds)
return string.format("\x2502d: \x2502.f. \x2502.f", elapsedInSeconds // 60, math.fmod(elapsedInSeconds, 60.) // 1, math.fmod(elapsedInSeconds, 1) * 100)
end
---Computes the max printable substring for a given string and a given linebreakWidth (regarding a single line of console).
---Returns both the substrings last char position and its total width in the multiboard.
---@param stringToPrint string the string supposed to be printed in the multiboard console.
---@param linebreakWidth number the maximum allowed width in one line of the console, before a string must linebreak
---@param textLanguage string 'ger' or 'eng'
---@return integer maxPrintableCharPosition, number printWidth
function IngameConsole.getLinebreakData(stringToPrint, linebreakWidth, textLanguage)
local loopWidth = 0.
local bytecodes = table.pack(string.byte(stringToPrint, 1, -1))
for i = 1, bytecodes.n do
loopWidth = loopWidth + string.charMultiboardWidth(bytecodes[i], textLanguage)
if loopWidth > linebreakWidth then
return i-1, loopWidth - string.charMultiboardWidth(bytecodes[i], textLanguage)
end
end
return bytecodes.n, loopWidth
end
-------------------------
--| Reserved Keywords |--
-------------------------
---Exits the Console
---@param self IngameConsole
function IngameConsole.keywords.exit(self)
DestroyMultiboard(self.multiboard)
DestroyTrigger(self.trigger)
DestroyTimer(self.timer)
IngameConsole.playerConsoles[self.player] = nil
if next(IngameConsole.playerConsoles) == nil then --set print function back to original, when no one has an active console left.
print = IngameConsole.originalPrint
end
end
---Lets the console print to chat
---@param self IngameConsole
function IngameConsole.keywords.printtochat(self)
self.printToConsole = false
self:out('info', 0, false, "The print function will print to the normal chat.")
end
---Lets the console print to itself (default)
---@param self IngameConsole
function IngameConsole.keywords.printtoconsole(self)
self.printToConsole = true
self:out('info', 0, false, "The print function will print to the console.")
end
---Shows the console in case it was hidden by another multiboard before
---@param self IngameConsole
function IngameConsole.keywords.show(self)
self:showToOwners() --might be necessary to do, if another multiboard has shown up and thereby hidden the console.
self:out('info', 0, false, "Console is showing.")
end
---Prints all available reserved keywords plus explanations.
---@param self IngameConsole
function IngameConsole.keywords.help(self)
self:out('info', 0, false, "The Console currently reserves the following keywords:")
self:out('info', 0, false, "'help' shows the text you are currently reading.")
self:out('info', 0, false, "'exit' closes the console.")
self:out('info', 0, false, "'lasttrace' shows the stack trace of the latest error that occured within IngameConsole.")
self:out('info', 0, false, "'share' allows other players to read and write into your console, but also force-closes their own consoles.")
self:out('info', 0, false, "'clear' clears all text from the console.")
self:out('info', 0, false, "'show' shows the console. Sensible to use, when displaced by another multiboard.")
self:out('info', 0, false, "'printtochat' lets Wc3 print text to normal chat again.")
self:out('info', 0, false, "'printtoconsole' lets Wc3 print text to the console (default).")
self:out('info', 0, false, "'autosize on' enables automatic console resize depending on the longest line in the display.")
self:out('info', 0, false, "'autosize off' retains the current console size.")
self:out('info', 0, false, "'textlang eng' will use english text installation font size to compute linebreaks (default).")
self:out('info', 0, false, "'textlang ger' will use german text installation font size to compute linebreaks.")
self:out('info', 0, false, "Preceeding a line with '>' prevents immediate execution, until a line not starting with '>' has been entered.")
end
---Clears the display of the console.
---@param self IngameConsole
function IngameConsole.keywords.clear(self)
self.output = {}
self.outputTimestamps = {}
self.outputWidths = {}
self.currentLine = 0
self:out('keywordInput', 1, false, 'clear') --we print 'clear' again. The keyword was already printed by self:processInput, but cleared immediately after.
end
---Shares the console with other players in the same game.
---@param self IngameConsole
function IngameConsole.keywords.share(self)
for _, console in pairs(IngameConsole.playerConsoles) do
if console ~= self then
IngameConsole.keywords['exit'](console) --share was triggered during console runtime, so there potentially are active consoles of others players that need to exit.
end
end
self:makeShared()
self:showToOwners() --showing it to the other players.
self:out('info', 0,false, "The console of player " .. GetConvertedPlayerId(self.player) .. " is now shared with all players.")
end
---Enables auto-sizing of console (will grow and shrink together with text size)
---@param self IngameConsole
IngameConsole.keywords["autosize on"] = function(self)
self.autosize = true
self:out('info', 0,false, "The console will now change size depending on its content.")
end
---Disables auto-sizing of console
---@param self IngameConsole
IngameConsole.keywords["autosize off"] = function(self)
self.autosize = false
self:out('info', 0,false, "The console will retain the width that it currently has.")
end
---Lets linebreaks be computed by german font size
---@param self IngameConsole
IngameConsole.keywords["textlang ger"] = function(self)
self.textLanguage = 'ger'
self:out('info', 0,false, "Linebreaks will now compute with respect to german text installation font size.")
end
---Lets linebreaks be computed by english font size
---@param self IngameConsole
IngameConsole.keywords["textlang eng"] = function(self)
self.textLanguage = 'eng'
self:out('info', 0,false, "Linebreaks will now compute with respect to english text installation font size.")
end
---Prints the stack trace of the latest error that occured within IngameConsole.
---@param self IngameConsole
IngameConsole.keywords["lasttrace"] = function(self)
self:out('error', 0,false, self.lastTrace)
end
--------------------
--| Main Trigger |--
--------------------
do
--Actions to be executed upon typing -exec
local function execCommand_Actions()
local input = string.sub(GetEventPlayerChatString(),7,-1)
print("Executing input: |cffffff44" .. input .. "|r")
--try preceeding the input by a return statement (preparation for printing below)
local loadedFunc, errorMsg = load("return ".. input)
if not loadedFunc then --if that doesn't produce valid code, try without return statement
loadedFunc, errorMsg = load(input)
end
--execute loaded function in case the string defined a valid function. Otherwise print error.
if errorMsg then
print("|cffff5555Invalid Lua-statement: " .. Debug.getLocalErrorMsg(errorMsg) .. "|r")
else
---@diagnostic disable-next-line: param-type-mismatch
local results = table.pack(Debug.try(loadedFunc))
if results[1] ~= nil or results.n > 1 then
for i = 1, results.n do
results[i] = tostring(results[i])
end
--concatenate all function return values to one colorized string
print("|cff00ffff" .. table.concat(results, ' ', 1, results.n) .. "|r")
end
end
end
local function execCommand_Condition()
return string.sub(GetEventPlayerChatString(), 1, 6) == "-exec "
end
local function startIngameConsole()
--if the triggering player already has a console, show that console and stop executing further actions
if IngameConsole.playerConsoles[GetTriggerPlayer()] then
IngameConsole.playerConsoles[GetTriggerPlayer()]:showToOwners()
return
end
--create Ingame Console object
IngameConsole.playerConsoles[GetTriggerPlayer()] = IngameConsole.create(GetTriggerPlayer())
--overwrite print function
print = function(...)
IngameConsole.originalPrint(...) --the new print function will also print "normally", but clear the text immediately after. This is to add the message to the F12-log.
if IngameConsole.playerConsoles[GetLocalPlayer()] and IngameConsole.playerConsoles[GetLocalPlayer()].printToConsole then
ClearTextMessages() --clear text messages for all players having an active console
end
for player, console in pairs(IngameConsole.playerConsoles) do
if console.printToConsole and (player == console.player) then --player == console.player ensures that the console only prints once, even if the console was shared among all players
console:out(nil, 0, false, ...)
end
end
end
end
---Creates the triggers listening to "-console" and "-exec" chat input.
---Being executed within DebugUtils (MarkGameStart overwrite).
function IngameConsole.createTriggers()
--Exec
local execTrigger = CreateTrigger()
TriggerAddCondition(execTrigger, Condition(execCommand_Condition))
TriggerAddAction(execTrigger, execCommand_Actions)
--Real Console
local consoleTrigger = CreateTrigger()
TriggerAddAction(consoleTrigger, startIngameConsole)
--Events
for i = 0, GetBJMaxPlayers() -1 do
TriggerRegisterPlayerChatEvent(execTrigger, Player(i), "-exec ", false)
TriggerRegisterPlayerChatEvent(consoleTrigger, Player(i), "-console", true)
end
end
end
--[[
used by Ingame Console to determine multiboard size
every unknown char will be treated as having default width (see constants below)
--]]
do
----------------------------
----| String Width API |----
----------------------------
local multiboardCharTable = {} ---@type table -- saves the width in screen percent (on 1920 pixel width resolutions) that each char takes up, when displayed in a multiboard.
local DEFAULT_MULTIBOARD_CHAR_WIDTH = 1. / 128. ---@type number -- used for unknown chars (where we didn't define a width in the char table)
local MULTIBOARD_TO_PRINT_FACTOR = 1. / 36. ---@type number -- 36 is actually the lower border (longest width of a non-breaking string only consisting of the letter "i")
---Returns the width of a char in a multiboard, when inputting a char (string of length 1) and 0 otherwise.
---also returns 0 for non-recorded chars (like ` and ´ and ß and § and €)
---@param char string | integer integer bytecode representations of chars are also allowed, i.e. the results of string.byte().
---@param textlanguage? '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@return number
function string.charMultiboardWidth(char, textlanguage)
return multiboardCharTable[textlanguage or 'eng'][char] or DEFAULT_MULTIBOARD_CHAR_WIDTH
end
---returns the width of a string in a multiboard (i.e. output is in screen percent)
---unknown chars will be measured with default width (see constants above)
---@param multichar string
---@param textlanguage? '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@return number
function string.multiboardWidth(multichar, textlanguage)
local chartable = table.pack(multichar:byte(1,-1)) --packs all bytecode char representations into a table
local charWidth = 0.
for i = 1, chartable.n do
charWidth = charWidth + string.charMultiboardWidth(chartable[i], textlanguage)
end
return charWidth
end
---The function should match the following criteria: If the value returned by this function is smaller than 1.0, than the string fits into a single line on screen.
---The opposite is not necessarily true (but should be true in the majority of cases): If the function returns bigger than 1.0, the string doesn't necessarily break.
---@param char string | integer integer bytecode representations of chars are also allowed, i.e. the results of string.byte().
---@param textlanguage? '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@return number
function string.charPrintWidth(char, textlanguage)
return string.charMultiboardWidth(char, textlanguage) * MULTIBOARD_TO_PRINT_FACTOR
end
---The function should match the following criteria: If the value returned by this function is smaller than 1.0, than the string fits into a single line on screen.
---The opposite is not necessarily true (but should be true in the majority of cases): If the function returns bigger than 1.0, the string doesn't necessarily break.
---@param multichar string
---@param textlanguage? '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@return number
function string.printWidth(multichar, textlanguage)
return string.multiboardWidth(multichar, textlanguage) * MULTIBOARD_TO_PRINT_FACTOR
end
----------------------------------
----| String Width Internals |----
----------------------------------
---@param charset '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@param char string|integer either the char or its bytecode
---@param lengthInScreenWidth number
local function setMultiboardCharWidth(charset, char, lengthInScreenWidth)
multiboardCharTable[charset] = multiboardCharTable[charset] or {}
multiboardCharTable[charset][char] = lengthInScreenWidth
end
---numberPlacements says how often the char can be placed in a multiboard column, before reaching into the right bound.
---@param charset '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@param char string|integer either the char or its bytecode
---@param numberPlacements integer
local function setMultiboardCharWidthBase80(charset, char, numberPlacements)
setMultiboardCharWidth(charset, char, 0.8 / numberPlacements) --1-based measure. 80./numberPlacements would result in Screen Percent.
setMultiboardCharWidth(charset, char:byte(1,-1), 0.8 / numberPlacements)
end
-- Set Char Width for all printable ascii chars in screen width (1920 pixels). Measured on a 80percent screen width multiboard column by counting the number of chars that fit into it.
-- Font size differs by text install language and patch (1.32- vs. 1.33+)
if BlzGetUnitOrderCount then --identifies patch 1.33+
--German font size for patch 1.33+
setMultiboardCharWidthBase80('ger', "a", 144)
setMultiboardCharWidthBase80('ger', "b", 131)
setMultiboardCharWidthBase80('ger', "c", 144)
setMultiboardCharWidthBase80('ger', "d", 120)
setMultiboardCharWidthBase80('ger', "e", 131)
setMultiboardCharWidthBase80('ger', "f", 240)
setMultiboardCharWidthBase80('ger', "g", 120)
setMultiboardCharWidthBase80('ger', "h", 131)
setMultiboardCharWidthBase80('ger', "i", 288)
setMultiboardCharWidthBase80('ger', "j", 288)
setMultiboardCharWidthBase80('ger', "k", 144)
setMultiboardCharWidthBase80('ger', "l", 288)
setMultiboardCharWidthBase80('ger', "m", 85)
setMultiboardCharWidthBase80('ger', "n", 131)
setMultiboardCharWidthBase80('ger', "o", 120)
setMultiboardCharWidthBase80('ger', "p", 120)
setMultiboardCharWidthBase80('ger', "q", 120)
setMultiboardCharWidthBase80('ger', "r", 206)
setMultiboardCharWidthBase80('ger', "s", 160)
setMultiboardCharWidthBase80('ger', "t", 206)
setMultiboardCharWidthBase80('ger', "u", 131)
setMultiboardCharWidthBase80('ger', "v", 131)
setMultiboardCharWidthBase80('ger', "w", 96)
setMultiboardCharWidthBase80('ger', "x", 144)
setMultiboardCharWidthBase80('ger', "y", 131)
setMultiboardCharWidthBase80('ger', "z", 144)
setMultiboardCharWidthBase80('ger', "A", 103)
setMultiboardCharWidthBase80('ger', "B", 120)
setMultiboardCharWidthBase80('ger', "C", 111)
setMultiboardCharWidthBase80('ger', "D", 103)
setMultiboardCharWidthBase80('ger', "E", 144)
setMultiboardCharWidthBase80('ger', "F", 160)
setMultiboardCharWidthBase80('ger', "G", 96)
setMultiboardCharWidthBase80('ger', "H", 96)
setMultiboardCharWidthBase80('ger', "I", 240)
setMultiboardCharWidthBase80('ger', "J", 240)
setMultiboardCharWidthBase80('ger', "K", 120)
setMultiboardCharWidthBase80('ger', "L", 144)
setMultiboardCharWidthBase80('ger', "M", 76)
setMultiboardCharWidthBase80('ger', "N", 96)
setMultiboardCharWidthBase80('ger', "O", 90)
setMultiboardCharWidthBase80('ger', "P", 131)
setMultiboardCharWidthBase80('ger', "Q", 90)
setMultiboardCharWidthBase80('ger', "R", 120)
setMultiboardCharWidthBase80('ger', "S", 131)
setMultiboardCharWidthBase80('ger', "T", 144)
setMultiboardCharWidthBase80('ger', "U", 103)
setMultiboardCharWidthBase80('ger', "V", 120)
setMultiboardCharWidthBase80('ger', "W", 76)
setMultiboardCharWidthBase80('ger', "X", 111)
setMultiboardCharWidthBase80('ger', "Y", 120)
setMultiboardCharWidthBase80('ger', "Z", 120)
setMultiboardCharWidthBase80('ger', "1", 144)
setMultiboardCharWidthBase80('ger', "2", 120)
setMultiboardCharWidthBase80('ger', "3", 120)
setMultiboardCharWidthBase80('ger', "4", 120)
setMultiboardCharWidthBase80('ger', "5", 120)
setMultiboardCharWidthBase80('ger', "6", 120)
setMultiboardCharWidthBase80('ger', "7", 131)
setMultiboardCharWidthBase80('ger', "8", 120)
setMultiboardCharWidthBase80('ger', "9", 120)
setMultiboardCharWidthBase80('ger', "0", 120)
setMultiboardCharWidthBase80('ger', ":", 288)
setMultiboardCharWidthBase80('ger', ";", 288)
setMultiboardCharWidthBase80('ger', ".", 288)
setMultiboardCharWidthBase80('ger', "#", 120)
setMultiboardCharWidthBase80('ger', ",", 288)
setMultiboardCharWidthBase80('ger', " ", 286) --space
setMultiboardCharWidthBase80('ger', "'", 180)
setMultiboardCharWidthBase80('ger', "!", 180)
setMultiboardCharWidthBase80('ger', "$", 131)
setMultiboardCharWidthBase80('ger', "&", 90)
setMultiboardCharWidthBase80('ger', "/", 180)
setMultiboardCharWidthBase80('ger', "(", 240)
setMultiboardCharWidthBase80('ger', ")", 240)
setMultiboardCharWidthBase80('ger', "=", 120)
setMultiboardCharWidthBase80('ger', "?", 144)
setMultiboardCharWidthBase80('ger', "^", 144)
setMultiboardCharWidthBase80('ger', "<", 144)
setMultiboardCharWidthBase80('ger', ">", 144)
setMultiboardCharWidthBase80('ger', "-", 180)
setMultiboardCharWidthBase80('ger', "+", 120)
setMultiboardCharWidthBase80('ger', "*", 180)
setMultiboardCharWidthBase80('ger', "|", 287) --2 vertical bars in a row escape to one. So you could print 960 ones in a line, 480 would display. Maybe need to adapt to this before calculating string width.
setMultiboardCharWidthBase80('ger', "~", 111)
setMultiboardCharWidthBase80('ger', "{", 240)
setMultiboardCharWidthBase80('ger', "}", 240)
setMultiboardCharWidthBase80('ger', "[", 240)
setMultiboardCharWidthBase80('ger', "]", 240)
setMultiboardCharWidthBase80('ger', "_", 144)
setMultiboardCharWidthBase80('ger', "\x25", 103) --percent
setMultiboardCharWidthBase80('ger', "\x5C", 205) --backslash
setMultiboardCharWidthBase80('ger', "\x22", 120) --double quotation mark
setMultiboardCharWidthBase80('ger', "\x40", 90) --at sign
setMultiboardCharWidthBase80('ger', "\x60", 144) --Gravis (Accent)
--English font size for patch 1.33+
setMultiboardCharWidthBase80('eng', "a", 144)
setMultiboardCharWidthBase80('eng', "b", 120)
setMultiboardCharWidthBase80('eng', "c", 131)
setMultiboardCharWidthBase80('eng', "d", 120)
setMultiboardCharWidthBase80('eng', "e", 120)
setMultiboardCharWidthBase80('eng', "f", 240)
setMultiboardCharWidthBase80('eng', "g", 120)
setMultiboardCharWidthBase80('eng', "h", 120)
setMultiboardCharWidthBase80('eng', "i", 288)
setMultiboardCharWidthBase80('eng', "j", 288)
setMultiboardCharWidthBase80('eng', "k", 144)
setMultiboardCharWidthBase80('eng', "l", 288)
setMultiboardCharWidthBase80('eng', "m", 80)
setMultiboardCharWidthBase80('eng', "n", 120)
setMultiboardCharWidthBase80('eng', "o", 111)
setMultiboardCharWidthBase80('eng', "p", 111)
setMultiboardCharWidthBase80('eng', "q", 111)
setMultiboardCharWidthBase80('eng', "r", 206)
setMultiboardCharWidthBase80('eng', "s", 160)
setMultiboardCharWidthBase80('eng', "t", 206)
setMultiboardCharWidthBase80('eng', "u", 120)
setMultiboardCharWidthBase80('eng', "v", 144)
setMultiboardCharWidthBase80('eng', "w", 90)
setMultiboardCharWidthBase80('eng', "x", 131)
setMultiboardCharWidthBase80('eng', "y", 144)
setMultiboardCharWidthBase80('eng', "z", 144)
setMultiboardCharWidthBase80('eng', "A", 103)
setMultiboardCharWidthBase80('eng', "B", 120)
setMultiboardCharWidthBase80('eng', "C", 103)
setMultiboardCharWidthBase80('eng', "D", 96)
setMultiboardCharWidthBase80('eng', "E", 131)
setMultiboardCharWidthBase80('eng', "F", 160)
setMultiboardCharWidthBase80('eng', "G", 96)
setMultiboardCharWidthBase80('eng', "H", 90)
setMultiboardCharWidthBase80('eng', "I", 240)
setMultiboardCharWidthBase80('eng', "J", 240)
setMultiboardCharWidthBase80('eng', "K", 120)
setMultiboardCharWidthBase80('eng', "L", 131)
setMultiboardCharWidthBase80('eng', "M", 76)
setMultiboardCharWidthBase80('eng', "N", 90)
setMultiboardCharWidthBase80('eng', "O", 85)
setMultiboardCharWidthBase80('eng', "P", 120)
setMultiboardCharWidthBase80('eng', "Q", 85)
setMultiboardCharWidthBase80('eng', "R", 120)
setMultiboardCharWidthBase80('eng', "S", 131)
setMultiboardCharWidthBase80('eng', "T", 144)
setMultiboardCharWidthBase80('eng', "U", 96)
setMultiboardCharWidthBase80('eng', "V", 120)
setMultiboardCharWidthBase80('eng', "W", 76)
setMultiboardCharWidthBase80('eng', "X", 111)
setMultiboardCharWidthBase80('eng', "Y", 120)
setMultiboardCharWidthBase80('eng', "Z", 111)
setMultiboardCharWidthBase80('eng', "1", 103)
setMultiboardCharWidthBase80('eng', "2", 111)
setMultiboardCharWidthBase80('eng', "3", 111)
setMultiboardCharWidthBase80('eng', "4", 111)
setMultiboardCharWidthBase80('eng', "5", 111)
setMultiboardCharWidthBase80('eng', "6", 111)
setMultiboardCharWidthBase80('eng', "7", 111)
setMultiboardCharWidthBase80('eng', "8", 111)
setMultiboardCharWidthBase80('eng', "9", 111)
setMultiboardCharWidthBase80('eng', "0", 111)
setMultiboardCharWidthBase80('eng', ":", 288)
setMultiboardCharWidthBase80('eng', ";", 288)
setMultiboardCharWidthBase80('eng', ".", 288)
setMultiboardCharWidthBase80('eng', "#", 103)
setMultiboardCharWidthBase80('eng', ",", 288)
setMultiboardCharWidthBase80('eng', " ", 286) --space
setMultiboardCharWidthBase80('eng', "'", 360)
setMultiboardCharWidthBase80('eng', "!", 288)
setMultiboardCharWidthBase80('eng', "$", 131)
setMultiboardCharWidthBase80('eng', "&", 120)
setMultiboardCharWidthBase80('eng', "/", 180)
setMultiboardCharWidthBase80('eng', "(", 206)
setMultiboardCharWidthBase80('eng', ")", 206)
setMultiboardCharWidthBase80('eng', "=", 111)
setMultiboardCharWidthBase80('eng', "?", 180)
setMultiboardCharWidthBase80('eng', "^", 144)
setMultiboardCharWidthBase80('eng', "<", 111)
setMultiboardCharWidthBase80('eng', ">", 111)
setMultiboardCharWidthBase80('eng', "-", 160)
setMultiboardCharWidthBase80('eng', "+", 111)
setMultiboardCharWidthBase80('eng', "*", 144)
setMultiboardCharWidthBase80('eng', "|", 479) --2 vertical bars in a row escape to one. So you could print 960 ones in a line, 480 would display. Maybe need to adapt to this before calculating string width.
setMultiboardCharWidthBase80('eng', "~", 144)
setMultiboardCharWidthBase80('eng', "{", 160)
setMultiboardCharWidthBase80('eng', "}", 160)
setMultiboardCharWidthBase80('eng', "[", 206)
setMultiboardCharWidthBase80('eng', "]", 206)
setMultiboardCharWidthBase80('eng', "_", 120)
setMultiboardCharWidthBase80('eng', "\x25", 103) --percent
setMultiboardCharWidthBase80('eng', "\x5C", 180) --backslash
setMultiboardCharWidthBase80('eng', "\x22", 180) --double quotation mark
setMultiboardCharWidthBase80('eng', "\x40", 85) --at sign
setMultiboardCharWidthBase80('eng', "\x60", 206) --Gravis (Accent)
else
--German font size up to patch 1.32
setMultiboardCharWidthBase80('ger', "a", 144)
setMultiboardCharWidthBase80('ger', "b", 144)
setMultiboardCharWidthBase80('ger', "c", 144)
setMultiboardCharWidthBase80('ger', "d", 131)
setMultiboardCharWidthBase80('ger', "e", 144)
setMultiboardCharWidthBase80('ger', "f", 240)
setMultiboardCharWidthBase80('ger', "g", 120)
setMultiboardCharWidthBase80('ger', "h", 144)
setMultiboardCharWidthBase80('ger', "i", 360)
setMultiboardCharWidthBase80('ger', "j", 288)
setMultiboardCharWidthBase80('ger', "k", 144)
setMultiboardCharWidthBase80('ger', "l", 360)
setMultiboardCharWidthBase80('ger', "m", 90)
setMultiboardCharWidthBase80('ger', "n", 144)
setMultiboardCharWidthBase80('ger', "o", 131)
setMultiboardCharWidthBase80('ger', "p", 131)
setMultiboardCharWidthBase80('ger', "q", 131)
setMultiboardCharWidthBase80('ger', "r", 206)
setMultiboardCharWidthBase80('ger', "s", 180)
setMultiboardCharWidthBase80('ger', "t", 206)
setMultiboardCharWidthBase80('ger', "u", 144)
setMultiboardCharWidthBase80('ger', "v", 131)
setMultiboardCharWidthBase80('ger', "w", 96)
setMultiboardCharWidthBase80('ger', "x", 144)
setMultiboardCharWidthBase80('ger', "y", 131)
setMultiboardCharWidthBase80('ger', "z", 144)
setMultiboardCharWidthBase80('ger', "A", 103)
setMultiboardCharWidthBase80('ger', "B", 131)
setMultiboardCharWidthBase80('ger', "C", 120)
setMultiboardCharWidthBase80('ger', "D", 111)
setMultiboardCharWidthBase80('ger', "E", 144)
setMultiboardCharWidthBase80('ger', "F", 180)
setMultiboardCharWidthBase80('ger', "G", 103)
setMultiboardCharWidthBase80('ger', "H", 103)
setMultiboardCharWidthBase80('ger', "I", 288)
setMultiboardCharWidthBase80('ger', "J", 240)
setMultiboardCharWidthBase80('ger', "K", 120)
setMultiboardCharWidthBase80('ger', "L", 144)
setMultiboardCharWidthBase80('ger', "M", 80)
setMultiboardCharWidthBase80('ger', "N", 103)
setMultiboardCharWidthBase80('ger', "O", 96)
setMultiboardCharWidthBase80('ger', "P", 144)
setMultiboardCharWidthBase80('ger', "Q", 90)
setMultiboardCharWidthBase80('ger', "R", 120)
setMultiboardCharWidthBase80('ger', "S", 144)
setMultiboardCharWidthBase80('ger', "T", 144)
setMultiboardCharWidthBase80('ger', "U", 111)
setMultiboardCharWidthBase80('ger', "V", 120)
setMultiboardCharWidthBase80('ger', "W", 76)
setMultiboardCharWidthBase80('ger', "X", 111)
setMultiboardCharWidthBase80('ger', "Y", 120)
setMultiboardCharWidthBase80('ger', "Z", 120)
setMultiboardCharWidthBase80('ger', "1", 288)
setMultiboardCharWidthBase80('ger', "2", 131)
setMultiboardCharWidthBase80('ger', "3", 144)
setMultiboardCharWidthBase80('ger', "4", 120)
setMultiboardCharWidthBase80('ger', "5", 144)
setMultiboardCharWidthBase80('ger', "6", 131)
setMultiboardCharWidthBase80('ger', "7", 144)
setMultiboardCharWidthBase80('ger', "8", 131)
setMultiboardCharWidthBase80('ger', "9", 131)
setMultiboardCharWidthBase80('ger', "0", 131)
setMultiboardCharWidthBase80('ger', ":", 480)
setMultiboardCharWidthBase80('ger', ";", 360)
setMultiboardCharWidthBase80('ger', ".", 480)
setMultiboardCharWidthBase80('ger', "#", 120)
setMultiboardCharWidthBase80('ger', ",", 360)
setMultiboardCharWidthBase80('ger', " ", 288) --space
setMultiboardCharWidthBase80('ger', "'", 480)
setMultiboardCharWidthBase80('ger', "!", 360)
setMultiboardCharWidthBase80('ger', "$", 160)
setMultiboardCharWidthBase80('ger', "&", 96)
setMultiboardCharWidthBase80('ger', "/", 180)
setMultiboardCharWidthBase80('ger', "(", 288)
setMultiboardCharWidthBase80('ger', ")", 288)
setMultiboardCharWidthBase80('ger', "=", 160)
setMultiboardCharWidthBase80('ger', "?", 180)
setMultiboardCharWidthBase80('ger', "^", 144)
setMultiboardCharWidthBase80('ger', "<", 160)
setMultiboardCharWidthBase80('ger', ">", 160)
setMultiboardCharWidthBase80('ger', "-", 144)
setMultiboardCharWidthBase80('ger', "+", 160)
setMultiboardCharWidthBase80('ger', "*", 206)
setMultiboardCharWidthBase80('ger', "|", 480) --2 vertical bars in a row escape to one. So you could print 960 ones in a line, 480 would display. Maybe need to adapt to this before calculating string width.
setMultiboardCharWidthBase80('ger', "~", 144)
setMultiboardCharWidthBase80('ger', "{", 240)
setMultiboardCharWidthBase80('ger', "}", 240)
setMultiboardCharWidthBase80('ger', "[", 240)
setMultiboardCharWidthBase80('ger', "]", 288)
setMultiboardCharWidthBase80('ger', "_", 144)
setMultiboardCharWidthBase80('ger', "\x25", 111) --percent
setMultiboardCharWidthBase80('ger', "\x5C", 206) --backslash
setMultiboardCharWidthBase80('ger', "\x22", 240) --double quotation mark
setMultiboardCharWidthBase80('ger', "\x40", 103) --at sign
setMultiboardCharWidthBase80('ger', "\x60", 240) --Gravis (Accent)
--English Font size up to patch 1.32
setMultiboardCharWidthBase80('eng', "a", 144)
setMultiboardCharWidthBase80('eng', "b", 120)
setMultiboardCharWidthBase80('eng', "c", 131)
setMultiboardCharWidthBase80('eng', "d", 120)
setMultiboardCharWidthBase80('eng', "e", 131)
setMultiboardCharWidthBase80('eng', "f", 240)
setMultiboardCharWidthBase80('eng', "g", 120)
setMultiboardCharWidthBase80('eng', "h", 131)
setMultiboardCharWidthBase80('eng', "i", 360)
setMultiboardCharWidthBase80('eng', "j", 288)
setMultiboardCharWidthBase80('eng', "k", 144)
setMultiboardCharWidthBase80('eng', "l", 360)
setMultiboardCharWidthBase80('eng', "m", 80)
setMultiboardCharWidthBase80('eng', "n", 131)
setMultiboardCharWidthBase80('eng', "o", 120)
setMultiboardCharWidthBase80('eng', "p", 120)
setMultiboardCharWidthBase80('eng', "q", 120)
setMultiboardCharWidthBase80('eng', "r", 206)
setMultiboardCharWidthBase80('eng', "s", 160)
setMultiboardCharWidthBase80('eng', "t", 206)
setMultiboardCharWidthBase80('eng', "u", 131)
setMultiboardCharWidthBase80('eng', "v", 144)
setMultiboardCharWidthBase80('eng', "w", 90)
setMultiboardCharWidthBase80('eng', "x", 131)
setMultiboardCharWidthBase80('eng', "y", 144)
setMultiboardCharWidthBase80('eng', "z", 144)
setMultiboardCharWidthBase80('eng', "A", 103)
setMultiboardCharWidthBase80('eng', "B", 120)
setMultiboardCharWidthBase80('eng', "C", 103)
setMultiboardCharWidthBase80('eng', "D", 103)
setMultiboardCharWidthBase80('eng', "E", 131)
setMultiboardCharWidthBase80('eng', "F", 160)
setMultiboardCharWidthBase80('eng', "G", 103)
setMultiboardCharWidthBase80('eng', "H", 96)
setMultiboardCharWidthBase80('eng', "I", 288)
setMultiboardCharWidthBase80('eng', "J", 240)
setMultiboardCharWidthBase80('eng', "K", 120)
setMultiboardCharWidthBase80('eng', "L", 131)
setMultiboardCharWidthBase80('eng', "M", 76)
setMultiboardCharWidthBase80('eng', "N", 96)
setMultiboardCharWidthBase80('eng', "O", 85)
setMultiboardCharWidthBase80('eng', "P", 131)
setMultiboardCharWidthBase80('eng', "Q", 85)
setMultiboardCharWidthBase80('eng', "R", 120)
setMultiboardCharWidthBase80('eng', "S", 131)
setMultiboardCharWidthBase80('eng', "T", 144)
setMultiboardCharWidthBase80('eng', "U", 103)
setMultiboardCharWidthBase80('eng', "V", 120)
setMultiboardCharWidthBase80('eng', "W", 76)
setMultiboardCharWidthBase80('eng', "X", 111)
setMultiboardCharWidthBase80('eng', "Y", 120)
setMultiboardCharWidthBase80('eng', "Z", 111)
setMultiboardCharWidthBase80('eng', "1", 206)
setMultiboardCharWidthBase80('eng', "2", 131)
setMultiboardCharWidthBase80('eng', "3", 131)
setMultiboardCharWidthBase80('eng', "4", 111)
setMultiboardCharWidthBase80('eng', "5", 131)
setMultiboardCharWidthBase80('eng', "6", 120)
setMultiboardCharWidthBase80('eng', "7", 131)
setMultiboardCharWidthBase80('eng', "8", 111)
setMultiboardCharWidthBase80('eng', "9", 120)
setMultiboardCharWidthBase80('eng', "0", 111)
setMultiboardCharWidthBase80('eng', ":", 360)
setMultiboardCharWidthBase80('eng', ";", 360)
setMultiboardCharWidthBase80('eng', ".", 360)
setMultiboardCharWidthBase80('eng', "#", 103)
setMultiboardCharWidthBase80('eng', ",", 360)
setMultiboardCharWidthBase80('eng', " ", 288) --space
setMultiboardCharWidthBase80('eng', "'", 480)
setMultiboardCharWidthBase80('eng', "!", 360)
setMultiboardCharWidthBase80('eng', "$", 131)
setMultiboardCharWidthBase80('eng', "&", 120)
setMultiboardCharWidthBase80('eng', "/", 180)
setMultiboardCharWidthBase80('eng', "(", 240)
setMultiboardCharWidthBase80('eng', ")", 240)
setMultiboardCharWidthBase80('eng', "=", 111)
setMultiboardCharWidthBase80('eng', "?", 180)
setMultiboardCharWidthBase80('eng', "^", 144)
setMultiboardCharWidthBase80('eng', "<", 131)
setMultiboardCharWidthBase80('eng', ">", 131)
setMultiboardCharWidthBase80('eng', "-", 180)
setMultiboardCharWidthBase80('eng', "+", 111)
setMultiboardCharWidthBase80('eng', "*", 180)
setMultiboardCharWidthBase80('eng', "|", 480) --2 vertical bars in a row escape to one. So you could print 960 ones in a line, 480 would display. Maybe need to adapt to this before calculating string width.
setMultiboardCharWidthBase80('eng', "~", 144)
setMultiboardCharWidthBase80('eng', "{", 240)
setMultiboardCharWidthBase80('eng', "}", 240)
setMultiboardCharWidthBase80('eng', "[", 240)
setMultiboardCharWidthBase80('eng', "]", 240)
setMultiboardCharWidthBase80('eng', "_", 120)
setMultiboardCharWidthBase80('eng', "\x25", 103) --percent
setMultiboardCharWidthBase80('eng', "\x5C", 180) --backslash
setMultiboardCharWidthBase80('eng', "\x22", 206) --double quotation mark
setMultiboardCharWidthBase80('eng', "\x40", 96) --at sign
setMultiboardCharWidthBase80('eng', "\x60", 206) --Gravis (Accent)
end
end
if Debug and Debug.endFile then Debug.endFile() end
if Debug then Debug.beginFile 'TotalInitialization' end
--[[——————————————————————————————————————————————————————
Total Initialization version 5.3.1
Created by: Bribe
Contributors: Eikonium, HerlySQR, Tasyen, Luashine, Forsakn
Inspiration: Almia, ScorpioT1000, Troll-Brain
Hosted at: https://github.com/BribeFromTheHive/Lua/blob/master/TotalInitialization.lua
Debug library hosted at: https://www.hiveworkshop.com/threads/debug-utils-ingame-console-etc.330758/
————————————————————————————————————————————————————————————]]
---Calls the user's initialization function during the map's loading process. The first argument should either be the init function,
---or it should be the string to give the initializer a name (works similarly to a module name/identically to a vJass library name).
---
---To use requirements, call `Require.strict 'LibraryName'` or `Require.optional 'LibraryName'`. Alternatively, the OnInit callback
---function can take the `Require` table as a single parameter: `OnInit(function(import) import.strict 'ThisIsTheSameAsRequire' end)`.
---
-- - `OnInit.global` or just `OnInit` is called after InitGlobals and is the standard point to initialize.
-- - `OnInit.trig` is called after InitCustomTriggers, and is useful for removing hooks that should only apply to GUI events.
-- - `OnInit.map` is the last point in initialization before the loading screen is completed.
-- - `OnInit.final` occurs immediately after the loading screen has disappeared, and the game has started.
---@class OnInit
--
--Simple Initialization without declaring a library name:
---@overload async fun(initCallback: Initializer.Callback)
--
--Advanced initialization with a library name and an optional third argument to signal to Eikonium's DebugUtils that the file has ended.
---@overload async fun(libraryName: string, initCallback: Initializer.Callback, debugLineNum?: integer)
--
--A way to yield your library to allow other libraries in the same initialization sequence to load, then resume once they have loaded.
---@overload async fun(customInitializerName: string)
OnInit = {}
---@alias Initializer.Callback fun(require?: Requirement | {[string]: Requirement}):...?
---@alias Requirement async fun(reqName: string, source?: table): unknown
-- `Require` will yield the calling `OnInit` initialization function until the requirement (referenced as a string) exists. It will check the
-- global API (for example, does 'GlobalRemap' exist) and then check for any named OnInit resources which might use that same string as its name.
--
-- Due to the way Sumneko's syntax highlighter works, the return value will only be linted for defined @class objects (and doesn't work for regular
-- globals like `TimerStart`). I tried to request the functionality here: https://github.com/sumneko/lua-language-server/issues/1792 , however it
-- was closed. Presumably, there are other requests asking for it, but I wouldn't count on it.
--
-- To declare a requirement, use: `Require.strict 'SomeLibrary'` or (if you don't care about the missing linting functionality) `Require 'SomeLibrary'`
--
-- To optionally require something, use any other suffix (such as `.optionally` or `.nonstrict`): `Require.optional 'SomeLibrary'`
--
---@class Require: { [string]: Requirement }
---@overload async fun(reqName: string, source?: table): string
Require = {}
do
local library = {} --You can change this to false if you don't use `Require` nor the `OnInit.library` API.
--CONFIGURABLE LEGACY API FUNCTION:
---@param _ENV table
---@param OnInit any
local function assignLegacyAPI(_ENV, OnInit)
OnGlobalInit = OnInit; OnTrigInit = OnInit.trig; OnMapInit = OnInit.map; OnGameStart = OnInit.final --Global Initialization Lite API
--OnMainInit = OnInit.main; OnLibraryInit = OnInit.library; OnGameInit = OnInit.final --short-lived experimental API
--onGlobalInit = OnInit; onTriggerInit = OnInit.trig; onInitialization = OnInit.map; onGameStart = OnInit.final --original Global Initialization API
--OnTriggerInit = OnInit.trig; OnInitialization = OnInit.map --Forsakn's Ordered Indices API
end
--END CONFIGURABLES
local _G, rawget, insert =
_G, rawget, table.insert
local initFuncQueue = {} ---@type table<integer, fun(require: Require)>
---@param libraryName string | Initializer.Callback
---@param func? Initializer.Callback
---@param debugLineNum? integer
local function callUserFunc(libraryName, func, debugLineNum)
if not func then
---@cast libraryName Initializer.Callback
func = libraryName
else
assert(type(libraryName) == 'string')
if debugLineNum and Debug then
Debug.beginFile(libraryName, 2)
Debug.data.sourceMap[#Debug.data.sourceMap].lastLine = debugLineNum
end
if library then
func = library:create(libraryName, func)
end
end
assert(type(func) == 'function')
--print('adding user func: ' , initName , libraryName, debugLineNum, incDebugLevel)
coroutine.wrap(func)(Require)
if library then
library:resume()
end
end
local initKeyNames = {
root = 'root',
config = 'config',
main = 'main',
['InitGlobals'] = 'global',
['InitCustomTriggers'] = 'trig',
['RunInitializationTriggers'] = 'map',
['MarkGameStarted'] = 'final'
}
---@param name string
---@param continue? function
local function runInitializers(name, continue)
--print('running:', name, tostring(initFuncQueue[name]))
if name ~= 'module' and name ~= 'library' then
OnInit[initKeyNames[name]] = callUserFunc
end
if initFuncQueue[name] then
for _,func in ipairs(initFuncQueue[name]) do
coroutine.wrap(func)(Require)
end
initFuncQueue[name] = nil
end
if library then
library:resume()
end
if continue then
continue()
end
end
local function initEverything()
---@param hookName string
---@param continue? function
local function hook(hookName, continue)
local hookedFunc = rawget(_G, hookName)
if hookedFunc then
rawset(_G, hookName,
function()
hookedFunc()
runInitializers(hookName, continue)
end
)
else
runInitializers(hookName, continue)
end
end
hook(
'InitGlobals',
function()
hook(
'InitCustomTriggers',
function()
hook('RunInitializationTriggers')
end
)
end
)
hook(
'MarkGameStarted',
function()
if library then
for _,func in ipairs(library.queuedInitializerList) do
func(nil, true) --run errors for missing requirements.
end
for _,func in pairs(library.yieldedModuleMatrix) do
func(true) --run errors for modules that aren't required.
end
end
OnInit = nil
Require = nil
end
)
end
---@param initName string
---@param libraryName string | Initializer.Callback
---@param func? Initializer.Callback
---@param debugLineNum? integer
---@param incDebugLevel? boolean
local function addUserFunc(initName, libraryName, func, debugLineNum, incDebugLevel)
if not func then
---@cast libraryName Initializer.Callback
func = libraryName
else
assert(type(libraryName) == 'string')
if debugLineNum and Debug then
Debug.beginFile(libraryName, incDebugLevel and 3 or 2)
Debug.data.sourceMap[#Debug.data.sourceMap].lastLine = debugLineNum
end
if library then
func = library:create(libraryName, func)
end
end
assert(type(func) == 'function')
--print('adding user func: ' , initName , libraryName, debugLineNum, incDebugLevel)
initFuncQueue[initName] = initFuncQueue[initName] or {}
insert(initFuncQueue[initName], func)
if initName == 'root' or initName == 'module' then
runInitializers(initName)
end
end
---@param name string
local function createInit(name)
---@async
---@param libraryName string --Assign your callback a unique name, allowing other OnInit callbacks can use it as a requirement.
---@param userInitFunc Initializer.Callback --Define a function to be called at the chosen point in the initialization process. It can optionally take the `Require` object as a parameter. Its optional return value(s) are passed to a requiring library via the `Require` object (defaults to `true`).
---@param debugLineNum? integer --If the Debug library is present, you can call Debug.getLine() for this parameter (which should coincide with the last line of your script file). This will neatly tie-in with OnInit's built-in Debug library functionality to define a starting line and an ending line for your module.
---@overload async fun(userInitFunc: Initializer.Callback)
return function(libraryName, userInitFunc, debugLineNum)
addUserFunc(name, libraryName, userInitFunc, debugLineNum)
end
end
OnInit.global = createInit 'InitGlobals' -- Called after InitGlobals, and is the standard point to initialize.
OnInit.trig = createInit 'InitCustomTriggers' -- Called after InitCustomTriggers, and is useful for removing hooks that should only apply to GUI events.
OnInit.map = createInit 'RunInitializationTriggers' -- Called last in the script's loading screen sequence. Runs after the GUI "Map Initialization" events have run.
OnInit.final = createInit 'MarkGameStarted' -- Called immediately after the loading screen has disappeared, and the game has started.
do
---@param self table
---@param libraryNameOrInitFunc function | string
---@param userInitFunc function
---@param debugLineNum number
local function __call(
self,
libraryNameOrInitFunc,
userInitFunc,
debugLineNum
)
if userInitFunc or type(libraryNameOrInitFunc) == 'function' then
addUserFunc(
'InitGlobals', --Calling OnInit directly defaults to OnInit.global (AKA OnGlobalInit)
libraryNameOrInitFunc,
userInitFunc,
debugLineNum,
true
)
elseif library then
library:declare(libraryNameOrInitFunc) --API handler for OnInit "Custom initializer"
else
error(
"Bad OnInit args: "..
tostring(libraryNameOrInitFunc) .. ", " ..
tostring(userInitFunc)
)
end
end
setmetatable(OnInit --[[@as table]], { __call = __call })
end
do --if you don't need the initializers for 'root', 'config' and 'main', you can delete this do...end block.
local gmt = getmetatable(_G) or
getmetatable(setmetatable(_G, {}))
local rawIndex = gmt.__newindex or rawset
local hookMainAndConfig
---@param _G table
---@param key string
---@param fnOrDiscard unknown
function hookMainAndConfig(_G, key, fnOrDiscard)
if key == 'main' or key == 'config' then
---@cast fnOrDiscard function
if key == 'main' then
runInitializers 'root'
end
rawIndex(_G, key, function()
if key == 'config' then
fnOrDiscard()
elseif gmt.__newindex == hookMainAndConfig then
gmt.__newindex = rawIndex --restore the original __newindex if no further hooks on __newindex exist.
end
runInitializers(key)
if key == 'main' then
fnOrDiscard()
end
end)
else
rawIndex(_G, key, fnOrDiscard)
end
end
gmt.__newindex = hookMainAndConfig
OnInit.root = createInit 'root' -- Runs immediately during the Lua root, but is yieldable (allowing requirements) and pcalled.
OnInit.config = createInit 'config' -- Runs when `config` is called. Credit to @Luashine: https://www.hiveworkshop.com/threads/inject-main-config-from-we-trigger-code-like-jasshelper.338201/
OnInit.main = createInit 'main' -- Runs when `main` is called. Idea from @Tasyen: https://www.hiveworkshop.com/threads/global-initialization.317099/post-3374063
end
if library then
library.queuedInitializerList = {}
library.customDeclarationList = {}
library.yieldedModuleMatrix = {}
library.moduleValueMatrix = {}
function library:pack(name, ...)
self.moduleValueMatrix[name] = table.pack(...)
end
function library:resume()
if self.queuedInitializerList[1] then
local continue, tempQueue, forceOptional
::initLibraries::
repeat
continue=false
self.queuedInitializerList, tempQueue =
{}, self.queuedInitializerList
for _,func in ipairs(tempQueue) do
if func(forceOptional) then
continue=true --Something was initialized; therefore further systems might be able to initialize.
else
insert(self.queuedInitializerList, func) --If the queued initializer returns false, that means its requirement wasn't met, so we re-queue it.
end
end
until not continue or not self.queuedInitializerList[1]
if self.customDeclarationList[1] then
self.customDeclarationList, tempQueue =
{}, self.customDeclarationList
for _,func in ipairs(tempQueue) do
func() --unfreeze any custom initializers.
end
elseif not forceOptional then
forceOptional = true
else
return
end
goto initLibraries
end
end
local function declareName(name, initialValue)
assert(type(name) == 'string')
assert(library.moduleValueMatrix[name] == nil)
library.moduleValueMatrix[name] =
initialValue and { true, n = 1 }
end
function library:create(name, userFunc)
assert(type(userFunc) == 'function')
declareName(name, false) --declare itself as a non-loaded library.
return function()
self:pack(name, userFunc(Require)) --pack return values to allow multiple values to be communicated.
if self.moduleValueMatrix[name].n == 0 then
self:pack(name, true) --No values were returned; therefore simply package the value as `true`
end
end
end
---@async
function library:declare(name)
declareName(name, true) --declare itself as a loaded library.
local co = coroutine.running()
insert(
self.customDeclarationList,
function()
coroutine.resume(co)
end
)
coroutine.yield() --yields the calling function until after all currently-queued initializers have run.
end
local processRequirement
---@async
function processRequirement(
optional,
requirement,
explicitSource
)
if type(optional) == 'string' then
optional, requirement, explicitSource =
true, optional, requirement --optional requirement (processed by the __index method)
else
optional = false --strict requirement (processed by the __call method)
end
local source = explicitSource or _G
assert(type(source)=='table')
assert(type(requirement)=='string')
::reindex::
local subSource, subReq =
requirement:match("([\x25w_]+)\x25.(.+)") --Check if user is requiring using "table.property" syntax
if subSource and subReq then
source,
requirement =
processRequirement(subSource, source), --If the container is nil, yield until it is not.
subReq
if type(source)=='table' then
explicitSource = source
goto reindex --check for further nested properties ("table.property.subProperty.anyOthers").
else
return --The source table for the requirement wasn't found, so disregard the rest (this only happens with optional requirements).
end
end
local function loadRequirement(unpack)
local package = rawget(source, requirement) --check if the requirement exists in the host table.
if not package and not explicitSource then
if library.yieldedModuleMatrix[requirement] then
library.yieldedModuleMatrix[requirement]() --load module if it exists
end
package = library.moduleValueMatrix[requirement] --retrieve the return value from the module.
if unpack and type(package)=='table' then
return table.unpack(package, 1, package.n) --using unpack allows any number of values to be returned by the required library.
end
end
return package
end
local co, loaded
local function checkReqs(forceOptional, printErrors)
if not loaded then
loaded = loadRequirement()
loaded = loaded or optional and
(loaded==nil or forceOptional)
if loaded then
if co then coroutine.resume(co) end --resume only if it was yielded in the first place.
return loaded
elseif printErrors then
coroutine.resume(co, true)
end
end
end
if not checkReqs() then --only yield if the requirement doesn't already exist.
co = coroutine.running()
insert(library.queuedInitializerList, checkReqs)
if coroutine.yield() then
error("Missing Requirement: "..requirement) --handle the error within the user's function to get an accurate stack trace via the `try` function.
end
end
return loadRequirement(true)
end
---@type Requirement
function Require.strict(name, explicitSource)
return processRequirement(nil, name, explicitSource)
end
setmetatable(Require --[[@as table]], {
__call = processRequirement,
__index = function()
return processRequirement
end
})
local module = createInit 'module'
--- `OnInit.module` will only call the OnInit function if the module is required by another resource, rather than being called at a pre-
--- specified point in the loading process. It works similarly to Go, in that including modules in your map that are not actually being
--- required will throw an error message.
---@param name string
---@param func Initializer.Callback
---@param debugLineNum? integer
OnInit.module = function(name, func, debugLineNum)
if func then
local userFunc = func
func = function(require)
local co = coroutine.running()
library.yieldedModuleMatrix[name] =
function(failure)
library.yieldedModuleMatrix[name] = nil
coroutine.resume(co, failure)
end
if coroutine.yield() then
error("Module declared but not required: "..name)
end
return userFunc(require)
end
end
module(name, func, debugLineNum)
end
end
if assignLegacyAPI then --This block handles legacy code.
---Allows packaging multiple requirements into one table and queues the initialization for later.
---@deprecated
---@param initList string | table
---@param userFunc function
function OnInit.library(initList, userFunc)
local typeOf = type(initList)
assert(typeOf=='table' or typeOf=='string')
assert(type(userFunc) == 'function')
local function caller(use)
if typeOf=='string' then
use(initList)
else
for _,initName in ipairs(initList) do
use(initName)
end
if initList.optional then
for _,initName in ipairs(initList.optional) do
use.lazily(initName)
end
end
end
end
if initList.name then
OnInit(initList.name, caller)
else
OnInit(caller)
end
end
local legacyTable = {}
assignLegacyAPI(legacyTable, OnInit)
for key,func in pairs(legacyTable) do
rawset(_G, key, func)
end
OnInit.final(function()
for key in pairs(legacyTable) do
rawset(_G, key, nil)
end
end)
end
initEverything()
end
if Debug then Debug.endFile() end
if Debug and Debug.begin then Debug.beginFile("DemonHuntersAnimations") end
OnInit.global(function()
-- DemonHunterIntro1
function LoadDemonHunter1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_FelRogueCin101Edit, "H02Arthas26")
SetSoundFacialAnimationGroupLabel(gg_snd_FelRogueCin101Edit, "Map-Arthas")
SetSoundFacialAnimationSetFilepath(gg_snd_FelRogueCin101Edit, "sound/dialogue/faceanimation/human02/facialanimation/arthas.animset")
end
-- DemonHunterIntro2
function LoadDemonHunter2FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_FelRogueCin103, "H02Arthas05")
SetSoundFacialAnimationGroupLabel(gg_snd_FelRogueCin103, "Map-Arthas")
SetSoundFacialAnimationSetFilepath(gg_snd_FelRogueCin103, "sound/dialogue/faceanimation/human02/facialanimation/arthas.animset_ingame")
end
-- DemonHunterIntro3
function LoadDemonHunter3FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_FelRogueCin105, "H02Arthas05")
SetSoundFacialAnimationGroupLabel(gg_snd_FelRogueCin105, "Map-Arthas")
SetSoundFacialAnimationSetFilepath(gg_snd_FelRogueCin105, "sound/dialogue/faceanimation/human02/facialanimation/arthas.animset_ingame")
end
-- PaladinIntro1
function LoadPaladin1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_PaladinCin102, "A01Kael02")
SetSoundFacialAnimationGroupLabel(gg_snd_PaladinCin102, "Map-Kael")
SetSoundFacialAnimationSetFilepath(gg_snd_PaladinCin102, "sound/dialogue/faceanimation/humanx01/facialanimation/kael.animset_ingame")
end
-- PaladinIntro2
function LoadPaladin2FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_PaladinCin104, "A01Kael02")
SetSoundFacialAnimationGroupLabel(gg_snd_PaladinCin104, "Map-Kael")
SetSoundFacialAnimationSetFilepath(gg_snd_, "sound/dialogue/faceanimation/humanx01/facialanimation/kael.animset_ingame")
end
-- VillagerQ1 Quest 1
function LoadVillager1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_Quest1New, "H01VillagerM28")
SetSoundFacialAnimationGroupLabel(gg_snd_Quest1New, "Map")
SetSoundFacialAnimationSetFilepath(gg_snd_Quest1New, "Sound/Dialogue/Faceanimation/Human01/Facialanimation/Villagerman.animset")
end
-- Villager1QComplete Quest 1
function LoadVillager1CompleteFacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_Quest1Complete, "H08Arthas25")
SetSoundFacialAnimationGroupLabel(gg_snd_Quest1Complete, "Map-Arthas")
SetSoundFacialAnimationSetFilepath(gg_snd_Quest1Complete, "Sound/Dialogue/Faceanimation/Human08/Facialanimation/arthas.animset")
end
-- Lot'hul Silverveil Quest2
function LoadLothul1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_Lothulv1, "A03Kael43")
SetSoundFacialAnimationGroupLabel(gg_snd_Lothulv1, "Map-Kael")
SetSoundFacialAnimationSetFilepath(gg_snd_Lothulv1, "sound/dialogue/faceanimation/humanx03/facialanimation/kael.animset_ingame")
end
-- Lot'hul Silverveil Quest2 Complete
function LoadLothul1CompleteFacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_Lothulv2, "A07Kael04")
SetSoundFacialAnimationGroupLabel(gg_snd_Lothulv2, "Map-Kael")
SetSoundFacialAnimationSetFilepath(gg_snd_Lothulv2, "sound/dialogue/faceanimation/humanx06/facialanimation/kael.animset")
end
-- Quest 2 Dark Wizard
function LoadQuest2DarkWizardFacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_HeroArchMagePissed3, "HeroArchMagePissed3")
SetSoundFacialAnimationGroupLabel(gg_snd_HeroArchMagePissed3, "BaseSD")
SetSoundFacialAnimationSetFilepath(gg_snd_HeroArchMagePissed3, "Units/Human/Heroarchmage/Heroarchmage_portrait.sd.animset")
end
-- Quest 3 Text 1
function LoadQuest3FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_Quest3NewSpeech, "H02Uther04")
SetSoundFacialAnimationGroupLabel(gg_snd_Quest3NewSpeech, "Map-Uther")
SetSoundFacialAnimationSetFilepath(gg_snd_Quest3NewSpeech, "sound/dialogue/faceanimation/human02/facialanimation/uther.animset")
end
-- Quest 3 Text 2
function LoadQuest3CompleteFacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_Quest3Complete, "A02Kael22")
SetSoundFacialAnimationGroupLabel(gg_snd_Quest3Complete, "Map-Kael")
SetSoundFacialAnimationSetFilepath(gg_snd_Quest3Complete, "sound/dialogue/faceanimation/humanx02/facialanimation/kael.animset_ingame")
end
-- Quest 4 Text 1
function LoadQuest4FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_Quest4, "H08Arthas25")
SetSoundFacialAnimationGroupLabel(gg_snd_Quest4, "Map-Arthas")
SetSoundFacialAnimationSetFilepath(gg_snd_Quest4, "sound/dialogue/faceanimation/human08/facialanimation/arthas.animset")
end
-- Quest 4 Text 2
function LoadQuest4CompleteFacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_Quest4Complete, "A02Kael22")
SetSoundFacialAnimationGroupLabel(gg_snd_Quest4Complete, "Map")
SetSoundFacialAnimationSetFilepath(gg_snd_Quest4Complete, "sound/dialogue/faceanimation/humanx02/facialanimation/kael.animset")
end
-- Quest 4 Text 3
function LoadQuest4Complete1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_Quest4Complete2, "A02Kael22")
SetSoundFacialAnimationGroupLabel(gg_snd_Quest4Complete2, "Map")
SetSoundFacialAnimationSetFilepath(gg_snd_Quest4Complete2, "sound/dialogue/faceanimation/humanx02/facialanimation/kael.animset")
end
-- Dreadlord Vul'Maris Text 1
function LoadVul1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_VulMarisx1, "L03Varimathras68")
SetSoundFacialAnimationGroupLabel(gg_snd_VulMarisx1, "Map-Varimathras")
SetSoundFacialAnimationSetFilepath(gg_snd_VulMarisx1, "sound/dialogue/faceanimation/undeadx03/facialanimation/varimathras.animset")
end
-- Dreadlord Vul'Maris Text 2
function LoadVul2FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_VulMarisx2, "L03Varimathras63")
SetSoundFacialAnimationGroupLabel(gg_snd_VulMarisx2, "Map-Varimathras")
SetSoundFacialAnimationSetFilepath(gg_snd_VulMarisx2, "sound/dialogue/faceanimation/undeadx03/facialanimation/varimathras.animset")
end
-- Dreadlord Vul'Maris Text 3
function LoadVul3FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_VulMarisx2, "L03Varimathras63")
SetSoundFacialAnimationGroupLabel(gg_snd_VulMarisx2, "Map-Varimathras")
SetSoundFacialAnimationSetFilepath(gg_snd_VulMarisx2, "sound/dialogue/faceanimation/undeadx03/facialanimation/varimathras.animset")
end
-- Dreadlord Vezgor Text 1
function LoadVez1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_Vezgor1, "L05Varimathras03")
SetSoundFacialAnimationGroupLabel(gg_snd_Vezgor1, "Map-Varimathras")
SetSoundFacialAnimationSetFilepath(gg_snd_Vezgor1, "sound/dialogue/faceanimation/undeadx05/facialanimation/varimathras.animset")
end
-- Dreadlord Vezgor Text 2
function LoadVez2FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_Vezgor2, "L05Varimathras03")
SetSoundFacialAnimationGroupLabel(gg_snd_Vezgor2, "Map-Varimathras")
SetSoundFacialAnimationSetFilepath(gg_snd_Vezgor2, "sound/dialogue/faceanimation/undeadx05/facialanimation/varimathras.animset")
end
-- Dreadlord Vezgor Text 3
function LoadVez3FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_Vezgor3, "L05Varimathras03")
SetSoundFacialAnimationGroupLabel(gg_snd_Vezgor3, "Map-Varimathras")
SetSoundFacialAnimationSetFilepath(gg_snd_Vezgor3, "sound/dialogue/faceanimation/undeadx05/facialanimation/varimathras.animset")
end
-- Quest 5 Text 1
function LoadQuest5FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_Quest5, "L05Varimathras03")
SetSoundFacialAnimationGroupLabel(gg_snd_Quest5, "Map-Varimathras")
SetSoundFacialAnimationSetFilepath(gg_snd_Quest5, "sound/dialogue/faceanimation/undeadx05/facialanimation/varimathras.animset")
end
-- Quest 5 Text 2
function LoadQuest5CompleteFacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_Quest5Complete, "H01Uther11")
SetSoundFacialAnimationGroupLabel(gg_snd_Quest5Complete, "Map")
SetSoundFacialAnimationSetFilepath(gg_snd_Quest5Complete, "sound/dialogue/faceanimation/human01/facialanimation/uther.animset_ingame")
end
-- Quest 4 Lich Text 1
function LoadQuest4Lich1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_HeroLichYesAttack1, "HeroLichYesAttack1")
SetSoundFacialAnimationGroupLabel(gg_snd_HeroLichYesAttack1, "BaseSD")
SetSoundFacialAnimationSetFilepath(gg_snd_HeroLichYesAttack1, "Units/Undead/Herolich/Herolich_portrait.sd.animset_ingame")
end
-- Random Talks trough the maps
-- Fire Secret Trees
function LoadSecretPaladin1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_BloodElf02PaladinSecret, "A03Kael21")
SetSoundFacialAnimationGroupLabel(gg_snd_BloodElf02PaladinSecret, "Map-Kael")
SetSoundFacialAnimationSetFilepath(gg_snd_BloodElf02PaladinSecret, "sound/dialogue/faceanimation/humanx03/facialanimation/kael.animset_ingame")
end
-- Ghost woods secret 1
function LoadSecretFelRogue1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_BloodElf02FelRogueSecretGhost1, "A01Kael23")
SetSoundFacialAnimationGroupLabel(gg_snd_BloodElf02FelRogueSecretGhost1, "Map-Kael")
SetSoundFacialAnimationSetFilepath(gg_snd_BloodElf02FelRogueSecretGhost1, "sound/dialogue/faceanimation/humanx01/facialanimation/kael.animset_ingame")
end
-- Ghost woods secret 2
function LoadSecretPaladin2FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_BloodElf02PaladinSecretGhost2, "A02Kael12")
SetSoundFacialAnimationGroupLabel(gg_snd_BloodElf02PaladinSecretGhost2, "Map-Kael")
SetSoundFacialAnimationSetFilepath(gg_snd_BloodElf02PaladinSecretGhost2, "sound/dialogue/faceanimation/humanx02/facialanimation/kael.animset_ingame")
end
----------
-- Homelands ruins 1
function LoadRuinsPaladin1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_BloodElf02PaladinRuins1, "A01Kael15")
SetSoundFacialAnimationGroupLabel(gg_snd_BloodElf02PaladinRuins1, "Map-Kael")
SetSoundFacialAnimationSetFilepath(gg_snd_BloodElf02PaladinRuins1, "sound/dialogue/faceanimation/humanx01/facialanimation/kael.animset_ingame")
end
----------
-- Homelands ruins 2
function LoadRuinsFelRogue1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_BloodElf02FelRogueRuins2, "A02Kael12")
SetSoundFacialAnimationGroupLabel(gg_snd_BloodElf02FelRogueRuins2, "Map-Kael")
SetSoundFacialAnimationSetFilepath(gg_snd_BloodElf02FelRogueRuins2, "sound/dialogue/faceanimation/humanx02/facialanimation/kael.animset_ingame")
end
----------
-- Corrupted Druid Fel Rogue 1
function LoadRuinsCorruptedDruidFelRogue1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_BloodElf02FelRogueCorruptedDruid1, "A01Kael16")
SetSoundFacialAnimationGroupLabel(gg_snd_BloodElf02FelRogueCorruptedDruid1, "Map-Kael")
SetSoundFacialAnimationSetFilepath(gg_snd_BloodElf02FelRogueCorruptedDruid1, "sound/dialogue/faceanimation/humanx01/facialanimation/kael.animset_ingame")
end
----------
-- Corrupted Druid Paladin 2
function LoadRuinsCorruptedDruidPaladin2FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_BloodElf02PaladinCorruptedDruid2, "A03Kael21")
SetSoundFacialAnimationGroupLabel(gg_snd_BloodElf02PaladinCorruptedDruid2, "Map-Kael")
SetSoundFacialAnimationSetFilepath(gg_snd_BloodElf02PaladinCorruptedDruid2, "sound/dialogue/faceanimation/humanx03/facialanimation/kael.animset_ingame")
end
----------
-- Luzran Warning
function LoadRuinsLuzranFelRogue1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_BloodElf02FelRogueLuzran1, "A01Kael10")
SetSoundFacialAnimationGroupLabel(gg_snd_BloodElf02FelRogueLuzran1, "Map-Kael")
SetSoundFacialAnimationSetFilepath(gg_snd_BloodElf02FelRogueLuzran1, "sound/dialogue/faceanimation/humanx01/facialanimation/kael.animset_ingame")
end
----------
-- House Ruins
function LoadRuinsHouseRuinsFelRogue1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_BloodElf02FelRogueRuinedHouse, "A02Kael02")
SetSoundFacialAnimationGroupLabel(gg_snd_BloodElf02FelRogueRuinedHouse, "Map-Kael")
SetSoundFacialAnimationSetFilepath(gg_snd_BloodElf02FelRogueRuinedHouse, "sound/dialogue/faceanimation/humanx02/facialanimation/kael.animset_ingame")
end
----------
end)
if Debug and Debug.endFile then Debug.endFile() end
if Debug then Debug.beginFile "SaveLoad Test" end
OnInit.global("SaveLoadTest", function(require)
require "SaveLoadHelper"
require "Sync Stream"
PlayerData = {} ---@type table<integer, table<integer>>
PlayerData[1] = {}
PlayerData[2] = {}
ReceivedPlayerData = {} ---@type table[][][]
ReceivedPlayerData[0] = {} --player indexes
ReceivedPlayerData[1] = {}
AmountTimesLoaded = __jarray(0)
LoadedHero = {false, false}
-------------------configure here-----------------------------------------------------------------------------
function SetHeroes()
Heroes = {
udg_Paladin, --must be set to a variable in GUI
udg_FelRogue
}
HeroNames[1] = GetHeroProperName(Heroes[1])
HeroNames[2] = GetHeroProperName(Heroes[2])
end
HeroNames = {}
--the RAW CODES must be the same throughout the maps
local abilitiesList= {
[1] = {"A021","AHhb","AHds","AHre","A023"}, --ARALANA SUNWING
[2] = {"A033","A022","A00U","A032","A034"} --FELROUGE
}
local abilitiesListByHeroId= {
[FourCC("H005")] = {"A021","AHhb","AHds","AHre","A023"}, --ARALANA SUNWING
[FourCC("E003")] = {"A033","A022","A00U","A032","A034"} --FELROUGE
}
----------------------------endconfiguration---------------------------------------------------------------
local function ToUpperCase(__, letter)
return letter:upper()
end
local function ToPascalCase(whichString)
whichString = whichString:gsub("|[cC]\x25x\x25x\x25x\x25x\x25x\x25x\x25x\x25x", "") --remove color codes
whichString = whichString:gsub("|[rR]", "") --remove closing color codes
whichString = whichString:gsub("(\x25s)(\x25a)", ToUpperCase) --remove spaces and convert to upper case after space
whichString = whichString:gsub("[^\x25w]", "") --remove special characters
return string.upper(whichString:sub(1,1)) .. string.sub(whichString,2) --converts first character to upper case
end
---@param hero unit
---@param player player
---@param receivedData table<integer, any>
function LoadHero(hero, player, receivedData)
if not hero then
DisplayTextToForce(GetPlayersAll(), "Couldn't find hero to receive loaded data to.")
return
end
local heroId = receivedData[1]
SetHeroLevelBJ(hero, receivedData[2], false)
SetHeroXP(hero, receivedData[3], false)
SetHeroStr(hero, receivedData[4], true)
SetHeroAgi(hero, receivedData[5], true)
SetHeroInt(hero, receivedData[6], true)
--abilities
UnitModifySkillPoints(hero, 30 - GetHeroSkillPoints(hero))
for i = 9, 13 do
if receivedData[i] > 0 then
SelectHeroSkill(hero, FourCC(abilitiesListByHeroId[heroId][i - 8]))
SetUnitAbilityLevel(hero, FourCC(abilitiesListByHeroId[heroId][i - 8]), receivedData[i])
end
end
UnitModifySkillPoints(hero, -GetHeroSkillPoints(hero))
--items
local item
for i = 0, 5 do
if receivedData[14 + i] ~= 0 then
item = UnitAddItemById(hero, receivedData[14 + i])
UnitDropItemSlot(hero, item, i)
SetItemCharges(item, receivedData[20 + i])
end
end
--only set these after everything else
BlzSetUnitMaxHP(hero, receivedData[7])
BlzSetUnitMaxMana(hero, receivedData[8])
SetUnitLifePercentBJ(hero, 100)
SetUnitManaPercentBJ(hero, 100)
--end
end
--load synced
function Sync(totalChunk, player)
local id = GetPlayerId(player)
--finished syncing the whole file
local myData = SaveLoad.loadHelperIndex(totalChunk)
--data decoded and retrieved
--check if data was validated
if not myData then
DisplayTextToPlayer(player, 0, 0, "Invalid. File was tampered.")
return
end
AmountTimesLoaded[id] = AmountTimesLoaded[id] + 1
ReceivedPlayerData[id][AmountTimesLoaded[id]] = myData
if AmountTimesLoaded[id] == 1 then
LoadHero(Heroes[1], player, ReceivedPlayerData[id][1]) -- ARALANA SUNWING
end
if AmountTimesLoaded[id] == 2 then
LoadHero(Heroes[2], player, ReceivedPlayerData[id][2]) -- FEL ROGUE
end
end
--[[local t = CreateTrigger()
for i = 0, 23 do
TriggerRegisterPlayerChatEvent(t, Player(i), "", false)
end]]
--save
function SaveHeroesData()
for heroIndex, hero in ipairs(Heroes) do
PlayerData[heroIndex][1] = GetUnitTypeId(hero)
PlayerData[heroIndex][2] = GetHeroLevel(hero)
PlayerData[heroIndex][3] = GetHeroXP(hero)
PlayerData[heroIndex][4] = GetHeroStatBJ(bj_HEROSTAT_STR, hero, false)
PlayerData[heroIndex][5] = GetHeroStatBJ(bj_HEROSTAT_AGI, hero, false)
PlayerData[heroIndex][6] = GetHeroStatBJ(bj_HEROSTAT_INT, hero, false)
PlayerData[heroIndex][7] = BlzGetUnitMaxHP(hero)
PlayerData[heroIndex][8] = BlzGetUnitMaxMana(hero)
--abilities
PlayerData[heroIndex][9] = GetUnitAbilityLevel(hero, FourCC(abilitiesList[heroIndex][1]))
PlayerData[heroIndex][10] = GetUnitAbilityLevel(hero,FourCC(abilitiesList[heroIndex][2])) --5
PlayerData[heroIndex][11] = GetUnitAbilityLevel(hero,FourCC(abilitiesList[heroIndex][3]))
PlayerData[heroIndex][12] = GetUnitAbilityLevel(hero,FourCC(abilitiesList[heroIndex][4]))
PlayerData[heroIndex][13] = GetUnitAbilityLevel(hero,FourCC(abilitiesList[heroIndex][5]))
--items
for k = 1, 6 do
PlayerData[heroIndex][13 + k] = GetItemTypeId(UnitItemInSlot(hero, k - 1))
PlayerData[heroIndex][19 + k] = (UnitItemInSlot(hero, k - 1) == nil) and -1 or GetItemCharges(UnitItemInSlot(hero, k - 1))
end
--save for each player
for j = 0, 1 do
SaveLoad.saveHelperIndex(Player(j), PlayerData[heroIndex], nil,
SaveLoad.FOLDER .. "\\" .. ToPascalCase(GetHeroProperName(hero)) ..".pld")
end
end
end
function LoadHeroData()
for _, hero in ipairs(HeroNames) do
local file
for i = 0, 1 do --player 0 is red
if GetLocalPlayer() == Player(i) then
if FileIO.Load(SaveLoad.FOLDER .. "\\" .. ToPascalCase(hero) ..".pld") then
--load file
file = FileIO.Load(SaveLoad.FOLDER .. "\\" .. ToPascalCase(hero) ..".pld")
--sync it
--SyncStream.sync(Player(i), file, "Sync") --i is for player 1 and 2
else
DisplayTextToPlayer(Player(i), 0, 0, "File doesn't exist.")
end
end
SyncStream.sync(Player(i), file, "Sync") --i is for player 1 and 2
end
end
end
--[[local function TypeChat()
local str = GetEventPlayerChatString()
local p = GetTriggerPlayer()
local id = GetPlayerId(p)
if str:sub(1, 5) == "-save" then
--save the file
SaveHeroesData()
elseif str:sub(1, 5) == "-load" then
LoadHeroData()
end
end
TriggerAddAction(t, TypeChat)]]
end)
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "SaveLoad Test" end
OnInit.map("SaveLoadTest", function(require)
require "SaveLoadHelper"
require "Sync Stream"
local t = CreateTrigger()
for i = 0, 23 do
TriggerRegisterPlayerChatEvent(t, Player(i), "", false)
end
function Sync(totalChunk, player)
local id = GetPlayerId(player)
--finished syncing the whole file
print(GetPlayerName(player) .. " has finished loading.")
local myData = SaveLoad.loadHelperDynamic(totalChunk)
--data decoded and retrieved
--check if data was validated
if not myData then
print("You cheating motherfucker, you thought you could have tampered the string didn't you?")
return
end
--DO SOMETHING WITH IT: set player properties and etc.
for key, v in pairs(myData) do
print("VIEW VALUES:", key, v) --prints values
PlayerData[id][key] = v --the table that holds player stuff to save/load
end
end
local function TypeChat()
local str = GetEventPlayerChatString()
local p = GetTriggerPlayer()
local id = GetPlayerId(p)
if str:sub(1, 5) == "-save" then
--save the file
SaveLoad.saveHelperDynamic(p, PlayerData[id], GetPlayerName(p))
elseif str:sub(1, 5) == "-load" then
local file
if GetLocalPlayer() == p then
if FileIO.Load(SaveLoad.getDefaultPath(GetPlayerName(p))) then
--load file
file = FileIO.Load(SaveLoad.getDefaultPath(GetPlayerName(p)))
DisplayTextToPlayer(p, 0, 0, "Loading...")
else
DisplayTextToPlayer(p, 0, 0, "File doesn't exist.")
end
end
--sync it
SyncStream.sync(p, file, "Sync")
elseif str:sub(1, 6) == "-nick " then --change nickname for testing.
SetPlayerName(p, str:sub(7, str:len()))
PlayerData[id][1] = str:sub(7, str:len())
end
end
TriggerAddAction(t, TypeChat)
--trigger to detect if you entered a region from map 2
t = CreateTrigger()
TriggerRegisterEnterRectSimple(t, gg_rct_CheckEnteredFromMap2)
TriggerAddAction(t, function()
if PlayerData[GetPlayerId(GetOwningPlayer(GetTriggerUnit()))].EnteredRegion then
print("Finally, you entered the region, slowpoke.")
else
print("You haven't entered the region in map 2, noob.")
end
end)
end)
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "RequiredExp" end
OnInit.global("RequiredExp", function()
local HERO_MAX_LEVEL = 30
local prevValueFactor = 1.00
local levelFactor = 100
local constantFactor = 0
local expTable = {
[1] = 200
}
local mt = {}
setmetatable(expTable, mt)
mt.__index = function(t, k)
if k == 0 then
return 0
elseif k == 1 then
return expTable[1]
else
return math.floor(expTable[k - 1] * prevValueFactor + levelFactor * (k + 1) +
constantFactor)
end
end
function GetRequiredMaxExp(curLevel)
--[[if curLevel == 0 then return 0 end
if curLevel == 1 then
return expTable[curLevel + 1]
elseif not expTable[curLevel + 1] then
expTable[curLevel + 1] = math.floor(expTable[curLevel] * prevValueFactor + levelFactor * (curLevel + 1) +
constantFactor)
end
return expTable[curLevel + 1]]
return expTable[curLevel]
end
function GetExpProgress(curLevel, curExp, maxExp)
local lastLevelMaxExp = GetRequiredMaxExp(curLevel - 1)
if (curExp == lastLevelMaxExp) and (curLevel == HERO_MAX_LEVEL) then
return 100
end
--relocate
local trueMaxExp = maxExp - lastLevelMaxExp
local trueCurExp = curExp - lastLevelMaxExp
return math.floor(trueCurExp / trueMaxExp * 100)
end
end)
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "SyncStream" end
--[[
By Trokkin
Provides functionality to designed to safely sync arbitrary amounts of data.
Uses timers to spread BlzSendSyncData calls over time.
Wrda's version - not using his encoder
API:
---@param whichPlayer player
---@param getLocalData string | fun():string
---@param funcName string
function SyncStream.sync(whichPlayer, getLocalData, funcName)
- Adds getLocalData (string or function that returns string) to the queue to be synced. Once completed, fires the funcName function.
- your funcName function MUST take the synced string and the owner of the string as parameters.
]]
OnInit.module("Sync Stream", function()
--CONFIGURATION
local PREFIX = "Sync"
local CHUNK_SIZE = 200 --string length per chunk
local PACKAGE_PER_TICK = 32 --amount of packages per interval
local PACKAGE_TICK_PER_SECOND = 32 --interval in which the syncing takes place
local MAX_IDS = 999
local MAX_CHUNKS = 99
local DELIMITER = "!" --function delimiter character
--END CONFIGURATION
--internal
local streams = {}
---Returns the function from the given string. Also works with functions within tables, the keys MUST be of string type.
---@param funcName string
---@return function
local function getFunction(funcName)
local f = _G
for v in funcName:gmatch("[^\x25.]+") do
f = f[v]
end
return type(f) == "function" and f or error("value is not a function")
end
---@param maxNum number
---@param currentAmount number
local function fillBlankDigits(maxNum, currentAmount)
local digits = #tostring(maxNum) - #tostring(currentAmount)
local blank = ""
for i = 1, digits do
blank = blank .. "0"
end
return blank
end
---@class syncQueue
---@field id integer
---@field idLength integer
---@field length integer
---@field chunks string[]
---@field next_chunk integer
---@field callbackName string
local syncQueue = {}
syncQueue.__index = syncQueue
---@param id integer The id of the promise.
---@param data string Data to be sent from the local player.
function syncQueue.create(id, data)
local queue = setmetatable({
id = id,
chunks = {},
next_chunk = 0,
length = #data,
callbackName = ""
}, syncQueue)
for i = 1, #data, CHUNK_SIZE do
queue.chunks[#queue.chunks + 1] = data:sub(i, i + CHUNK_SIZE - 1)
end
if #queue.chunks > MAX_CHUNKS then
error("WARNING: Max CHUNK digits reached!")
end
return queue
end
function syncQueue:pop()
if self.next_chunk > #self.chunks then
self = nil
return
end
--assign id to chunk
local idDigit0 = fillBlankDigits(MAX_IDS, self.id)
local chunkDigit0 = fillBlankDigits(MAX_CHUNKS, self.next_chunk)
local package = idDigit0 .. tostring(self.id) .. chunkDigit0 .. tostring(self.next_chunk)
--print("SYNC POP")
--print(self.id, self.next_chunk)
if self.next_chunk == 0 then
local maxChunkDigit0 = fillBlankDigits(MAX_CHUNKS, #self.chunks)
package = DELIMITER .. self.callbackName .. DELIMITER .. package .. maxChunkDigit0 .. #self.chunks .. self.length
--print("OVERALL PACKAGE LENGTH: " .. self.length)
--print(package)
else
--print("THIS PACKAGE")
package = package .. self.chunks[self.next_chunk]
end
-- print(">", self.next_chunk, package)
if BlzSendSyncData(PREFIX, package) then
self.next_chunk = self.next_chunk + 1
end
end
--[ PROMISE CLASS ]--
---@class promise
---@field id integer
---@field length integer?
---@field next_chunk integer
---@field chunks string[]
---@field queue syncQueue?
local promise = {}
promise.__index = promise
---@param id integer The id of the promise.
function promise.create(id)
return setmetatable({
id = id,
chunks = {},
next_chunk = 0,
length = nil,
queue = nil,
}, promise)
end
function promise:consume(chunk_id, package)
--print("prev: " .. self.next_chunk)
if self.length and self.length <= (self.next_chunk - 1) * CHUNK_SIZE then
return
end
-- print("<", chunk_id, package)
self.chunks[chunk_id] = package
while self.next_chunk <= chunk_id and self.chunks[self.next_chunk] ~= nil do
self.next_chunk = self.next_chunk + 1
end
--print("now: " .. self.next_chunk)
--new DISABLED
--if self.length and self.length <= (self.next_chunk - 1) * CHUNK_SIZE then
-- self.callback(table.concat(self.chunks), GetTriggerPlayer())
--end
end
local syncTimer
--[ SYNC STREAM CLASS ]--
local syncTrigger ---@type trigger
local localPlayer
--- Sends or receives player's data assymentrically
---@class SyncStream
---@field owner player
---@field is_local boolean
---@field next_promise integer
---@field promises promise[]
SyncStream = {}
SyncStream.__index = SyncStream
---@param owner player The player owning the data from the stream
local function CreateSyncStream(owner)
return setmetatable({
owner = owner,
is_local = owner == localPlayer,
next_promise = 1,
promises = {}
}, SyncStream)
end
---Adds getLocalData (string or function that returns string) to the queue to be synced. Once completed, fires the callBackFunctionName function.
---your callBackFunctionName function MUST take the synced string and the owner of the string as parameters.
---@param whichPlayer player
---@param getLocalData string | fun():string
---@param callBackFunctionName string
function SyncStream.sync(whichPlayer, getLocalData, callBackFunctionName)
if not getLocalData then return end
local self = streams[GetPlayerId(whichPlayer)] ---@type SyncStream
if #self.promises == MAX_IDS then
error("WARNING: Max ID digits reached!")
return
end
local promise = promise.create(#self.promises + 1)
--print("created promise id:" .. promise.id)
if self.is_local then
if type(getLocalData) == "function" then
getLocalData = getLocalData()
end
if type(getLocalData) ~= "string" then
getLocalData = "sync error: bad data type provided " .. type(getLocalData)
end
promise.queue = syncQueue.create(promise.id, getLocalData)
promise.queue.callbackName = callBackFunctionName
--print("created queue id:" .. promise.queue.id)
end
self.promises[promise.id] = promise
end
OnInit.final(function()
syncTimer = CreateTimer()
localPlayer = GetLocalPlayer()
local playerSyncedPromises = {}
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
streams[i] = CreateSyncStream(Player(i)) ---@type SyncStream
--new
playerSyncedPromises[Player(i)] = {}
end
--- Setup sender timer
local s = streams[GetPlayerId(GetLocalPlayer())] ---@type SyncStream
if not s.is_local then
print("SyncStream panic: local stream is not local")
return
end
TimerStart(syncTimer, 1 / PACKAGE_TICK_PER_SECOND, true, function()
for i = 1, PACKAGE_PER_TICK do
while s.next_promise <= #s.promises and s.promises[s.next_promise].queue == nil do
s.next_promise = s.next_promise + 1
end
if s.promises[s.next_promise] == nil then
return
end
local q = s.promises[s.next_promise].queue
if q == nil then
return
end
--process sync queue
q:pop()
if q.next_chunk > #q.chunks then
s.promises[s.next_promise].queue = nil
s.promises[s.next_promise].queue = s.promises[#s.promises].queue
s.promises[#s.promises] = nil
s.next_promise = math.max(s.next_promise - 1, 1)
end
end
end)
--- Setup receiver trigger
syncTrigger = CreateTrigger()
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
BlzTriggerRegisterPlayerSyncEvent(syncTrigger, Player(i), PREFIX, false)
end
TriggerAddAction(syncTrigger, function()
local owner = GetTriggerPlayer()
local package = BlzGetTriggerSyncData()
local stream = streams[GetPlayerId(owner)]
if stream == nil then
print("SyncStream panic: no stream found for player" .. GetPlayerName(owner) .. "but got 'nothing'")
return
end
--print("START")
--print(#package, package)
local _, startPos, funcName = nil, 0, nil
--check if string starts with the delimiter, then it's the first time the promise is getting synced
--and the position will be adjusted.
--if not, then default position is 1.
if package:sub(1, 1):match(DELIMITER) then
_, startPos, funcName = package:find( DELIMITER .. "(\x25a[\x25w_.]*)" .. DELIMITER)
end
local id = tonumber(string.sub(package, startPos + 1, startPos + #tostring(MAX_IDS)))
local promise = stream.promises[id]
--local chunk_id = promise.queue.id ignore this comment
local chunk_id = tonumber(string.sub(package, startPos + #tostring(MAX_IDS) + 1 , startPos + #tostring(MAX_IDS) + #tostring(MAX_CHUNKS)))
--new
if chunk_id == 0 then
local max_chunks = tonumber(string.sub(package, startPos + #tostring(MAX_IDS) + #tostring(MAX_CHUNKS) + 1 , startPos + #tostring(MAX_IDS) + #tostring(MAX_CHUNKS)*2))
playerSyncedPromises[owner][id] = {}
playerSyncedPromises[owner][id].maxChunks = max_chunks
playerSyncedPromises[owner][id].callback = funcName
else
playerSyncedPromises[owner][id][chunk_id] = package:sub(#tostring(MAX_IDS) + #tostring(MAX_CHUNKS) + 1)
if chunk_id == playerSyncedPromises[owner][id].maxChunks then
--execute callback, inputs data and player
getFunction(playerSyncedPromises[owner][id].callback)(table.concat(playerSyncedPromises[owner][id]), owner)
playerSyncedPromises[owner][id] = nil
return
end
end
if not promise then
--async area
--triggers for player B when player A is getting synced data
--print("SyncStream panic: no promise found for id", id)
return
end
--print("CHUNK ID: ")
--print(chunk_id)
if chunk_id == 0 then
if not promise.queue then return end
promise.length = promise.queue.length or 0 --data_length
promise.next_chunk = 1
return
end
--print("CONSUME")
--print(package:sub(#tostring(MAX_IDS) + #tostring(MAX_CHUNKS) + 1))
promise:consume(chunk_id, package:sub(#tostring(MAX_IDS) + #tostring(MAX_CHUNKS) + 1))
end)
end)
end)
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "FileIO" end
--[[
FileIO v1a (Trokkin)
Provides functionality to read and write files, optimized with lua functionality in mind.
API:
FileIO.Save(filename, data)
- Write string data to a file
FileIO.Load(filename) -> string?
- Read string data from a file. Returns nil if file doesn't exist.
FileIO.SaveAsserted(filename, data, onFail?) -> bool
- Saves the file and checks that it was saved successfully.
If it fails, passes (filename, data, loadResult) to onFail.
FileIO.enabled : bool
- field that indicates that files can be accessed correctly.
Optional requirements:
DebugUtils by Eikonium @ https://www.hiveworkshop.com/threads/330758/
Total Initialization by Bribe @ https://www.hiveworkshop.com/threads/317099/
Inspired by:
- TriggerHappy's Codeless Save and Load @ https://www.hiveworkshop.com/threads/278664/
- ScrewTheTrees's Codeless Save/Sync concept @ https://www.hiveworkshop.com/threads/325749/
- Luashine's LUA variant of TH's FileIO @ https://www.hiveworkshop.com/threads/307568/post-3519040
- HerlySQR's LUA variant of TH's Save/Load @ https://www.hiveworkshop.com/threads/331536/post-3565884
Updated: 8 Mar 2023
--]]
OnInit.map("FileIO", function()
local RAW_PREFIX = ']]i([['
local RAW_SUFFIX = ']])--[['
local RAW_SIZE = 256 - #RAW_PREFIX - #RAW_SUFFIX
local LOAD_ABILITY = FourCC('ANdc')
local LOAD_EMPTY_KEY = '!@#$, empty data'
local name = nil ---@type string?
local function open(filename)
name = filename
PreloadGenClear()
Preload('")\nendfunction\n//!beginusercode\nlocal p={} local i=function(s) table.insert(p,s) end--[[')
end
local function write(s)
for i = 1, #s, RAW_SIZE do
Preload(RAW_PREFIX .. s:sub(i, i + RAW_SIZE - 1) .. RAW_SUFFIX)
end
end
local function close()
Preload(']]BlzSetAbilityTooltip(' .. LOAD_ABILITY .. ', table.concat(p), 0)\n//!endusercode\nfunction a takes nothing returns nothing\n//')
PreloadGenEnd(name --[[@as string]])
name = nil
end
---
---@param filename string
---@param data string
local function savefile(filename, data)
open(filename)
write(data)
close()
end
---@param filename string
---@return string?
local function loadfile(filename)
local s = BlzGetAbilityTooltip(LOAD_ABILITY, 0)
BlzSetAbilityTooltip(LOAD_ABILITY, LOAD_EMPTY_KEY, 0)
Preloader(filename)
local loaded = BlzGetAbilityTooltip(LOAD_ABILITY, 0)
BlzSetAbilityTooltip(LOAD_ABILITY, s, 0)
if loaded == LOAD_EMPTY_KEY then
return nil
end
return loaded
end
---@param filename string
---@param data string
---@param onFail function?
---@return boolean
local function saveAsserted(filename, data, onFail)
savefile(filename, data)
local res = loadfile(filename)
if res == data then
return true
end
if onFail then
onFail(filename, data, res)
end
return false
end
local fileIO_enabled = saveAsserted('TestFileIO.pld', 'FileIO is Enabled')
FileIO = {
Save = savefile,
Load = loadfile,
SaveAsserted = saveAsserted,
enabled = fileIO_enabled,
}
end)
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "SaveLoadHelper" end
OnInit.module("SaveLoadHelper", function(require)
require "Encoder62"
require "FileIO"
--[[
SaveLoadHelper version 1.3 by Wrda
(Special thanks to Antares)
This system is responsible in squeezing a player's data from a table and then retrieving it matching
how it was saved in. For example, you save a player's data such as PlayerSave (table) which has the fields
set to some value you set:
kills; gems; lives; locationX; locationY
Loading the file will result in a new table, with those exact same fields, and their respective values.
There are different methods to save data, a table with string keys, as described above, and a table with
indexed keys. Both have their strengths.
WARNING: You can't use \x25 (percentage sign) on a string. The loading will fail.
The workaround for this is to think of a character you'll never going to use and then replace it
with \x25\x25 (double because it escapes the sign).
Example: str = "takes 5< damage, 600< more hp"
local result = string.gsub(str, "<", "\x25\x25")
print(result) -> "takes 5% damage, 600% more hp"
API:
---@param playerName string
---@return string
function SaveLoad.getDefaultPath(playerName)
- Gets the default string format path.
---@param p player
---@param list table
---@param playerName string?
---@param filePath string?
SaveLoad.saveHelperDynamic(p, list, playerName?, filePath?)
- Saves data to a single player. "list" must be a string-key table.
- If not given a playerName, it is saved with the current player name.
- Returns the resulting table in key-indexed format.
---@param p player
---@param list table<integer, any>
---@param playerName string?
---@param filePath string?
SaveLoad.saveHelperIndex(p, list, playerName?, filePath?)
- Saves data to a single player. "list" must be an indexed-key table.
- If not given a playerName, it is saved with the current player name.
- Returns the "list" table, may be useful in when one uses SaveLoad.saveHelperDynamic because it
calls SaveLoad.saveHelperIndex inside.
---@param data string
---@return table
SaveLoad.loadHelperIndex(data)
return loadDataIndex(data)
- Loads data into a table. The table will have indexed keys.
---@param data string
---@return table<string, any>
function SaveLoad.loadHelperDynamic(data)
- Loads data into a table. The table will have string keys.
]]
SaveLoad = {}
--[[----------------------------------------------------------------------------------------------------
CONFIGURATION ]]
SaveLoad.FOLDER = "TEST MAP" -- Name of the folder. Not required, but serves as a default.
SaveLoad.FILE_PREFIX = "TestCode-" -- You can have none. Use empty string and NOT nil. Not required, but serves as a default.
SaveLoad.FILE_SUFFIX = "-0" -- You can have none. Use empty string and NOT nil. Not required, but serves as a default.
SAVE_LOAD_SEED = 1 -- This is used for generating a random permutation of the scrambled string. Set it to any integer unique for your map. You're not supposed to change your mind on this later on.
---Gets the default string format path.
---@param playerName string
---@return string
function SaveLoad.getDefaultPath(playerName)
return SaveLoad.FOLDER .. "\\" .. SaveLoad.FILE_PREFIX .. playerName .. SaveLoad.FILE_SUFFIX .. ".pld"
end
--------------------------------------------------------------------------------------------------------
local pack = string.pack
local unpack = string.unpack
local byte = string.byte
local pseudoRandomPermutation
local delimiterList = {
["integer"] = "#",
["float"] = "_",
["string"] = "&",
["true"] = "!",
["false"] = "@",
--reverse
["#"] = "integer",
["_"] = "float",
["&"] = "string",
["!"] = "true",
["@"] = "false"
}
---@param value any
local function getDelimiterType(value)
local mathType = math.type(value)
if delimiterList[mathType] then
return delimiterList[mathType]
elseif type(value) == "string" then
return delimiterList[type(value)]
elseif type(value) == "boolean" then
return delimiterList[tostring(value)]
else
error("Unrecognized delimiter type.")
end
return nil
end
---@param str string
---@param pos integer
---@return string|nil
local function findDelimiterTypeIndex(str, pos)
local found
found = str:match("([#_&!@])\x25d+", pos)
return found
end
---@param str string
---@param pos integer
---@return string|nil
local function findDelimiterTypeDynamic(str, pos)
local found
found = str:match("([#_&!@])\x25w+", pos)
return found
end
--compress
---@param float number
---@return integer
local function binaryFloat2Integer(float)
return unpack("i4", pack("f", float))
end
---@param integer integer
---@return number
local function binaryInteger2Float(integer)
return string.unpack("f", string.pack("i4", integer))
end
--validating parts of the file
---@param str string
---@return integer
local function getCheckNumber(str)
local checkNum = 0
for i = 1, str:len() do
checkNum = checkNum + byte(str:sub(i, i))
end
return checkNum
end
---@param str string
---@return string
local function addCheckNumber(str)
return Base62.toBase62(getCheckNumber(str)) .. "-" .. str
end
---@param str string
---@return string, boolean
local function separateAndValidateCheckNumber(str)
local separatedString = str:sub(str:find("-") + 1, str:len())
return separatedString, getCheckNumber(separatedString) == Base62.fromBase62(str:sub(1, str:find("-") - 1))
end
---@param str string
---@param seed integer
---@return string
pseudoRandomPermutation = function(str, seed)
local oldSeed = math.random(0, 2147483647)
math.randomseed(seed)
local chars = {}
for i = 1, #str do
table.insert(chars, str:sub(i, i))
end
for i = #chars, 2, -1 do
local j = math.random(i)
chars[i], chars[j] = chars[j], chars[i]
end
math.randomseed(oldSeed)
return table.concat(chars)
end
--scrambler
local chars = [[!#$&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}~]]
local scrambled = pseudoRandomPermutation(chars, SAVE_LOAD_SEED)
local SCRAMBLED = {}
local UNSCRAMBLED = {}
for i = 1, chars:len() do
SCRAMBLED[chars:sub(i, i)] = scrambled:sub(i, i)
UNSCRAMBLED[scrambled:sub(i, i)] = chars:sub(i, i)
end
local function scrambleString(whichString)
local scrambledString = ""
for i = 1, whichString:len() do
scrambledString = scrambledString .. (SCRAMBLED[whichString:sub(i, i)] or whichString:sub(i, i))
end
return scrambledString
end
local function unscrambleString(whichString)
local unscrambledString = ""
for i = 1, whichString:len() do
unscrambledString = unscrambledString .. (UNSCRAMBLED[whichString:sub(i, i)] or whichString:sub(i, i))
end
return unscrambledString
end
local function convertToIndexedTable(dynamicTable)
--you may use a table recycler here
local indexedTable = {}
for key, value in pairs(dynamicTable) do
indexedTable[#indexedTable + 1] = key
indexedTable[#indexedTable + 1] = value
end
return indexedTable
end
local function convertToDictionary(indexedTable)
--you may use a table recycler here
local dynamicTable = {}
for i = 1, #indexedTable, 2 do
dynamicTable[indexedTable[i]] = indexedTable[i + 1]
end
return dynamicTable
end
---Saves data to a single player. "list" must be a string-key table.
---If not given a playerName, it is saved with the current player name.
---Returns the resulting table in key-indexed format.
---@param p player
---@param list table
---@param playerName string?
---@param filePath string?
---@return table
function SaveLoad.saveHelperDynamic(p, list, playerName, filePath)
local indexedTable = convertToIndexedTable(list)
return SaveLoad.saveHelperIndex(p, indexedTable, playerName, filePath)
end
---Saves data to a single player. "list" must be an indexed-key table.
---If not given a playerName, it is saved with the current player name.
---Returns the "list" table, may be useful in when one uses SaveLoad.saveHelperDynamic because it calls SaveLoad.saveHelperIndex inside.
---@param p player
---@param list table<integer, any>
---@param playerName string?
---@param filePath string?
---@return table
function SaveLoad.saveHelperIndex(p, list, playerName, filePath)
local data = ""
local delimiterType
local value
for _, v in ipairs(list) do
delimiterType = getDelimiterType(v)
if delimiterList[delimiterType] == "float" then
value = binaryFloat2Integer(v)
value = Base62.toBase62(value)
elseif delimiterList[delimiterType] == "integer" then
value = Base62.toBase62(v)
else
value = tostring(v)
end
if type(v) == "boolean" then
data = data .. delimiterType .. Base62.toBase62(0) .. delimiterType
else
data = data .. delimiterType .. Base62.toBase62(string.len(value)) .. delimiterType .. value
end
end
data = addCheckNumber(data)
local encData = scrambleString(data)
if not playerName then
playerName = GetPlayerName(p)
end
local path = type(filePath) == "string" and filePath or SaveLoad.getDefaultPath(playerName)
if GetLocalPlayer() == p then
FileIO.Save(path, encData)
end
return list
end
---Loads data into a table. The table will have indexed keys.
---@param scrambledData string
---@return table<integer, any>|nil
function SaveLoad.loadHelperIndex(scrambledData)
local unscrambled = unscrambleString(scrambledData)
local oldpos = 1
local i = 1
local data, isValid = separateAndValidateCheckNumber(unscrambled)
if not isValid then
--tampering detected
return nil
end
local max = data:len()
--you may use a table recycler here
output = {}
repeat
local delType = findDelimiterTypeIndex(data, oldpos)
local _, fin, length = data:find(delType .. "(\x25w+)" .. delType, oldpos) --\x25w+ because base62
length = Base62.fromBase62(length)
oldpos = fin + length + 1
local value
if length == 0 then --boolean data always has 0 length
value = (delimiterList[delType] == "true") and true or false
goto skip
else
value = string.sub(data, fin + 1, length + fin)
end
if delimiterList[delType] == "float" then
value = binaryInteger2Float(Base62.fromBase62(value))
elseif delimiterList[delType] == "integer" then
value = math.tointeger(Base62.fromBase62(value))
end
::skip:: --skip if delimiter type was a boolean
output[i] = value
i = i + 1
until oldpos >= max
return output
end
---Loads scrambledData into a table. The table will have string keys.
---@param scrambledData string
---@return table<string, any>|nil
function SaveLoad.loadHelperDynamic(scrambledData)
local unscrambled = unscrambleString(scrambledData)
local oldpos = 1
local i = 1
local data, isValid = separateAndValidateCheckNumber(unscrambled)
if not isValid then
--tampering detected
return nil
end
local max = data:len()
--you may use a table recycler here
output = {}
repeat
local delType = findDelimiterTypeDynamic(data, oldpos)
local _, fin, length = data:find(delType .. "(\x25w+)" .. delType, oldpos) --\x25w+ because base62
length = Base62.fromBase62(length)
oldpos = fin + length + 1
local value
if length == 0 then --boolean data always has 0 length
value = (delimiterList[delType] == "true") and true or false
goto skip
else
value = string.sub(data, fin + 1, length + fin)
end
if delimiterList[delType] == "float" then
value = binaryInteger2Float(Base62.fromBase62(value))
elseif delimiterList[delType] == "integer" then
value = math.tointeger(Base62.fromBase62(value))
end
::skip:: --skip if delimiter type was a boolean
output[i] = value
i = i + 1
until oldpos >= max
local dictionaryTable = convertToDictionary(output)
--recycle the table if you have a table recycler
output = nil
return dictionaryTable
end
end)
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "Encoder62" end
OnInit.root("Encoder62", function()
-- Alphanumeric character set for Base62
local base62Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
local fmod = math.fmod
Base62 = {}
---Convert a number to a Base62 string
---@param num number
---@return string
function Base62.toBase62(num)
local base = 62
local result = ""
local isNegative = false
if num < 0 then
num = -num
isNegative = true
end
repeat
-- Get the remainder when dividing the number by Base62
local remainder = fmod(num, base)
-- Map the remainder to the corresponding Base62 character
result = base62Chars:sub(remainder + 1, remainder + 1) .. result
-- Update the number (integer division by base)
num = math.tointeger(num // base)
until num == 0
return isNegative and "-" .. result or result
end
---Convert a Base62 string back to a number
---@param base62Str string
---@return number
function Base62.fromBase62(base62Str)
local base = 62
local num = 0
local isNegative = false
if base62Str:sub(1, 1) == "-" then
isNegative = true
base62Str = base62Str:sub(2, base62Str:len())
end
for i = 1, #base62Str do
-- Get the value of the current character in Base62
local char = base62Str:sub(i, i)
local value = base62Chars:find(char) - 1 -- find returns 1-based index
-- Accumulate the value into the result
num = num * base + value
end
return isNegative and -num or num
end
end)
if Debug then Debug.endFile() end
do
local funcs = {}
function onInit(code)
if type(code) == "function" then
table.insert(funcs, code)
end
end
local old = InitBlizzard
function InitBlizzard()
old()
for i = 1, #funcs do
funcs[i]()
end
funcs = nil
end
end
--[[ requires Indexer, TimedHandles, RegisterPlayerUnitEvent
-- --------------------------------------- Utilities v1.8 --------------------------------------- --
-- How to Import:
-- 1 - Copy this library into your map
-- 2 - Copy the dummy unit in object editor and match its raw code below
-- 3 - Copy the Indexer library over to your map and follow its install instructions
-- 4 - Copy the TimedHandles library over to your map and follow its install instructions
-- 5 - Copy the RegisterPlayerUnitEvent library over to your map and follow its install instructions
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
do
-- ---------------------------------------------------------------------------------------------- --
-- Configuration --
-- ---------------------------------------------------------------------------------------------- --
-- The dummy caster unit id
local DUMMY = FourCC('dumi')
-- Update period
local PERIOD = 0.031250000
-- location z
local location = Location(0,0)
-- Closest Unit
local bj_closestUnitGroup
-- ---------------------------------------------------------------------------------------------- --
-- LUA API --
-- ---------------------------------------------------------------------------------------------- --
-- Returns the terrain Z value
function GetLocZ(x, y)
MoveLocation(location, x, y)
return GetLocationZ(location)
end
-- Similar to GetUnitX and GetUnitY but for Z axis
function GetUnitZ(unit)
return GetLocZ(GetUnitX(unit), GetUnitY(unit)) + GetUnitFlyHeight(unit)
end
-- Similar to SetUnitX and SetUnitY but for Z axis
function SetUnitZ(unit, z)
SetUnitFlyHeight(unit, z - GetLocZ(GetUnitX(unit), GetUnitY(unit)), 0)
end
-- Anlge between 2D points
function AngleBetweenCoordinates(x, y, x2, y2)
return Atan2(y2 - y, x2 - x)
end
-- Similar to AddSpecialEffect but scales the effect and considers z and return it
function AddSpecialEffectEx(effect, x, y, z, scale)
bj_lastCreatedEffect = AddSpecialEffect(effect, x, y)
if z ~= 0 then
BlzSetSpecialEffectZ(bj_lastCreatedEffect, z + GetLocZ(x, y))
end
BlzSetSpecialEffectScale(bj_lastCreatedEffect, scale)
return bj_lastCreatedEffect
end
-- Returns a group of enemy units of the specified player within the specified AOE of x and y
function GetEnemyUnitsInRange(player, x, y, aoe, structures, magicImmune)
local group = CreateGroup()
local g = CreateGroup()
local unit
GroupEnumUnitsInRange(g, x, y, aoe, null)
if structures and magicImmune then
for i = 0, BlzGroupGetSize(g) - 1 do
unit = BlzGroupUnitAt(g, i)
if IsUnitEnemy(unit, player) and UnitAlive(unit) then
GroupAddUnit(group, unit)
end
end
elseif structures and not magicImmune then
for i = 0, BlzGroupGetSize(g) - 1 do
unit = BlzGroupUnitAt(g, i)
if IsUnitEnemy(unit, player) and UnitAlive(unit) and not IsUnitType(unit, UNIT_TYPE_MAGIC_IMMUNE) then
GroupAddUnit(group, unit)
end
end
elseif magicImmune and not structures then
for i = 0, BlzGroupGetSize(g) - 1 do
unit = BlzGroupUnitAt(g, i)
if IsUnitEnemy(unit, player) and UnitAlive(unit) and not IsUnitType(unit, UNIT_TYPE_STRUCTURE) then
GroupAddUnit(group, unit)
end
end
else
for i = 0, BlzGroupGetSize(g) - 1 do
unit = BlzGroupUnitAt(g, i)
if IsUnitEnemy(unit, player) and UnitAlive(unit) and not IsUnitType(unit, UNIT_TYPE_STRUCTURE) and not IsUnitType(unit, UNIT_TYPE_MAGIC_IMMUNE) then
GroupAddUnit(group, unit)
end
end
end
DestroyGroup(g)
return group
end
-- Returns the closest unit in a unit group with center at x and y
function GetClosestUnitGroup(x, y, group)
local md = 100000
local unit
local dx
local dy
bj_closestUnitGroup = nil
for i = 0, BlzGroupGetSize(group) - 1 do
unit = BlzGroupUnitAt(group, i)
if UnitAlive(unit) then
dx = GetUnitX(unit) - x
dy = GetUnitY(unit) - y
if (dx*dx + dy*dy)/100000 < md then
bj_closestUnitGroup = unit
md = (dx*dx + dy*dy)/100000
end
end
end
return bj_closestUnitGroup
end
-- Link an effect to a unit buff or ability
function LinkEffectToBuff(unit, buffId, model, attach)
EffectLink:buff(unit, buffId, model, attach)
end
-- Link an effect to an unit item.
function LinkEffectToItem(unit, i, model, attach)
EffectLink:item(unit, i, model, attach)
end
-- Spams the specified effect model at a location with the given interval for the number of times count
function SpamEffect(model, x, y, z, scale, interval, count)
local timer = CreateTimer()
TimerStart(timer, interval, true, function()
if count > 0 then
DestroyEffect(AddSpecialEffectEx(model, x, y, z, scale))
else
PauseTimer(timer)
DestroyTimer(timer)
end
count = count - 1
end)
end
-- Spams the specified effect model attached to a unit for the given interval for the number of times count
function SpamEffectUnit(unit, model, attach, interval, count)
local timer = CreateTimer()
TimerStart(timer, interval, true, function()
if count > 0 then
DestroyEffect(AddSpecialEffectTarget(model, unit, attach))
else
PauseTimer(timer)
DestroyTimer(timer)
end
count = count - 1
end)
end
-- Add the specified ability to the specified unit for the given duration. Use hide to show or not the ability button.
function UnitAddAbilityTimed(unit, ability, duration, level, hide)
TimedAbility:add(unit, ability, duration, level, hide)
end
-- Resets the specified unit ability cooldown
function ResetUnitAbilityCooldown(unit, ability)
local timer = CreateTimer()
TimerStart(timer, 0.01, false, function()
BlzEndUnitAbilityCooldown(unit, ability)
PauseTimer(timer)
DestroyTimer(timer)
end)
end
-- Returns the distance between 2 coordinates in Warcraft III units
function DistanceBetweenCoordinates(x1, y1, x2, y2)
local dx = (x2 - x1)
local dy = (y2 - y1)
return SquareRoot(dx*dx + dy*dy)
end
-- Makes the specified source damage an area respecting some basic unit filters
function UnitDamageArea(unit, x, y, aoe, damage, attacktype, damagetype, structures, magicImmune, allies)
local group = CreateGroup()
GroupEnumUnitsInRange(group, x, y, aoe, null)
GroupRemoveUnit(group, unit)
if allies then
if structures and magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
if UnitAlive(u) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif structures and not magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
if UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif magicImmune and not structures then
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
if UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
else
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
if UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
end
else
if structures and magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
local player = GetOwningPlayer(unit)
if IsUnitEnemy(u, player) and UnitAlive(u) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif structures and not magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
local player = GetOwningPlayer(unit)
if IsUnitEnemy(u, player) and UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif magicImmune and not structures then
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
local player = GetOwningPlayer(unit)
if IsUnitEnemy(u, player) and UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
else
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
local player = GetOwningPlayer(unit)
if IsUnitEnemy(u, player) and UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
end
end
DestroyGroup(group)
end
-- Makes the specified source damage a group. Creates a special effect if specified
function UnitDamageGroup(unit, group, damage, attacktype, damagetype, effect, attach, destroy)
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, nil)
if effect and attach then
DestroyEffect(AddSpecialEffectTarget(effect, u, attach))
end
end
if destroy then
DestroyGroup(group)
end
return group
end
-- Returns a random range given a max value
function GetRandomRange(radius)
local r = GetRandomReal(0, 1) + GetRandomReal(0, 1)
if r > 1 then
return (2 - r)*radius
end
return r*radius
end
-- Returns a random value in the x/y coordinates depending on the value of boolean x
function GetRandomCoordInRange(center, radius, x)
local theta = 2*bj_PI*GetRandomReal(0, 1)
local r
if x then
r = center + radius*Cos(theta)
else
r = center + radius*Sin(theta)
end
return r
end
-- Clones the items in the source unit inventory to the target unit
function CloneItems(source, target, isIllusion)
for i = 0, bj_MAX_INVENTORY do
local item = UnitItemInSlot(source, i)
local j = GetItemCharges(item)
item = CreateItem(GetItemTypeId(item), GetUnitX(target), GetUnitY(target))
SetItemCharges(item, j)
UnitAddItem(target, item)
if isIllusion then
if GetItemTypeId(item) == FourCC('ankh') then
BlzItemRemoveAbility(item, FourCC('AIrc'))
end
BlzSetItemBooleanField(item, ITEM_BF_ACTIVELY_USED, false)
end
end
end
-- Add the mount for he unit mana pool
function AddUnitMana(unit, real)
SetUnitState(unit, UNIT_STATE_MANA, (GetUnitState(unit, UNIT_STATE_MANA) + real))
end
-- Add the specified amounts to a hero str/agi/int base amount
function UnitAddStat(unit, strength, agility, intelligence)
if strength ~= 0 then
SetHeroStr(unit, GetHeroStr(unit, false) + strength, true)
end
if agility ~= 0 then
SetHeroAgi(unit, GetHeroAgi(unit, false) + agility, true)
end
if intelligence ~= 0 then
SetHeroInt(unit, GetHeroInt(unit, false) + intelligence, true)
end
end
-- Returns the closest unit from the x and y coordinates in the map
function GetClosestUnit(x, y, boolexpr)
local group = CreateGroup()
local md = 100000
bj_closestUnitGroup = nil
GroupEnumUnitsInRect(group, bj_mapInitialPlayableArea, boolexpr)
for i = 0, BlzGroupGetSize(group) - 1 do
local unit = BlzGroupUnitAt(group, i)
if UnitAlive(unit) then
local dx = GetUnitX(unit) - x
local dy = GetUnitY(unit) - y
if (dx*dx + dy*dy)/100000 < md then
bj_closestUnitGroup = unit
md = (dx*dx + dy*dy)/100000
end
end
end
DestroyGroup(group)
DestroyBoolExpr(boolexpr)
return bj_closestUnitGroup
end
-- Creates a chain lightning with the specified ligihtning effect with the amount of bounces
function CreateChainLightning(source, target, damage, aoe, duration, interval, bounces, attacktype, damagetype, lightning, effect, attach, rebounce)
local player = GetOwningPlayer(source)
local group = GetEnemyUnitsInRange(player, GetUnitX(target), GetUnitY(target), aoe, false, false)
if BlzGroupGetSize(group) == 1 then
DestroyLightningTimed(AddLightningEx(lightning, true, GetUnitX(source), GetUnitY(source), GetUnitZ(source) + 60.0, GetUnitX(target), GetUnitY(target), GetUnitZ(target) + 60.0), duration)
DestroyEffect(AddSpecialEffectTarget(effect, target, attach))
UnitDamageTarget(source, target, damage, false, false, attacktype, damagetype, nil)
DestroyGroup(group)
else
local timer = CreateTimer()
local damaged = CreateGroup()
local prev = nil
local this = target
local next = nil
GroupRemoveUnit(group, this)
GroupAddUnit(damaged, this)
UnitDamageTarget(source, this, damage, false, false, attacktype, damagetype, nil)
DestroyEffect(AddSpecialEffectTarget(effect, this, attach))
TimerStart(timer, interval, true, function()
DestroyGroup(group)
if bounces > 0 then
group = GetEnemyUnitsInRange(player, GetUnitX(this), GetUnitY(this), aoe, false, false)
GroupRemoveUnit(group, this)
if not rebounce then
BlzGroupRemoveGroupFast(damaged, group)
end
if BlzGroupGetSize(group) == 0 then
PauseTimer(timer)
DestroyTimer(timer)
DestroyGroup(group)
DestroyGroup(damaged)
else
next = GetClosestUnitGroup(GetUnitX(this), GetUnitY(this), group)
if next == prev and BlzGroupGetSize(group) > 1 then
GroupRemoveUnit(group, prev)
next = GetClosestUnitGroup(GetUnitX(this), GetUnitY(this), group)
end
if next then
DestroyLightningTimed(AddLightningEx(lightning, true, GetUnitX(this), GetUnitY(this), GetUnitZ(this) + 60.0, GetUnitX(next), GetUnitY(next), GetUnitZ(next) + 60.0), duration)
DestroyEffect(AddSpecialEffectTarget(effect, next, attach))
GroupAddUnit(damaged, next)
UnitDamageTarget(source, next, damage, false, false, attacktype, damagetype, nil)
DestroyGroup(group)
prev = this
this = next
next = nil
else
PauseTimer(timer)
DestroyTimer(timer)
DestroyGroup(group)
DestroyGroup(damaged)
end
end
else
PauseTimer(timer)
DestroyTimer(timer)
DestroyGroup(group)
DestroyGroup(damaged)
end
bounces = bounces - 1
end)
end
DestroyGroup(group)
end
-- Add the specified amount to the specified player gold amount
function AddPlayerGold(player, amount)
SetPlayerState(player, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(player, PLAYER_STATE_RESOURCE_GOLD) + amount)
end
-- Creates a text tag in an unit position for a duration
function CreateTextOnUnit(unit, string, duration, red, green, blue, alpha)
local texttag = CreateTextTag()
SetTextTagText(texttag, string, 0.015)
SetTextTagPosUnit(texttag, unit, 0)
SetTextTagColor(texttag, red, green, blue, alpha)
SetTextTagLifespan(texttag, duration)
SetTextTagVelocity(texttag, 0.0, 0.0355)
SetTextTagPermanent(texttag, false)
end
-- Add health regeneration to the unit base value
function UnitAddHealthRegen(unit, regen)
BlzSetUnitRealField(unit, UNIT_RF_HIT_POINTS_REGENERATION_RATE, BlzGetUnitRealField(unit, UNIT_RF_HIT_POINTS_REGENERATION_RATE) + regen)
end
-- Retrieves a dummy from the pool. Facing angle in radians
function DummyRetrieve(player, x, y, z, face)
return DummyPool:retrieve(player, x, y, z, face)
end
-- Recycles a dummy unit type, putting it back into the pool.
function DummyRecycle(unit)
DummyPool:recycle(unit)
end
-- Recycles a dummy with a delay.
function DummyRecycleTimed(unit, delay)
DummyPool:timed(unit, delay)
end
-- Casts an ability in the target unit. Must have no casting time
function CastAbilityTarget(unit, ability, order, level)
local dummy = DummyRetrieve(GetOwningPlayer(unit), 0, 0, 0, 0)
UnitAddAbility(dummy, ability)
SetUnitAbilityLevel(dummy, ability, level)
IssueTargetOrder(dummy, order, unit)
UnitRemoveAbility(dummy, ability)
DummyRecycle(dummy)
end
-- Returns a random unit within a group
function GroupPickRandomUnitEx(group)
if BlzGroupGetSize(group) > 0 then
return BlzGroupUnitAt(group, GetRandomInt(0, BlzGroupGetSize(group) - 1))
else
return nil
end
end
-- Returns true if a unit is within a cone given a facing and fov angle in degrees (Less precise)
function IsUnitInConeEx(unit, x, y, face, fov)
return Acos(Cos((Atan2(GetUnitY(unit) - y, GetUnitX(unit) - x)) - face*bj_DEGTORAD)) < fov*bj_DEGTORAD/2
end
-- Returns true if a unit is within a cone given a facing, fov angle and a range in degrees (takes collision into consideration). Credits to AGD.
function IsUnitInCone(unit, x, y, range, face, fov)
if IsUnitInRangeXY(unit, x, y, range) then
x = GetUnitX(unit) - x
y = GetUnitY(unit) - y
range = x*x + y*y
if range > 0 then
face = face*bj_DEGTORAD - Atan2(y, x)
fov = fov*bj_DEGTORAD/2 + Asin(BlzGetUnitCollisionSize(unit)/SquareRoot(range))
return RAbsBJ(face) <= fov or RAbsBJ(face - 2.00*bj_PI) <= fov
end
return true
end
return false
end
-- Makes the source unit damage enemy unit in a cone given a direction, foy and range
function UnitDamageCone(unit, x, y, face, fov, aoe, damage, attacktype, damagetype, structures, magicImmune, allies)
local group = CreateGroup()
local player = GetOwningPlayer(unit)
local u
GroupEnumUnitsInRange(group, x, y, aoe, null)
GroupRemoveUnit(group, unit)
if allies then
if structures and magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if UnitAlive(u) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif structures and not magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif magicImmune and not structures then
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
else
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
end
else
if structures and magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if IsUnitEnemy(u, player) and UnitAlive(u) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif structures and not magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if IsUnitEnemy(u, player) and UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif magicImmune and not structures then
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if IsUnitEnemy(u, player) and UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
else
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if IsUnitEnemy(u, player) and UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
end
end
DestroyGroup(group)
end
-- Heals all allied units of specified player in an area
function HealArea(player, x, y, aoe, amount, effect, attach)
local group = CreateGroup()
local unit
GroupEnumUnitsInRange(group, x, y, aoe, null)
for i = 0, BlzGroupGetSize(group) - 1 do
unit = BlzGroupUnitAt(group, i)
if IsUnitAlly(unit, player) and UnitAlive(unit) and not IsUnitType(unit, UNIT_TYPE_STRUCTURE) then
SetWidgetLife(unit, GetWidgetLife(unit) + amount)
if effect ~= "" then
if attach ~= "" then
DestroyEffect(AddSpecialEffectTarget(effect, unit, attach))
else
DestroyEffect(AddSpecialEffect(effect, GetUnitX(unit), GetUnitY(unit)))
end
end
end
end
DestroyGroup(group)
end
-- Pretty obvious.
function R2I2S(real)
return I2S(R2I(real))
end
-- Returns an ability real level field as a string. Usefull for toolltip manipulation.
function AbilityRealField(unit, ability, field, level, multiplier, integer)
if integer then
return R2I2S(BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ability), field, level)*multiplier)
else
return R2SW(BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ability), field, level)*multiplier, 1, 1)
end
end
-- Fix for camera pan desync. credits do Daffa
function SmartCameraPanBJModified(player, loc, duration)
local x = GetLocationX(loc)
local y = GetLocationY(loc)
local dx = x - GetCameraTargetPositionX()
local dy = y - GetCameraTargetPositionY()
local dist = SquareRoot(dx*dx + dy*dy)
if GetLocalPlayer() == player then
if dist >= bj_SMARTPAN_TRESHOLD_SNAP then
PanCameraToTimed(x, y, duration)
elseif dist >= bj_SMARTPAN_TRESHOLD_PAN then
PanCameraToTimed(x, y, duration)
else
-- User is close, dont move camera
end
end
end
-- Fix for camera pan desync. credits do Daffa
function SmartCameraPanBJModifiedXY(player, x, y, duration)
local dx = x - GetCameraTargetPositionX()
local dy = y - GetCameraTargetPositionY()
local dist = SquareRoot(dx*dx + dy*dy)
if GetLocalPlayer() == player then
if dist >= bj_SMARTPAN_TRESHOLD_SNAP then
PanCameraToTimed(x, y, duration)
elseif dist >= bj_SMARTPAN_TRESHOLD_PAN then
PanCameraToTimed(x, y, duration)
else
-- User is close, dont move camera
end
end
end
-- Start the cooldown for the source unit unit the new value
function StartUnitAbilityCooldown(unit, ability, cooldown)
local timer = CreateTimer()
TimerStart(timer, 0.01, false, function()
BlzStartUnitAbilityCooldown(unit, ability, cooldown)
PauseTimer(timer)
DestroyTimer(timer)
end)
end
-- ---------------------------------------------------------------------------------------------- --
-- Systems --
-- ---------------------------------------------------------------------------------------------- --
-- ----------------------------------------- Dummy Pool ----------------------------------------- --
do
local group = CreateGroup()
local player = Player(PLAYER_NEUTRAL_PASSIVE)
DummyPool = setmetatable({}, {})
local mt = getmetatable(DummyPool)
mt.__index = mt
function mt:recycle(unit)
if GetUnitTypeId(unit) ~= DUMMY then
print("[DummyPool] Error: Trying to recycle a non dummy unit")
else
GroupAddUnit(group, unit)
SetUnitX(unit, WorldBounds.maxX)
SetUnitY(unit, WorldBounds.maxY)
SetUnitOwner(unit, player, false)
ShowUnit(unit, false)
BlzPauseUnitEx(unit, true)
end
end
function mt:retrieve(owner, x, y, z, face)
if BlzGroupGetSize(group) > 0 then
bj_lastCreatedUnit = FirstOfGroup(group)
BlzPauseUnitEx(bj_lastCreatedUnit, false)
ShowUnit(bj_lastCreatedUnit, true)
GroupRemoveUnit(group, bj_lastCreatedUnit)
SetUnitX(bj_lastCreatedUnit, x)
SetUnitY(bj_lastCreatedUnit, y)
SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
BlzSetUnitFacingEx(bj_lastCreatedUnit, face*bj_RADTODEG)
SetUnitOwner(bj_lastCreatedUnit, owner, false)
else
bj_lastCreatedUnit = CreateUnit(owner, DUMMY, x, y, face*bj_RADTODEG)
SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
end
return bj_lastCreatedUnit
end
function mt:timed(unit, delay)
local timer = CreateTimer()
if GetUnitTypeId(unit) ~= DUMMY then
print("[DummyPool] Error: Trying to recycle a non dummy unit")
else
TimerStart(timer, delay, false, function()
GroupAddUnit(group, unit)
SetUnitX(unit, WorldBounds.maxX)
SetUnitY(unit, WorldBounds.maxY)
SetUnitOwner(unit, player, false)
ShowUnit(unit, false)
BlzPauseUnitEx(unit, true)
PauseTimer(timer)
DestroyTimer(timer)
end)
end
end
onInit(function()
local timer = CreateTimer()
local unit
TimerStart(timer, 0, false, function()
for i = 0, 20 do
unit = CreateUnit(player, DUMMY, WorldBounds.maxX, WorldBounds.maxY, 0)
BlzPauseUnitEx(unit, false)
GroupAddUnit(group, unit)
end
PauseTimer(timer)
DestroyTimer(timer)
end)
end)
end
-- ---------------------------------------- Timed Ability --------------------------------------- --
do
TimedAbility = setmetatable({}, {})
local mt = getmetatable(TimedAbility)
mt.__index = mt
local timer = CreateTimer()
local ability = {}
local array = {}
local key = 0
function mt:destroy(i)
UnitRemoveAbility(self.unit, self.id)
array[i] = array[key]
key = key - 1
ability[self.unit][self.id] = nil
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:add(unit, id, duration, level, hide)
if not ability[unit] then ability[unit] = {} end
local this = ability[unit][id]
if not this then
this = {}
setmetatable(this, mt)
this.unit = unit
this.id = id
key = key + 1
array[key] = this
ability[unit][id] = this
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.duration <= 0 then
i = this:destroy(i)
end
this.duration = this.duration - PERIOD
i = i + 1
end
end)
end
end
if GetUnitAbilityLevel(unit, id) ~= level then
UnitAddAbility(unit, id)
SetUnitAbilityLevel(unit, id, level)
UnitMakeAbilityPermanent(unit, true, id)
BlzUnitHideAbility(unit, id, hide)
end
this.duration = duration
end
end
-- ----------------------------------------- Effect Link ---------------------------------------- --
do
EffectLink = setmetatable({}, {})
local mt = getmetatable(EffectLink)
mt.__index = mt
local timer = CreateTimer()
local array = {}
local items = {}
local key = 0
local k = 0
function mt:destroy(i, item)
DestroyEffect(self.effect)
if item then
items[i] = items[k]
k = k - 1
else
array[i] = array[key]
key = key - 1
if key == 0 then
PauseTimer(timer)
end
end
self = nil
return i - 1
end
function mt:buff(unit, buffId, model, attach)
local this = {}
setmetatable(this, mt)
key = key + 1
array[key] = this
this.unit = unit
this.buff = buffId
this.effect = AddSpecialEffectTarget(model, unit, attach)
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if GetUnitAbilityLevel(this.unit, this.buff) == 0 then
i = this:destroy(i, false)
end
i = i + 1
end
end)
end
end
function mt:item(unit, i, model, attach)
local this = {}
setmetatable(this, mt)
k = k + 1
items[k] = this
this.item = i
this.effect = AddSpecialEffectTarget(model, unit, attach)
end
onInit(function()
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DROP_ITEM, function()
local item = GetManipulatedItem()
local i = 1
local this
while i <= k do
this = items[i]
if this.item == item then
i = this:destroy(i, true)
end
i = i + 1
end
end)
end)
end
end
if Debug and Debug.beginFile then Debug.beginFile("Utilities") end
--[[ requires Indexer, TimedHandles, RegisterPlayerUnitEvent
-- --------------------------------------- Utilities v1.8 --------------------------------------- --
-- How to Import:
-- 1 - Copy this library into your map
-- 2 - Copy the dummy unit in object editor and match its raw code below
-- 3 - Copy the Indexer library over to your map and follow its install instructions
-- 4 - Copy the TimedHandles library over to your map and follow its install instructions
-- 5 - Copy the RegisterPlayerUnitEvent library over to your map and follow its install instructions
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
do
-- ---------------------------------------------------------------------------------------------- --
-- Configuration --
-- ---------------------------------------------------------------------------------------------- --
-- The dummy caster unit id
local DUMMY = FourCC('dumi')
-- Update period
local PERIOD = 0.031250000
-- location z
local location = Location(0,0)
-- Closest Unit
local bj_closestUnitGroup
-- ---------------------------------------------------------------------------------------------- --
-- LUA API --
-- ---------------------------------------------------------------------------------------------- --
-- Returns the terrain Z value
function GetLocZ(x, y)
MoveLocation(location, x, y)
return GetLocationZ(location)
end
-- Similar to GetUnitX and GetUnitY but for Z axis
function GetUnitZ(unit)
return GetLocZ(GetUnitX(unit), GetUnitY(unit)) + GetUnitFlyHeight(unit)
end
-- Similar to SetUnitX and SetUnitY but for Z axis
function SetUnitZ(unit, z)
SetUnitFlyHeight(unit, z - GetLocZ(GetUnitX(unit), GetUnitY(unit)), 0)
end
-- Anlge between 2D points
function AngleBetweenCoordinates(x, y, x2, y2)
return Atan2(y2 - y, x2 - x)
end
-- Similar to AddSpecialEffect but scales the effect and considers z and return it
function AddSpecialEffectEx(effect, x, y, z, scale)
bj_lastCreatedEffect = AddSpecialEffect(effect, x, y)
if z ~= 0 then
BlzSetSpecialEffectZ(bj_lastCreatedEffect, z + GetLocZ(x, y))
end
BlzSetSpecialEffectScale(bj_lastCreatedEffect, scale)
return bj_lastCreatedEffect
end
-- Returns a group of enemy units of the specified player within the specified AOE of x and y
function GetEnemyUnitsInRange(player, x, y, aoe, structures, magicImmune)
local group = CreateGroup()
local g = CreateGroup()
local unit
GroupEnumUnitsInRange(g, x, y, aoe, null)
if structures and magicImmune then
for i = 0, BlzGroupGetSize(g) - 1 do
unit = BlzGroupUnitAt(g, i)
if IsUnitEnemy(unit, player) and UnitAlive(unit) then
GroupAddUnit(group, unit)
end
end
elseif structures and not magicImmune then
for i = 0, BlzGroupGetSize(g) - 1 do
unit = BlzGroupUnitAt(g, i)
if IsUnitEnemy(unit, player) and UnitAlive(unit) and not IsUnitType(unit, UNIT_TYPE_MAGIC_IMMUNE) then
GroupAddUnit(group, unit)
end
end
elseif magicImmune and not structures then
for i = 0, BlzGroupGetSize(g) - 1 do
unit = BlzGroupUnitAt(g, i)
if IsUnitEnemy(unit, player) and UnitAlive(unit) and not IsUnitType(unit, UNIT_TYPE_STRUCTURE) then
GroupAddUnit(group, unit)
end
end
else
for i = 0, BlzGroupGetSize(g) - 1 do
unit = BlzGroupUnitAt(g, i)
if IsUnitEnemy(unit, player) and UnitAlive(unit) and not IsUnitType(unit, UNIT_TYPE_STRUCTURE) and not IsUnitType(unit, UNIT_TYPE_MAGIC_IMMUNE) then
GroupAddUnit(group, unit)
end
end
end
DestroyGroup(g)
return group
end
-- Returns the closest unit in a unit group with center at x and y
function GetClosestUnitGroup(x, y, group)
local md = 100000
local unit
local dx
local dy
bj_closestUnitGroup = nil
for i = 0, BlzGroupGetSize(group) - 1 do
unit = BlzGroupUnitAt(group, i)
if UnitAlive(unit) then
dx = GetUnitX(unit) - x
dy = GetUnitY(unit) - y
if (dx*dx + dy*dy)/100000 < md then
bj_closestUnitGroup = unit
md = (dx*dx + dy*dy)/100000
end
end
end
return bj_closestUnitGroup
end
-- Link an effect to a unit buff or ability
function LinkEffectToBuff(unit, buffId, model, attach)
EffectLink:buff(unit, buffId, model, attach)
end
-- Link an effect to an unit item.
function LinkEffectToItem(unit, i, model, attach)
EffectLink:item(unit, i, model, attach)
end
-- Spams the specified effect model at a location with the given interval for the number of times count
function SpamEffect(model, x, y, z, scale, interval, count)
local timer = CreateTimer()
TimerStart(timer, interval, true, function()
if count > 0 then
DestroyEffect(AddSpecialEffectEx(model, x, y, z, scale))
else
PauseTimer(timer)
DestroyTimer(timer)
end
count = count - 1
end)
end
-- Spams the specified effect model attached to a unit for the given interval for the number of times count
function SpamEffectUnit(unit, model, attach, interval, count)
local timer = CreateTimer()
TimerStart(timer, interval, true, function()
if count > 0 then
DestroyEffect(AddSpecialEffectTarget(model, unit, attach))
else
PauseTimer(timer)
DestroyTimer(timer)
end
count = count - 1
end)
end
-- Add the specified ability to the specified unit for the given duration. Use hide to show or not the ability button.
function UnitAddAbilityTimed(unit, ability, duration, level, hide)
TimedAbility:add(unit, ability, duration, level, hide)
end
-- Resets the specified unit ability cooldown
function ResetUnitAbilityCooldown(unit, ability)
local timer = CreateTimer()
TimerStart(timer, 0.01, false, function()
BlzEndUnitAbilityCooldown(unit, ability)
PauseTimer(timer)
DestroyTimer(timer)
end)
end
-- Returns the distance between 2 coordinates in Warcraft III units
function DistanceBetweenCoordinates(x1, y1, x2, y2)
local dx = (x2 - x1)
local dy = (y2 - y1)
return SquareRoot(dx*dx + dy*dy)
end
-- Makes the specified source damage an area respecting some basic unit filters
function UnitDamageArea(unit, x, y, aoe, damage, attacktype, damagetype, structures, magicImmune, allies)
local group = CreateGroup()
GroupEnumUnitsInRange(group, x, y, aoe, null)
GroupRemoveUnit(group, unit)
if allies then
if structures and magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
if UnitAlive(u) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif structures and not magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
if UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif magicImmune and not structures then
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
if UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
else
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
if UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
end
else
if structures and magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
local player = GetOwningPlayer(unit)
if IsUnitEnemy(u, player) and UnitAlive(u) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif structures and not magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
local player = GetOwningPlayer(unit)
if IsUnitEnemy(u, player) and UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif magicImmune and not structures then
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
local player = GetOwningPlayer(unit)
if IsUnitEnemy(u, player) and UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
else
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
local player = GetOwningPlayer(unit)
if IsUnitEnemy(u, player) and UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
end
end
DestroyGroup(group)
end
-- Makes the specified source damage a group. Creates a special effect if specified
function UnitDamageGroup(unit, group, damage, attacktype, damagetype, effect, attach, destroy)
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, nil)
if effect and attach then
DestroyEffect(AddSpecialEffectTarget(effect, u, attach))
end
end
if destroy then
DestroyGroup(group)
end
return group
end
-- Returns a random range given a max value
function GetRandomRange(radius)
local r = GetRandomReal(0, 1) + GetRandomReal(0, 1)
if r > 1 then
return (2 - r)*radius
end
return r*radius
end
-- Returns a random value in the x/y coordinates depending on the value of boolean x
function GetRandomCoordInRange(center, radius, x)
local theta = 2*bj_PI*GetRandomReal(0, 1)
local r
if x then
r = center + radius*Cos(theta)
else
r = center + radius*Sin(theta)
end
return r
end
-- Clones the items in the source unit inventory to the target unit
function CloneItems(source, target, isIllusion)
for i = 0, bj_MAX_INVENTORY do
local item = UnitItemInSlot(source, i)
local j = GetItemCharges(item)
item = CreateItem(GetItemTypeId(item), GetUnitX(target), GetUnitY(target))
SetItemCharges(item, j)
UnitAddItem(target, item)
if isIllusion then
if GetItemTypeId(item) == FourCC('ankh') then
BlzItemRemoveAbility(item, FourCC('AIrc'))
end
BlzSetItemBooleanField(item, ITEM_BF_ACTIVELY_USED, false)
end
end
end
-- Add the mount for he unit mana pool
function AddUnitMana(unit, real)
SetUnitState(unit, UNIT_STATE_MANA, (GetUnitState(unit, UNIT_STATE_MANA) + real))
end
-- Add the specified amounts to a hero str/agi/int base amount
function UnitAddStat(unit, strength, agility, intelligence)
if strength ~= 0 then
SetHeroStr(unit, GetHeroStr(unit, false) + strength, true)
end
if agility ~= 0 then
SetHeroAgi(unit, GetHeroAgi(unit, false) + agility, true)
end
if intelligence ~= 0 then
SetHeroInt(unit, GetHeroInt(unit, false) + intelligence, true)
end
end
-- Returns the closest unit from the x and y coordinates in the map
function GetClosestUnit(x, y, boolexpr)
local group = CreateGroup()
local md = 100000
bj_closestUnitGroup = nil
GroupEnumUnitsInRect(group, bj_mapInitialPlayableArea, boolexpr)
for i = 0, BlzGroupGetSize(group) - 1 do
local unit = BlzGroupUnitAt(group, i)
if UnitAlive(unit) then
local dx = GetUnitX(unit) - x
local dy = GetUnitY(unit) - y
if (dx*dx + dy*dy)/100000 < md then
bj_closestUnitGroup = unit
md = (dx*dx + dy*dy)/100000
end
end
end
DestroyGroup(group)
DestroyBoolExpr(boolexpr)
return bj_closestUnitGroup
end
-- Creates a chain lightning with the specified ligihtning effect with the amount of bounces
function CreateChainLightning(source, target, damage, aoe, duration, interval, bounces, attacktype, damagetype, lightning, effect, attach, rebounce)
local player = GetOwningPlayer(source)
local group = GetEnemyUnitsInRange(player, GetUnitX(target), GetUnitY(target), aoe, false, false)
if BlzGroupGetSize(group) == 1 then
DestroyLightningTimed(AddLightningEx(lightning, true, GetUnitX(source), GetUnitY(source), GetUnitZ(source) + 60.0, GetUnitX(target), GetUnitY(target), GetUnitZ(target) + 60.0), duration)
DestroyEffect(AddSpecialEffectTarget(effect, target, attach))
UnitDamageTarget(source, target, damage, false, false, attacktype, damagetype, nil)
DestroyGroup(group)
else
local timer = CreateTimer()
local damaged = CreateGroup()
local prev = nil
local this = target
local next = nil
GroupRemoveUnit(group, this)
GroupAddUnit(damaged, this)
UnitDamageTarget(source, this, damage, false, false, attacktype, damagetype, nil)
DestroyEffect(AddSpecialEffectTarget(effect, this, attach))
TimerStart(timer, interval, true, function()
DestroyGroup(group)
if bounces > 0 then
group = GetEnemyUnitsInRange(player, GetUnitX(this), GetUnitY(this), aoe, false, false)
GroupRemoveUnit(group, this)
if not rebounce then
BlzGroupRemoveGroupFast(damaged, group)
end
if BlzGroupGetSize(group) == 0 then
PauseTimer(timer)
DestroyTimer(timer)
DestroyGroup(group)
DestroyGroup(damaged)
else
next = GetClosestUnitGroup(GetUnitX(this), GetUnitY(this), group)
if next == prev and BlzGroupGetSize(group) > 1 then
GroupRemoveUnit(group, prev)
next = GetClosestUnitGroup(GetUnitX(this), GetUnitY(this), group)
end
if next then
DestroyLightningTimed(AddLightningEx(lightning, true, GetUnitX(this), GetUnitY(this), GetUnitZ(this) + 60.0, GetUnitX(next), GetUnitY(next), GetUnitZ(next) + 60.0), duration)
DestroyEffect(AddSpecialEffectTarget(effect, next, attach))
GroupAddUnit(damaged, next)
UnitDamageTarget(source, next, damage, false, false, attacktype, damagetype, nil)
DestroyGroup(group)
prev = this
this = next
next = nil
else
PauseTimer(timer)
DestroyTimer(timer)
DestroyGroup(group)
DestroyGroup(damaged)
end
end
else
PauseTimer(timer)
DestroyTimer(timer)
DestroyGroup(group)
DestroyGroup(damaged)
end
bounces = bounces - 1
end)
end
DestroyGroup(group)
end
-- Add the specified amount to the specified player gold amount
function AddPlayerGold(player, amount)
SetPlayerState(player, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(player, PLAYER_STATE_RESOURCE_GOLD) + amount)
end
-- Creates a text tag in an unit position for a duration
function CreateTextOnUnit(unit, string, duration, red, green, blue, alpha)
local texttag = CreateTextTag()
SetTextTagText(texttag, string, 0.015)
SetTextTagPosUnit(texttag, unit, 0)
SetTextTagColor(texttag, red, green, blue, alpha)
SetTextTagLifespan(texttag, duration)
SetTextTagVelocity(texttag, 0.0, 0.0355)
SetTextTagPermanent(texttag, false)
end
-- Add health regeneration to the unit base value
function UnitAddHealthRegen(unit, regen)
BlzSetUnitRealField(unit, UNIT_RF_HIT_POINTS_REGENERATION_RATE, BlzGetUnitRealField(unit, UNIT_RF_HIT_POINTS_REGENERATION_RATE) + regen)
end
-- Retrieves a dummy from the pool. Facing angle in radians
function DummyRetrieve(player, x, y, z, face)
return DummyPool:retrieve(player, x, y, z, face)
end
-- Recycles a dummy unit type, putting it back into the pool.
function DummyRecycle(unit)
DummyPool:recycle(unit)
end
-- Recycles a dummy with a delay.
function DummyRecycleTimed(unit, delay)
DummyPool:timed(unit, delay)
end
-- Casts an ability in the target unit. Must have no casting time
function CastAbilityTarget(unit, ability, order, level)
local dummy = DummyRetrieve(GetOwningPlayer(unit), 0, 0, 0, 0)
UnitAddAbility(dummy, ability)
SetUnitAbilityLevel(dummy, ability, level)
IssueTargetOrder(dummy, order, unit)
UnitRemoveAbility(dummy, ability)
DummyRecycle(dummy)
end
-- Returns a random unit within a group
function GroupPickRandomUnitEx(group)
if BlzGroupGetSize(group) > 0 then
return BlzGroupUnitAt(group, GetRandomInt(0, BlzGroupGetSize(group) - 1))
else
return nil
end
end
-- Returns true if a unit is within a cone given a facing and fov angle in degrees (Less precise)
function IsUnitInConeEx(unit, x, y, face, fov)
return Acos(Cos((Atan2(GetUnitY(unit) - y, GetUnitX(unit) - x)) - face*bj_DEGTORAD)) < fov*bj_DEGTORAD/2
end
-- Returns true if a unit is within a cone given a facing, fov angle and a range in degrees (takes collision into consideration). Credits to AGD.
function IsUnitInCone(unit, x, y, range, face, fov)
if IsUnitInRangeXY(unit, x, y, range) then
x = GetUnitX(unit) - x
y = GetUnitY(unit) - y
range = x*x + y*y
if range > 0 then
face = face*bj_DEGTORAD - Atan2(y, x)
fov = fov*bj_DEGTORAD/2 + Asin(BlzGetUnitCollisionSize(unit)/SquareRoot(range))
return RAbsBJ(face) <= fov or RAbsBJ(face - 2.00*bj_PI) <= fov
end
return true
end
return false
end
-- Makes the source unit damage enemy unit in a cone given a direction, foy and range
function UnitDamageCone(unit, x, y, face, fov, aoe, damage, attacktype, damagetype, structures, magicImmune, allies)
local group = CreateGroup()
local player = GetOwningPlayer(unit)
local u
GroupEnumUnitsInRange(group, x, y, aoe, null)
GroupRemoveUnit(group, unit)
if allies then
if structures and magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if UnitAlive(u) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif structures and not magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif magicImmune and not structures then
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
else
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
end
else
if structures and magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if IsUnitEnemy(u, player) and UnitAlive(u) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif structures and not magicImmune then
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if IsUnitEnemy(u, player) and UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
elseif magicImmune and not structures then
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if IsUnitEnemy(u, player) and UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
else
for i = 0, BlzGroupGetSize(group) - 1 do
u = BlzGroupUnitAt(group, i)
if IsUnitEnemy(u, player) and UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and IsUnitInCone(u, x, y, aoe, face, fov) then
UnitDamageTarget(unit, u, damage, true, false, attacktype, damagetype, null)
end
end
end
end
DestroyGroup(group)
end
-- Heals all allied units of specified player in an area
function HealArea(player, x, y, aoe, amount, effect, attach)
local group = CreateGroup()
local unit
GroupEnumUnitsInRange(group, x, y, aoe, null)
for i = 0, BlzGroupGetSize(group) - 1 do
unit = BlzGroupUnitAt(group, i)
if IsUnitAlly(unit, player) and UnitAlive(unit) and not IsUnitType(unit, UNIT_TYPE_STRUCTURE) then
SetWidgetLife(unit, GetWidgetLife(unit) + amount)
if effect ~= "" then
if attach ~= "" then
DestroyEffect(AddSpecialEffectTarget(effect, unit, attach))
else
DestroyEffect(AddSpecialEffect(effect, GetUnitX(unit), GetUnitY(unit)))
end
end
end
end
DestroyGroup(group)
end
-- Pretty obvious.
function R2I2S(real)
return I2S(R2I(real))
end
-- Returns an ability real level field as a string. Usefull for toolltip manipulation.
function AbilityRealField(unit, ability, field, level, multiplier, integer)
if integer then
return R2I2S(BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ability), field, level)*multiplier)
else
return R2SW(BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ability), field, level)*multiplier, 1, 1)
end
end
-- Fix for camera pan desync. credits do Daffa
function SmartCameraPanBJModified(player, loc, duration)
local x = GetLocationX(loc)
local y = GetLocationY(loc)
local dx = x - GetCameraTargetPositionX()
local dy = y - GetCameraTargetPositionY()
local dist = SquareRoot(dx*dx + dy*dy)
if GetLocalPlayer() == player then
if dist >= bj_SMARTPAN_TRESHOLD_SNAP then
PanCameraToTimed(x, y, duration)
elseif dist >= bj_SMARTPAN_TRESHOLD_PAN then
PanCameraToTimed(x, y, duration)
else
-- User is close, dont move camera
end
end
end
-- Fix for camera pan desync. credits do Daffa
function SmartCameraPanBJModifiedXY(player, x, y, duration)
local dx = x - GetCameraTargetPositionX()
local dy = y - GetCameraTargetPositionY()
local dist = SquareRoot(dx*dx + dy*dy)
if GetLocalPlayer() == player then
if dist >= bj_SMARTPAN_TRESHOLD_SNAP then
PanCameraToTimed(x, y, duration)
elseif dist >= bj_SMARTPAN_TRESHOLD_PAN then
PanCameraToTimed(x, y, duration)
else
-- User is close, dont move camera
end
end
end
-- Start the cooldown for the source unit unit the new value
function StartUnitAbilityCooldown(unit, ability, cooldown)
local timer = CreateTimer()
TimerStart(timer, 0.01, false, function()
BlzStartUnitAbilityCooldown(unit, ability, cooldown)
PauseTimer(timer)
DestroyTimer(timer)
end)
end
-- ---------------------------------------------------------------------------------------------- --
-- Systems --
-- ---------------------------------------------------------------------------------------------- --
-- ----------------------------------------- Dummy Pool ----------------------------------------- --
do
local group = CreateGroup()
local player = Player(PLAYER_NEUTRAL_PASSIVE)
DummyPool = setmetatable({}, {})
local mt = getmetatable(DummyPool)
mt.__index = mt
function mt:recycle(unit)
if GetUnitTypeId(unit) ~= DUMMY then
print("[DummyPool] Error: Trying to recycle a non dummy unit")
else
GroupAddUnit(group, unit)
SetUnitX(unit, WorldBounds.maxX)
SetUnitY(unit, WorldBounds.maxY)
SetUnitOwner(unit, player, false)
ShowUnit(unit, false)
BlzPauseUnitEx(unit, true)
end
end
function mt:retrieve(owner, x, y, z, face)
if BlzGroupGetSize(group) > 0 then
bj_lastCreatedUnit = FirstOfGroup(group)
BlzPauseUnitEx(bj_lastCreatedUnit, false)
ShowUnit(bj_lastCreatedUnit, true)
GroupRemoveUnit(group, bj_lastCreatedUnit)
SetUnitX(bj_lastCreatedUnit, x)
SetUnitY(bj_lastCreatedUnit, y)
SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
BlzSetUnitFacingEx(bj_lastCreatedUnit, face*bj_RADTODEG)
SetUnitOwner(bj_lastCreatedUnit, owner, false)
else
bj_lastCreatedUnit = CreateUnit(owner, DUMMY, x, y, face*bj_RADTODEG)
SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
end
return bj_lastCreatedUnit
end
function mt:timed(unit, delay)
local timer = CreateTimer()
if GetUnitTypeId(unit) ~= DUMMY then
print("[DummyPool] Error: Trying to recycle a non dummy unit")
else
TimerStart(timer, delay, false, function()
GroupAddUnit(group, unit)
SetUnitX(unit, WorldBounds.maxX)
SetUnitY(unit, WorldBounds.maxY)
SetUnitOwner(unit, player, false)
ShowUnit(unit, false)
BlzPauseUnitEx(unit, true)
PauseTimer(timer)
DestroyTimer(timer)
end)
end
end
OnInit.global(function()
local timer = CreateTimer()
local unit
TimerStart(timer, 0, false, function()
for i = 0, 20 do
unit = CreateUnit(player, DUMMY, WorldBounds.maxX, WorldBounds.maxY, 0)
BlzPauseUnitEx(unit, false)
GroupAddUnit(group, unit)
end
PauseTimer(timer)
DestroyTimer(timer)
end)
end)
end
-- ---------------------------------------- Timed Ability --------------------------------------- --
do
TimedAbility = setmetatable({}, {})
local mt = getmetatable(TimedAbility)
mt.__index = mt
local timer = CreateTimer()
local ability = {}
local array = {}
local key = 0
function mt:destroy(i)
UnitRemoveAbility(self.unit, self.id)
array[i] = array[key]
key = key - 1
ability[self.unit][self.id] = nil
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:add(unit, id, duration, level, hide)
if not ability[unit] then ability[unit] = {} end
local this = ability[unit][id]
if not this then
this = {}
setmetatable(this, mt)
this.unit = unit
this.id = id
key = key + 1
array[key] = this
ability[unit][id] = this
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.duration <= 0 then
i = this:destroy(i)
end
this.duration = this.duration - PERIOD
i = i + 1
end
end)
end
end
if GetUnitAbilityLevel(unit, id) ~= level then
UnitAddAbility(unit, id)
SetUnitAbilityLevel(unit, id, level)
UnitMakeAbilityPermanent(unit, true, id)
BlzUnitHideAbility(unit, id, hide)
end
this.duration = duration
end
end
-- ----------------------------------------- Effect Link ---------------------------------------- --
do
EffectLink = setmetatable({}, {})
local mt = getmetatable(EffectLink)
mt.__index = mt
local timer = CreateTimer()
local array = {}
local items = {}
local key = 0
local k = 0
function mt:destroy(i, item)
DestroyEffect(self.effect)
if item then
items[i] = items[k]
k = k - 1
else
array[i] = array[key]
key = key - 1
if key == 0 then
PauseTimer(timer)
end
end
self = nil
return i - 1
end
function mt:buff(unit, buffId, model, attach)
local this = {}
setmetatable(this, mt)
key = key + 1
array[key] = this
this.unit = unit
this.buff = buffId
this.effect = AddSpecialEffectTarget(model, unit, attach)
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if GetUnitAbilityLevel(this.unit, this.buff) == 0 then
i = this:destroy(i, false)
end
i = i + 1
end
end)
end
end
function mt:item(unit, i, model, attach)
local this = {}
setmetatable(this, mt)
k = k + 1
items[k] = this
this.item = i
this.effect = AddSpecialEffectTarget(model, unit, attach)
end
OnInit.global(function()
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DROP_ITEM, function()
local item = GetManipulatedItem()
local i = 1
local this
while i <= k do
this = items[i]
if this.item == item then
i = this:destroy(i, true)
end
i = i + 1
end
end)
end)
end
end
if Debug and Debug.endFile then Debug.endFile() end
--[[
-- ------------------------ Indexer v1.1 by Chopinski ----------------------- --
Simple Unit Indexer for LUA.
Simply copya nd paste to import
]]--
do
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
local ability = FourCC('Adef')
local onIndex = {}
local onDeindex = {}
local id = 0
local source
local SetUnitId = SetUnitUserData
function SetUnitUserData(unit, id) end
local function IndexUnit(unit)
if GetUnitUserData(unit) == 0 then
id = id + 1
source = unit
if GetUnitAbilityLevel(unit, ability) == 0 then
UnitAddAbility(unit, ability)
UnitMakeAbilityPermanent(unit, true, ability)
BlzUnitDisableAbility(unit, ability, true, true)
end
SetUnitId(unit, id)
for i = 1, #onIndex do
onIndex[i]()
end
source = nil
end
end
onInit(function()
local trigger = CreateTrigger()
local region = CreateRegion()
local rect = GetWorldBounds()
RegionAddRect(region, rect)
RemoveRect(rect)
TriggerRegisterEnterRegion(CreateTrigger(), region, Filter(function()
IndexUnit(GetFilterUnit())
end))
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, Player(i), Filter(function()
IndexUnit(GetFilterUnit())
end))
TriggerRegisterPlayerUnitEvent(trigger, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
end
TriggerAddCondition(trigger, Filter(function()
if GetIssuedOrderId() == 852056 then
if GetUnitAbilityLevel(GetTriggerUnit(), ability) == 0 then
source = GetTriggerUnit()
for i = 1, #onDeindex do
onDeindex[i]()
end
source = nil
end
end
end))
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function RegisterUnitIndexEvent(code)
if type(code) == "function" then
table.insert(onIndex, code)
end
end
function RegisterUnitDeindexEvent(code)
if type(code) == "function" then
table.insert(onDeindex, code)
end
end
function GetIndexUnit()
return source
end
end
if Debug and Debug.beginFile then Debug.beginFile("Indexer") end
--[[
-- ------------------------ Indexer v1.1 by Chopinski ----------------------- --
Simple Unit Indexer for LUA.
Simply copya nd paste to import
]]--
do
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
local ability = FourCC('Adef')
local onIndex = {}
local onDeindex = {}
local id = 0
local source
local SetUnitId = SetUnitUserData
function SetUnitUserData(unit, id) end
local function IndexUnit(unit)
if GetUnitUserData(unit) == 0 then
id = id + 1
source = unit
if GetUnitAbilityLevel(unit, ability) == 0 then
UnitAddAbility(unit, ability)
UnitMakeAbilityPermanent(unit, true, ability)
BlzUnitDisableAbility(unit, ability, true, true)
end
SetUnitId(unit, id)
for i = 1, #onIndex do
onIndex[i]()
end
source = nil
end
end
OnInit.global(function()
local trigger = CreateTrigger()
local region = CreateRegion()
local rect = GetWorldBounds()
RegionAddRect(region, rect)
RemoveRect(rect)
TriggerRegisterEnterRegion(CreateTrigger(), region, Filter(function()
IndexUnit(GetFilterUnit())
end))
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, Player(i), Filter(function()
IndexUnit(GetFilterUnit())
end))
TriggerRegisterPlayerUnitEvent(trigger, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
end
TriggerAddCondition(trigger, Filter(function()
if GetIssuedOrderId() == 852056 then
if GetUnitAbilityLevel(GetTriggerUnit(), ability) == 0 then
source = GetTriggerUnit()
for i = 1, #onDeindex do
onDeindex[i]()
end
source = nil
end
end
end))
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function RegisterUnitIndexEvent(code)
if type(code) == "function" then
table.insert(onIndex, code)
end
end
function RegisterUnitDeindexEvent(code)
if type(code) == "function" then
table.insert(onDeindex, code)
end
end
function GetIndexUnit()
return source
end
end
if Debug and Debug.endFile then Debug.endFile() end
--[[
Allow to retrieve the x and y position of a player mouse
]]--
do
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
local mouse = {}
local trigger = CreateTrigger()
onInit(function()
for i = 0, bj_MAX_PLAYER_SLOTS do
local player = Player(i)
if GetPlayerController(player) == MAP_CONTROL_USER and GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING then
mouse[player] = {}
TriggerRegisterPlayerEvent(trigger, player, EVENT_PLAYER_MOUSE_MOVE)
end
end
TriggerAddCondition(trigger, Condition(function()
local player = GetTriggerPlayer()
mouse[player].x = BlzGetTriggerPlayerMouseX()
mouse[player].y = BlzGetTriggerPlayerMouseY()
end))
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function GetPlayerMouseX(player)
return mouse[player].x or 0
end
function GetPlayerMouseY(player)
return mouse[player].y or 0
end
end
if Debug and Debug.beginFile then Debug.beginFile("MouseUtils") end
--[[
Allow to retrieve the x and y position of a player mouse
]]--
do
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
local mouse = {}
local trigger = CreateTrigger()
OnInit.final(function()
for i = 0, bj_MAX_PLAYER_SLOTS do
local player = Player(i)
if GetPlayerController(player) == MAP_CONTROL_USER and GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING then
mouse[player] = {}
TriggerRegisterPlayerEvent(trigger, player, EVENT_PLAYER_MOUSE_MOVE)
end
end
TriggerAddCondition(trigger, Condition(function()
local player = GetTriggerPlayer()
mouse[player].x = BlzGetTriggerPlayerMouseX()
mouse[player].y = BlzGetTriggerPlayerMouseY()
end))
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function GetPlayerMouseX(player)
return mouse[player].x or 0
end
function GetPlayerMouseY(player)
return mouse[player].y or 0
end
end
if Debug and Debug.endFile then Debug.endFile() end
if Debug and Debug.beginFile then Debug.beginFile("MapBounds") end
--[[ mapbounds.lua v1.0.0 |
Description:
Nestharus's WorldBounds ported into Lua, with few simple but useful
additions. Aside from providing map boundary data thru variables,
also provides premade functions for checking whether a coordinate
is inside map boundaries, getting a random coordinate within
map boundaries, and getting a bounded coordinate value.
Requirements:
- None
API:
Prefixes:
MapBounds
- Refers to initial playable map bounds
WorldBounds
- Refers to world bounds
Variables:
number: <Prefix>.centerX
number: <Prefix>.centerY
number: <Prefix>.minX
number: <Prefix>.minY
number: <Prefix>.maxX
number: <Prefix>.maxY
rect: <Prefix>.rect
region: <Prefix>.region
- These variables are intended to be READONLY
Functions:
function <Prefix>:getRandomX() -> number
function <Prefix>:getRandomY() -> number
- Returns a random coordinate inside the bounds
function <Prefix>:getBoundedX(number: x, number: margin=0.00) -> number
function <Prefix>:getBoundedY(number: y, number: margin=0.00) -> number
- Returns a coordinate that is inside the bounds
function <Prefix>:containsX(number: x) -> boolean
function <Prefix>:containsY(number: y) -> boolean
- Checks if the bounds contain the input coordinate
]]--
MapBounds = setmetatable({}, {})
WorldBounds = setmetatable({}, getmetatable(MapBounds))
do
local mt = getmetatable(MapBounds)
mt.__index = mt
function mt:getRandomX()
return GetRandomReal(self.minX, self.maxX)
end
function mt:getRandomY()
return GetRandomReal(self.minY, self.maxY)
end
local function GetBoundedValue(bounds, v, minV, maxV, margin)
margin = margin or 0.00
if v < (bounds[minV] + margin) then
return bounds[minV] + margin
elseif v > (bounds[maxV] - margin) then
return bounds[maxV] - margin
end
return v
end
function mt:getBoundedX(x, margin)
return GetBoundedValue(self, x, "minX", "maxX", margin)
end
function mt:getBoundedY(y, margin)
return GetBoundedValue(self, y, "minY", "maxY", margin)
end
function mt:containsX(x)
return self:getBoundedX(x) == x
end
function mt:containsY(y)
return self:getBoundedY(y) == y
end
local function InitData(bounds)
bounds.region = CreateRegion()
bounds.minX = GetRectMinX(bounds.rect)
bounds.minY = GetRectMinY(bounds.rect)
bounds.maxX = GetRectMaxX(bounds.rect)
bounds.maxY = GetRectMaxY(bounds.rect)
bounds.centerX = (bounds.minX + bounds.maxX) / 2.00
bounds.centerY = (bounds.minY + bounds.maxY) / 2.00
RegionAddRect(bounds.region, bounds.rect)
end
local oldInit = InitGlobals
function InitGlobals()
oldInit()
MapBounds.rect = bj_mapInitialPlayableArea
WorldBounds.rect = GetWorldBounds()
InitData(MapBounds)
InitData(WorldBounds)
end
end
if Debug and Debug.endFile then Debug.endFile() end
if Debug and Debug.beginFile then Debug.beginFile("LineSignment") end
--[[ LineSegmentEnumeration v2.2a
API:
function LineSegment.EnumUnits(number: ax, number: ay, number: bx, number: by, number: offset, boolean: checkCollision)
function LineSegment.EnumDestructables(number: ax, number: ay, number: bx, number: by, number: offset)
function LineSegment.EnumItems(number: ax, number: ay, number: bx, number: by, number: offset)
- returns the enumerated widgets as a table
]]--
LineSegment = {}
do
local RECT = Rect(0, 0, 0, 0)
local GROUP = CreateGroup()
local ox
local oy
local dx
local dy
local da
local db
local ui
local uj
local wdx
local wdy
local uui
local uuj
local function prepare_rect(ax, ay, bx, by, offset)
local x_max
local x_min
local y_max
local y_min
-- get center coordinates of rectangle
ox, oy = 0.5*(ax + bx), 0.5*(ay + by)
-- get rectangle major axis as vector
dx, dy = 0.5*(bx - ax), 0.5*(by - ay)
-- get half of rectangle length (da) and height (db)
da, db = math.sqrt(dx*dx + dy*dy), offset
-- get unit vector of the major axis
ui, uj = dx/da, dy/da
-- prepare bounding rect
if ax > bx then
x_min, x_max = bx - offset, ax + offset
else
x_min, x_max = ax - offset, bx + offset
end
if ay > by then
y_min, y_max = by - offset, ay + offset
else
y_min, y_max = ay - offset, by + offset
end
SetRect(RECT, x_min, y_min, x_max, y_max)
end
local function rect_contains_widget(w, offset)
wdx, wdy = GetWidgetX(w) - ox, GetWidgetY(w) - oy
dx, dy = wdx*ui + wdy*uj, wdx*(-uj) + wdy*ui
da, db = da + offset, db + offset
return dx*dx <= da*da and dy*dy <= db*db
end
local function widget_filter(w, offset)
if rect_contains_widget(w, offset) then
table.insert(LineSegment.enumed, w)
end
end
function LineSegment.EnumUnits(ax, ay, bx, by, offset, checkCollision)
prepare_rect(ax, ay, bx, by, offset)
GroupEnumUnitsInRect(GROUP, RECT)
local enumed = {}
LineSegment.enumed = enumed
for i = 0, BlzGroupGetSize(GROUP) - 1 do
local u = BlzGroupUnitAt(GROUP, i)
widget_filter(u, checkCollision and BlzGetUnitCollisionSize(u) or 0.)
end
return enumed
end
function LineSegment.EnumDestructables(ax, ay, bx, by, offset)
prepare_rect(ax, ay, bx, by, offset)
local enumed = {}
LineSegment.enumed = enumed
EnumDestructablesInRect(RECT, Filter(function ()
widget_filter(GetFilterDestructable(), 0.)
end))
return enumed
end
function LineSegment.EnumItems(ax, ay, bx, by, offset)
prepare_rect(ax, ay, bx, by, offset)
local enumed = {}
LineSegment.enumed = enumed
EnumItemsInRect(RECT, Filter(function ()
widget_filter(GetFilterItem(), 0.)
end))
return enumed
end
end
if Debug and Debug.endFile then Debug.endFile() end
--[[
/**************************************************************
*
* v1.0.5 by TriggerHappy
* ----------------------
*
* Use this to destroy a handle after X amount seconds.
*
* It's useful for things like effects where you may
* want it to be temporary, but not have to worry
* about the cleaning memory leak. By default it supports
* effects, lightning, weathereffect, items, ubersplats, and units.
*
* Installation
----------------------
* 1. Copy this script and over to your map inside a blank trigger.
*
* API
* ----------------------
* call DestroyEffectTimed(AddSpecialEffect("effect.mdx", 0, 0), 5)
* call DestroyLightningTimed(AddLightning("CLPB", true, 0, 0, 100, 100), 5)
*
**************************************************************/
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
local PERIOD = 0.05
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
Timed = setmetatable({}, {})
local mt = getmetatable(Timed)
mt.__index = mt
local timer = CreateTimer()
local array = {}
local key = 0
function mt:destroy(i)
if self.flag == 0 then
DestroyEffect(self.type)
elseif self.flag == 1 then
DestroyLightning(self.type)
elseif self.flag == 2 then
RemoveWeatherEffect(self.type)
elseif self.flag == 3 then
RemoveItem(self.type)
elseif self.flag == 4 then
RemoveUnit(self.type)
elseif self.flag == 5 then
DestroyUbersplat(self.type)
elseif self.flag == 6 then
RemoveDestructable(self.type)
end
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:handle(type, duration, flag)
local this = {}
setmetatable(this, mt)
this.type = type
this.flag = flag
this.ticks = duration/PERIOD
key = key + 1
array[key] = this
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.ticks <= 0 then
i = this:destroy(i)
end
this.ticks = this.ticks - 1
i = i + 1
end
end)
end
end
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function DestroyEffectTimed(effect, duration)
Timed:handle(effect, duration, 0)
end
function DestroyLightningTimed(lightning, duration)
Timed:handle(lightning, duration, 1)
end
function RemoveWeatherEffectTimed(weathereffect, duration)
Timed:handle(weathereffect, duration, 2)
end
function RemoveItemTimed(item, duration)
Timed:handle(item, duration, 3)
end
function RemoveUnitTimed(unit, duration)
Timed:handle(unit, duration, 4)
end
function DestroyUbersplatTimed(ubersplat, duration)
Timed:handle(ubersplat, duration, 5)
end
function RemoveDestructableTimed(destructable, duration)
Timed:handle(destructable, duration, 6)
end
end
if Debug and Debug.beginFile then Debug.beginFile("TimedHandles") end
--[[
/**************************************************************
*
* v1.0.5 by TriggerHappy
* ----------------------
*
* Use this to destroy a handle after X amount seconds.
*
* It's useful for things like effects where you may
* want it to be temporary, but not have to worry
* about the cleaning memory leak. By default it supports
* effects, lightning, weathereffect, items, ubersplats, and units.
*
* Installation
----------------------
* 1. Copy this script and over to your map inside a blank trigger.
*
* API
* ----------------------
* call DestroyEffectTimed(AddSpecialEffect("effect.mdx", 0, 0), 5)
* call DestroyLightningTimed(AddLightning("CLPB", true, 0, 0, 100, 100), 5)
*
**************************************************************/
]]--
OnInit.global(function()
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
local PERIOD = 0.05
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
Timed = setmetatable({}, {})
local mt = getmetatable(Timed)
mt.__index = mt
local timer = CreateTimer()
local array = {}
local key = 0
function mt:destroy(i)
if self.flag == 0 then
DestroyEffect(self.type)
elseif self.flag == 1 then
DestroyLightning(self.type)
elseif self.flag == 2 then
RemoveWeatherEffect(self.type)
elseif self.flag == 3 then
RemoveItem(self.type)
elseif self.flag == 4 then
RemoveUnit(self.type)
elseif self.flag == 5 then
DestroyUbersplat(self.type)
elseif self.flag == 6 then
RemoveDestructable(self.type)
end
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:handle(type, duration, flag)
local this = {}
setmetatable(this, mt)
this.type = type
this.flag = flag
this.ticks = duration/PERIOD
key = key + 1
array[key] = this
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.ticks <= 0 then
i = this:destroy(i)
end
this.ticks = this.ticks - 1
i = i + 1
end
end)
end
end
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function DestroyEffectTimed(effect, duration)
Timed:handle(effect, duration, 0)
end
function DestroyLightningTimed(lightning, duration)
Timed:handle(lightning, duration, 1)
end
function RemoveWeatherEffectTimed(weathereffect, duration)
Timed:handle(weathereffect, duration, 2)
end
function RemoveItemTimed(item, duration)
Timed:handle(item, duration, 3)
end
function RemoveUnitTimed(unit, duration)
Timed:handle(unit, duration, 4)
end
function DestroyUbersplatTimed(ubersplat, duration)
Timed:handle(ubersplat, duration, 5)
end
function RemoveDestructableTimed(destructable, duration)
Timed:handle(destructable, duration, 6)
end
end)
if Debug and Debug.endFile then Debug.endFile() end
--[[
/* ------------------- SpellEffectEvent v1.1 by Chopinski ------------------ */
-- Full credits to Bribe for the original library. I just modified it to store
-- some usefull information in a table to be used later instead of calling
-- these functions all the time for every ability.
/* ----------------------------------- END ---------------------------------- */
]]--
do
local funcs = {}
local trigger = nil
local location = Location(0, 0)
Spell = {
source = {
unit,
player,
handle,
isHero,
isStructure,
id,
x,
y,
z
},
target = {
unit,
player,
handle,
isHero,
isStructure,
id,
x,
y,
z
},
ability,
level,
id,
x,
y,
z
}
local function GetUnitZ(unit)
MoveLocation(location, GetUnitX(unit), GetUnitY(unit))
return GetUnitFlyHeight(unit) + GetLocationZ(location)
end
local function GetSpellTargetZ()
MoveLocation(location, Spell.x, Spell.y)
if Spell.target.unit then
return GetUnitZ(Spell.target.unit)
else
return GetLocationZ(location)
end
end
function RegisterSpellEffectEvent(ability, code)
if type(code) == "function" then
if not trigger then
trigger = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trigger, EVENT_PLAYER_UNIT_SPELL_EFFECT)
TriggerAddCondition(trigger, Condition(function()
local f = funcs[GetSpellAbilityId()]
if f then
Spell.source.unit = GetTriggerUnit()
Spell.source.player = GetOwningPlayer(Spell.source.unit)
Spell.source.handle = GetHandleId(Spell.source.unit)
Spell.source.id = GetUnitUserData(Spell.source.unit)
Spell.source.x = GetUnitX(Spell.source.unit)
Spell.source.y = GetUnitY(Spell.source.unit)
Spell.source.z = GetUnitZ(Spell.source.unit)
Spell.source.isHero = IsUnitType(Spell.source.unit, UNIT_TYPE_HERO)
Spell.source.isStructure = IsUnitType(Spell.source.unit, UNIT_TYPE_STRUCTURE)
Spell.target.unit = GetSpellTargetUnit()
Spell.target.player = GetOwningPlayer(Spell.target.unit)
Spell.target.handle = GetHandleId(Spell.target.unit)
Spell.target.id = GetUnitUserData(Spell.target.unit)
Spell.target.x = GetUnitX(Spell.target.unit)
Spell.target.y = GetUnitY(Spell.target.unit)
Spell.target.z = GetUnitZ(Spell.target.unit)
Spell.target.isHero = IsUnitType(Spell.target.unit, UNIT_TYPE_HERO)
Spell.target.isStructure = IsUnitType(Spell.target.unit, UNIT_TYPE_STRUCTURE)
Spell.x = GetSpellTargetX()
Spell.y = GetSpellTargetY()
Spell.z = GetSpellTargetZ()
Spell.id = GetSpellAbilityId()
Spell.level = GetUnitAbilityLevel(Spell.source.unit, Spell.id)
Spell.ability = BlzGetUnitAbility(Spell.source.unit, Spell.id)
f()
end
end))
end
funcs[ability] = code
end
end
end
if Debug and Debug.beginFile then Debug.beginFile("SpellEffectEvent") end
--[[
/* ------------------- SpellEffectEvent v1.1 by Chopinski ------------------ */
-- Full credits to Bribe for the original library. I just modified it to store
-- some usefull information in a table to be used later instead of calling
-- these functions all the time for every ability.
/* ----------------------------------- END ---------------------------------- */
]]--
OnInit.global(function()
local funcs = {}
local trigger = nil
local location = Location(0, 0)
Spell = {
source = {
unit,
player,
handle,
isHero,
isStructure,
id,
x,
y,
z
},
target = {
unit,
player,
handle,
isHero,
isStructure,
id,
x,
y,
z
},
ability,
level,
id,
x,
y,
z
}
local function GetUnitZ(unit)
MoveLocation(location, GetUnitX(unit), GetUnitY(unit))
return GetUnitFlyHeight(unit) + GetLocationZ(location)
end
local function GetSpellTargetZ()
MoveLocation(location, Spell.x, Spell.y)
if Spell.target.unit then
return GetUnitZ(Spell.target.unit)
else
return GetLocationZ(location)
end
end
function RegisterSpellEffectEvent(ability, code)
if type(code) == "function" then
if not trigger then
trigger = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trigger, EVENT_PLAYER_UNIT_SPELL_EFFECT)
TriggerAddCondition(trigger, Condition(function()
local f = funcs[GetSpellAbilityId()]
if f then
Spell.source.unit = GetTriggerUnit()
Spell.source.player = GetOwningPlayer(Spell.source.unit)
Spell.source.handle = GetHandleId(Spell.source.unit)
Spell.source.id = GetUnitUserData(Spell.source.unit)
Spell.source.x = GetUnitX(Spell.source.unit)
Spell.source.y = GetUnitY(Spell.source.unit)
Spell.source.z = GetUnitZ(Spell.source.unit)
Spell.source.isHero = IsUnitType(Spell.source.unit, UNIT_TYPE_HERO)
Spell.source.isStructure = IsUnitType(Spell.source.unit, UNIT_TYPE_STRUCTURE)
Spell.target.unit = GetSpellTargetUnit()
Spell.target.player = GetOwningPlayer(Spell.target.unit)
Spell.target.handle = GetHandleId(Spell.target.unit)
Spell.target.id = GetUnitUserData(Spell.target.unit)
Spell.target.x = GetUnitX(Spell.target.unit)
Spell.target.y = GetUnitY(Spell.target.unit)
Spell.target.z = GetUnitZ(Spell.target.unit)
Spell.target.isHero = IsUnitType(Spell.target.unit, UNIT_TYPE_HERO)
Spell.target.isStructure = IsUnitType(Spell.target.unit, UNIT_TYPE_STRUCTURE)
Spell.x = GetSpellTargetX()
Spell.y = GetSpellTargetY()
Spell.z = GetSpellTargetZ()
Spell.id = GetSpellAbilityId()
Spell.level = GetUnitAbilityLevel(Spell.source.unit, Spell.id)
Spell.ability = BlzGetUnitAbility(Spell.source.unit, Spell.id)
f()
end
end))
end
funcs[ability] = code
end
end
end)
if Debug and Debug.endFile then Debug.endFile() end
-- Arcing Text Tag v1.0.0.3 by Maker encoded to Lua
DEFINITION = 1.0/32.0
SIZE_MIN = 0.013 -- Minimum size of text
SIZE_BONUS = 0.005 -- Text size increase
TIME_LIFE = 0.5 -- How long the text lasts
TIME_FADE = 0.3 -- When does the text start to fade
Z_OFFSET = 50 -- Height above unit
Z_OFFSET_BON = 50 -- How much extra height the text gains
VELOCITY = 2.0 -- How fast the text move in x/y plane
TMR = CreateTimer()
ANGLE_RND = true -- Is the angle random or fixed
if not ANGLE_RND then
ANGLE = bj_PI/2.0 -- If fixed, specify the Movement angle of the text.
end
tt = {}
as = {} -- angle, sin component
ac = {} -- angle, cos component
ah = {} -- arc height
t = {} -- time
x = {} -- origin x
y = {} -- origin y
str = {} -- text
ic = 0 -- Instance count
rn = {} ; rn[0] = 0
next = {} ; next[0] = 0
prev = {} ; prev[0] = 0 --Needed due to Lua not initializing them.
function ArcingTextTag(s, u)
local this = rn[0]
if this == 0 then
ic = ic + 1
this = ic
else
rn[0] = rn[this]
end
next[this] = 0
prev[this] = prev[0]
next[prev[0]] = this
prev[0] = this
str[this] = s
x[this] = GetUnitX(u)
y[this] = GetUnitY(u)
t[this] = TIME_LIFE
local a
if ANGLE_RND then
a = GetRandomReal(0, 2*bj_PI)
else
a = ANGLE
end
as[this] = Sin(a)*VELOCITY
ac[this] = Cos(a)*VELOCITY
ah[this] = 0.
if IsUnitVisible(u, GetLocalPlayer()) then
tt[this] = CreateTextTag()
SetTextTagPermanent(tt[this], false)
SetTextTagLifespan(tt[this], TIME_LIFE)
SetTextTagFadepoint(tt[this], TIME_FADE)
SetTextTagText(tt[this], s, SIZE_MIN)
SetTextTagPos(tt[this], x[this], y[this], Z_OFFSET)
end
if prev[this] == 0 then
TimerStart(TMR, DEFINITION, true, function()
local this = next[0]
local p
while (this ~= 0) do
p = Sin(bj_PI*t[this])
t[this] = t[this] - DEFINITION
x[this] = x[this] + ac[this]
y[this] = y[this] + as[this]
SetTextTagPos(tt[this], x[this], y[this], Z_OFFSET + Z_OFFSET_BON * p)
SetTextTagText(tt[this], str[this], SIZE_MIN + SIZE_BONUS * p)
if t[this] <= 0.0 then
tt[this] = null
next[prev[this]] = next[this]
prev[next[this]] = prev[this]
rn[this] = rn[0]
rn[0] = this
if next[0] == 0 then
PauseTimer(TMR)
end
end
this = next[this]
end
end)
end
return this
end
if Debug and Debug.beginFile then Debug.beginFile("ArcingFloatingText") end
-- Arcing Text Tag v1.0.0.3 by Maker encoded to Lua
OnInit.global(function()
DEFINITION = 1.0/32.0
SIZE_MIN = 0.013 -- Minimum size of text
SIZE_BONUS = 0.005 -- Text size increase
TIME_LIFE = 0.5 -- How long the text lasts
TIME_FADE = 0.3 -- When does the text start to fade
Z_OFFSET = 50 -- Height above unit
Z_OFFSET_BON = 50 -- How much extra height the text gains
VELOCITY = 2.0 -- How fast the text move in x/y plane
TMR = CreateTimer()
ANGLE_RND = true -- Is the angle random or fixed
if not ANGLE_RND then
ANGLE = bj_PI/2.0 -- If fixed, specify the Movement angle of the text.
end
tt = {}
as = {} -- angle, sin component
ac = {} -- angle, cos component
ah = {} -- arc height
t = {} -- time
x = {} -- origin x
y = {} -- origin y
str = {} -- text
ic = 0 -- Instance count
rn = {} ; rn[0] = 0
next = {} ; next[0] = 0
prev = {} ; prev[0] = 0 --Needed due to Lua not initializing them.
function ArcingTextTag(s, u)
local this = rn[0]
if this == 0 then
ic = ic + 1
this = ic
else
rn[0] = rn[this]
end
next[this] = 0
prev[this] = prev[0]
next[prev[0]] = this
prev[0] = this
str[this] = s
x[this] = GetUnitX(u)
y[this] = GetUnitY(u)
t[this] = TIME_LIFE
local a
if ANGLE_RND then
a = GetRandomReal(0, 2*bj_PI)
else
a = ANGLE
end
as[this] = Sin(a)*VELOCITY
ac[this] = Cos(a)*VELOCITY
ah[this] = 0.
if IsUnitVisible(u, GetLocalPlayer()) then
tt[this] = CreateTextTag()
SetTextTagPermanent(tt[this], false)
SetTextTagLifespan(tt[this], TIME_LIFE)
SetTextTagFadepoint(tt[this], TIME_FADE)
SetTextTagText(tt[this], s, SIZE_MIN)
SetTextTagPos(tt[this], x[this], y[this], Z_OFFSET)
end
if prev[this] == 0 then
TimerStart(TMR, DEFINITION, true, function()
local this = next[0]
local p
while (this ~= 0) do
p = Sin(bj_PI*t[this])
t[this] = t[this] - DEFINITION
x[this] = x[this] + ac[this]
y[this] = y[this] + as[this]
SetTextTagPos(tt[this], x[this], y[this], Z_OFFSET + Z_OFFSET_BON * p)
SetTextTagText(tt[this], str[this], SIZE_MIN + SIZE_BONUS * p)
if t[this] <= 0.0 then
tt[this] = null
next[prev[this]] = next[this]
prev[next[this]] = prev[this]
rn[this] = rn[0]
rn[0] = this
if next[0] == 0 then
PauseTimer(TMR)
end
end
this = next[this]
end
end)
end
return this
end
end)
if Debug and Debug.endFile then Debug.endFile() end
if Debug and Debug.beginFile then Debug.beginFile("RegisterPlayerUnitEvent") end
--[[
/**************************************************************
*
* RegisterPlayerUnitEvent
* v5.1.0.1
* By Magtheridon96
* Lua version by Chopinski
*
* I would like to give a special thanks to Bribe, azlier
* and BBQ for improving this library. For modularity, it only
* supports player unit events.
*
* Functions passed to RegisterPlayerUnitEvent must either
* return a boolean (false) or nothing. (Which is a Pro)
*
* Warning:
* --------
*
* - Don't use TriggerSleepAction inside registered code.
* - Don't destroy a trigger unless you really know what you're doing.
*
* API:
* ----
*
* - function RegisterPlayerUnitEvent takes playerunitevent whichEvent, code whichFunction returns nothing
* - Registers code that will execute when an event fires.
* - function RegisterPlayerUnitEventForPlayer takes playerunitevent whichEvent, code whichFunction, player whichPlayer returns nothing
* - Registers code that will execute when an event fires for a certain player.
* - function GetPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
* - Returns the trigger corresponding to ALL functions of a playerunitevent.
*
**************************************************************/
]]--
do
local trigger = {}
local f = {}
local n = {}
function RegisterPlayerUnitEvent(playerunitevent, code)
if type(code) == "function" then
local i = GetHandleId(playerunitevent)
if not trigger[i] then
trigger[i] = CreateTrigger()
for j = 0, bj_MAX_PLAYERS do
TriggerRegisterPlayerUnitEvent(trigger[i], Player(j), playerunitevent, null)
end
end
if not n[i] then n[i] = 1 end
if not f[i] then f[i] = {} end
table.insert(f[i], code)
TriggerAddCondition(trigger[i], Filter(function()
f[i][n[i]]()
n[i] = n[i] + 1
if n[i] > #f[i] then n[i] = 1 end
end))
end
end
function RegisterPlayerUnitEventForPlayer(playerunitevent, code, player)
if type(code) == "function" then
local i = (bj_MAX_PLAYERS + 1) * GetHandleId(playerunitevent) + GetPlayerId(player)
if not trigger[i] then
trigger[i] = CreateTrigger()
TriggerRegisterPlayerUnitEvent(trigger[i], player, playerunitevent, null)
end
if not n[i] then n[i] = 1 end
if not f[i] then f[i] = {} end
table.insert(f[i], code)
TriggerAddCondition(event[i].trigger, Filter(function()
f[i][n[i]]()
n[i] = n[i] + 1
if n[i] > #f[i] then n[i] = 1 end
end))
end
end
function GetPlayerUnitEventTrigger(playerunitevent)
return trigger[GetHandleId(playerunitevent)]
end
end
if Debug and Debug.endFile then Debug.endFile() end
if Debug and Debug.beginFile then Debug.beginFile("MissileEffect") end
--[[
-- ------------------------------------- Missile Effect v2.7 ------------------------------------ --
-- Credits to Forsakn for the first translation of Missile Effect to LUA
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
do
MissileEffect = setmetatable({}, {})
local mt = getmetatable(MissileEffect)
mt.__index = mt
function mt:destroy()
local size = #self.array
for i = 1, size do
local this = self.array[i]
DestroyEffect(this.effect)
this = nil
end
DestroyEffect(self.effect)
self = nil
end
function mt:scale(effect, scale)
self.size = scale
BlzSetSpecialEffectScale(effect, scale)
end
function mt:orient(yaw, pitch, roll)
self.yaw = yaw
self.pitch = pitch
self.roll = roll
BlzSetSpecialEffectOrientation(self.effect, yaw, pitch, roll)
for i = 1, #self.array do
local this = self.array[i]
this.yaw = yaw
this.pitch = pitch
this.roll = roll
BlzSetSpecialEffectOrientation(this.effect, yaw, pitch, roll)
end
end
function mt:move(x, y, z)
if not (x > WorldBounds.maxX or x < WorldBounds.minX or y > WorldBounds.maxY or y < WorldBounds.minY) then
BlzSetSpecialEffectPosition(self.effect, x, y, z)
for i = 1, #self.array do
local this = self.array[i]
BlzSetSpecialEffectPosition(this.effect, x - this.x, y - this.y, z - this.z)
end
return true
end
return false
end
function mt:attach(model, dx, dy, dz, scale)
local this = {}
this.x = dx
this.y = dy
this.z = dz
this.yaw = 0
this.pitch = 0
this.roll = 0
this.path = model
this.size = scale
this.effect = AddSpecialEffect(model, dx, dy)
BlzSetSpecialEffectZ(this.effect, dz)
BlzSetSpecialEffectScale(this.effect, scale)
BlzSetSpecialEffectPosition(this.effect, BlzGetLocalSpecialEffectX(this.effect) - dx, BlzGetLocalSpecialEffectY(this.effect) - dy, BlzGetLocalSpecialEffectZ(this.effect) - dz)
table.insert(self.array, this)
return this.effect
end
function mt:detach(effect)
for i = 1, #self.array do
local this = self.array[i]
if this.effect == effect then
table.remove(self.array, i)
DestroyEffect(effect)
this = nil
break
end
end
end
function mt:setColor(red, green, blue)
BlzSetSpecialEffectColor(self.effect, red, green, blue)
end
function mt:timeScale(real)
BlzSetSpecialEffectTimeScale(self.effect, real)
end
function mt:alpha(integer)
BlzSetSpecialEffectAlpha(self.effect, integer)
end
function mt:playerColor(integer)
BlzSetSpecialEffectColorByPlayer(self.effect, Player(integer))
end
function mt:animation(integer)
BlzPlaySpecialEffect(self.effect, ConvertAnimType(integer))
end
function mt:create(x, y, z)
local this = {}
setmetatable(this, mt)
this.path = ""
this.size = 1
this.yaw = 0
this.pitch = 0
this.roll = 0
this.array = {}
this.effect = AddSpecialEffect("", x, y)
BlzSetSpecialEffectZ(this.effect, z)
return this
end
end
if Debug and Debug.endFile then Debug.endFile() end
if Debug and Debug.beginFile then Debug.beginFile("MissileUtils") end
--[[ requires Missiles
-- ------------------------------------- Missile Utils v2.7 ------------------------------------- --
-- This is a simple Utils library for the Relativistic Missiles system.
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
do
-- ---------------------------------------------------------------------------------------------- --
-- LUA API --
-- ---------------------------------------------------------------------------------------------- --
function CreateMissileGroup()
return MissileGroup:create()
end
function DestroyMissileGroup(group)
if group then
group:destroy()
end
end
function MissileGroupGetSize(group)
if group then
return #group.group
else
return 0
end
end
function GroupMissileAt(group, position)
if group then
return group:missileAt(position)
else
return nil
end
end
function ClearMissileGroup(group)
if group then
group:clear()
end
end
function IsMissileInGroup(missile, group)
if group and missile then
if #group.group > 0 then
return group:contains(missile)
else
return false
end
else
return false
end
end
function GroupRemoveMissile(group, missile)
if group and missile then
if #group.group > 0 then
group:remove(missile)
end
end
end
function GroupAddMissile(group, missile)
if group and missile then
if not group:contains(missile) then
group:insert(missile)
end
end
end
function GroupPickRandomMissile(group)
if group then
if #group.group > 0 then
return group:missileAt(GetRandomInt(0, #group.group - 1))
else
return nil
end
else
return nil
end
end
function FirstOfMissileGroup(group)
if group then
if #group.group > 0 then
return group.group[1]
else
return nil
end
else
return nil
end
end
function GroupAddMissileGroup(source, destiny)
if source and destiny then
if #source.group > 0 and source ~= destiny then
destiny:addGroup(source)
end
end
end
function GroupRemoveMissileGroup(source, destiny)
if source and destiny then
if source == destiny then
source:clear()
elseif #source.group > 0 then
destiny:removeGroup(source)
end
end
end
function GroupEnumMissilesOfType(group, type)
if group then
if Missiles.count > -1 then
if #group.group > 0 then
group:clear()
end
for i = 0, Missiles.count do
local missile = Missiles.collection[i]
if missile.type == type then
group:insert(missile)
end
end
end
end
end
function GroupEnumMissilesOfTypeCounted(group, type, amount)
local i = 0
local j = amount
if group then
if Missiles.count > -1 then
if #group.group > 0 then
group:clear()
end
while i <= Missiles.count and j > 0 do
local missile = Missiles.collection[i]
if missile.type == type then
group:insert(missile)
end
j = j - 1
i = i + 1
end
end
end
end
function GroupEnumMissilesOfPlayer(group, player)
if group then
if Missiles.count > -1 then
if #group.group > 0 then
group:clear()
end
for i = 0, Missiles.count do
local missile = Missiles.collection[i]
if missile.owner == player then
group:insert(missile)
end
end
end
end
end
function GroupEnumMissilesOfPlayerCounted(group, player, amount)
local i = 0
local j = amount
if group then
if Missiles.count > -1 then
if #group.group > 0 then
group:clear()
end
while i <= Missiles.count and j > 0 do
local missile = Missiles.collection[i]
if missile.owner == player then
group:insert(missile)
end
j = j - 1
i = i + 1
end
end
end
end
function GroupEnumMissilesInRect(group, rect)
if group and rect then
if Missiles.count > -1 then
if #group.group > 0 then
group:clear()
end
for i = 0, Missiles.count do
local missile = Missiles.collection[i]
if GetRectMinX(rect) <= missile.x and missile.x <= GetRectMaxX(rect) and GetRectMinY(rect) <= missile.y and missile.y <= GetRectMaxY(rect) then
group:insert(missile)
end
end
end
end
end
function GroupEnumMissilesInRectCounted(group, rect, amount)
local i = 0
local j = amount
if group and rect then
if Missiles.count > -1 then
if #group.group > 0 then
group:clear()
end
while i <= Missiles.count and j > 0 do
local missile = Missiles.collection[i]
if GetRectMinX(rect) <= missile.x and missile.x <= GetRectMaxX(rect) and GetRectMinY(rect) <= missile.y and missile.y <= GetRectMaxY(rect) then
group:insert(missile)
end
j = j - 1
i = i + 1
end
end
end
end
function GroupEnumMissilesInRangeOfLoc(group, location, radius)
if group and location and radius > 0 then
if Missiles.count > -1 then
if #group.group > 0 then
group:clear()
end
for i = 0, Missiles.count do
local missile = Missiles.collection[i]
local dx = missile.x - GetLocationX(location)
local dy = missile.y - GetLocationY(location)
if SquareRoot(dx*dx + dy*dy) <= radius then
group:insert(missile)
end
end
end
end
end
function GroupEnumMissilesInRangeOfLocCounted(group, location, radius, amount)
local i = 0
local j = amount
if group and location and radius > 0 then
if Missiles.count > -1 then
if #group.group > 0 then
group:clear()
end
while i <= Missiles.count and j > 0 do
local missile = Missiles.collection[i]
local dx = missile.x - GetLocationX(location)
local dy = missile.y - GetLocationY(location)
if SquareRoot(dx*dx + dy*dy) <= radius then
group:insert(missile)
end
j = j - 1
i = i + 1
end
end
end
end
function GroupEnumMissilesInRange(group, x, y, radius)
if group and radius > 0 then
if Missiles.count > -1 then
if #group.group > 0 then
group:clear()
end
for i = 0, Missiles.count do
local missile = Missiles.collection[i]
local dx = missile.x - x
local dy = missile.y - y
if SquareRoot(dx*dx + dy*dy) <= radius then
group:insert(missile)
end
end
end
end
end
function GroupEnumMissilesInRangeCounted(group, x, y, radius, amount)
local i = 0
local j = amount
if group and radius > 0 then
if Missiles.count > -1 then
if #group.group > 0 then
group:clear()
end
while i <= Missiles.count and j > 0 do
local missile = Missiles.collection[i]
local dx = missile.x - x
local dy = missile.y - y
if SquareRoot(dx*dx + dy*dy) <= radius then
group:insert(missile)
end
j = j - 1
i = i + 1
end
end
end
end
-- ---------------------------------------------------------------------------------------------- --
-- System --
-- ---------------------------------------------------------------------------------------------- --
MissileGroup = setmetatable({}, {})
local mt = getmetatable(MissileGroup)
mt.__index = mt
function mt:destroy()
self.group = nil
self.set = nil
self = nil
end
function mt:missileAt(i)
if #self.group > 0 and i <= #self.group - 1 then
return self.group[i + 1]
else
return 0
end
end
function mt:remove(missile)
for i = 1, #self.group do
if self.group[i] == missile then
self.set[missile] = nil
table.remove(self.group, i)
break
end
end
end
function mt:insert(missile)
table.insert(self.group, missile)
self.set[missile] = missile
end
function mt:clear()
local size = #self.group
for i = 1, size do
self.set[i] = nil
self.group[i] = nil
end
end
function mt:contains(missile)
return self.set[missile] ~= nil
end
function mt:addGroup(this)
for i = 1, #this.group do
if not self:contains(this.group[i]) then
self:insert(this.group[i])
end
end
end
function mt:removeGroup(this)
for i = 1, #this.group do
if self:contains(this.group[i]) then
self:remove(this.group[i])
end
end
end
function mt:create()
local this = {}
setmetatable(this, mt)
this.group = {}
this.set = {}
return this
end
end
if Debug and Debug.endFile then Debug.endFile() end
--[[ requires MissileEffect, optional MissilesUtils
-- ---------------------------------------- Missiles v2.8 --------------------------------------- --
-- Thanks and Credits to BPower, Dirac and Vexorian for the Missile Library's at which i based
-- this Missiles library. Credits and thanks to AGD and for the effect orientation ideas.
-- This version of Missiles requires patch 1.31+. Thanks to Forsakn for the first translation
-- of the vJASS version of Missiles into LUA.
--
-- How to Import:
-- 1 - Copy this, MissileEffect and optionaly the MissileUtils libraries into your map
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
do
-- ---------------------------------------------------------------------------------------------- --
-- Configuration --
-- ---------------------------------------------------------------------------------------------- --
-- The update period of the system
local PERIOD = 1. / 40.
-- The max amount of Missiles processed in a PERIOD
-- You can play around with both these values to find
-- your sweet spot. If equal to 0, the system will
-- process all missiles at once every period.
local SWEET_SPOT = 600
-- the avarage collision size compensation when detecting collisions
local COLLISION_SIZE = 128.
-- item size used in z collision
local ITEM_SIZE = 16.
-- Raw code of the dummy unit used for vision
local DUMMY = FourCC('dumi')
-- Needed, dont touch. Seriously, dont touch!
local location = Location(0., 0.)
local rect = Rect(0., 0., 0., 0.)
local function GetLocZ(x, y)
MoveLocation(location, x, y)
return GetLocationZ(location)
end
local function GetUnitZ(unit)
return GetLocZ(GetUnitX(unit), GetUnitY(unit)) + GetUnitFlyHeight(unit)
end
local function SetUnitZ(unit, z)
SetUnitFlyHeight(unit, z - GetLocZ(GetUnitX(unit), GetUnitY(unit)), 0)
end
local function GetMapCliffLevel()
return GetTerrainCliffLevel(WorldBounds.maxX, WorldBounds.maxY)
end
do
Pool = setmetatable({}, {})
local mt = getmetatable(Pool)
mt.__index = mt
local player = Player(PLAYER_NEUTRAL_PASSIVE)
local group = CreateGroup()
function mt:recycle(unit)
if GetUnitTypeId(unit) == DUMMY then
GroupAddUnit(group, unit)
SetUnitX(unit, WorldBounds.maxX)
SetUnitY(unit, WorldBounds.maxY)
SetUnitOwner(unit, player, false)
PauseUnit(unit, true)
end
end
function mt:retrieve(x, y, z, face)
if BlzGroupGetSize(group) > 0 then
bj_lastCreatedUnit = FirstOfGroup(group)
PauseUnit(bj_lastCreatedUnit, false)
GroupRemoveUnit(group, bj_lastCreatedUnit)
SetUnitX(bj_lastCreatedUnit, x)
SetUnitY(bj_lastCreatedUnit, y)
SetUnitZ(bj_lastCreatedUnit, z)
BlzSetUnitFacingEx(bj_lastCreatedUnit, face)
else
bj_lastCreatedUnit = CreateUnit(player, DUMMY, x, y, face)
SetUnitZ(bj_lastCreatedUnit, z)
UnitRemoveAbility(bj_lastCreatedUnit, FourCC('Amrf'))
end
return bj_lastCreatedUnit
end
function mt:recycleTimed(unit, delay)
if GetUnitTypeId(unit) == DUMMY then
local timer = CreateTimer()
TimerStart(timer, delay, false, function()
self:recycle(unit)
PauseTimer(timer)
DestroyTimer(timer)
end)
end
end
onInit(function()
local timer = CreateTimer()
TimerStart(timer, 0, false, function()
for i = 0, SWEET_SPOT do
local unit = CreateUnit(player, DUMMY, WorldBounds.maxX, WorldBounds.maxY, 0)
PauseUnit(unit, false)
GroupAddUnit(group, unit)
UnitRemoveAbility(unit, FourCC('Amrf'))
end
PauseTimer(timer)
DestroyTimer(timer)
end)
end)
end
do
Coordinates = setmetatable({}, {})
local mt = getmetatable(Coordinates)
mt.__index = mt
function mt:destroy()
self = nil
end
function mt:math(a, b)
local dx
local dy
while true do
dx = b.x - a.x
dy = b.y - a.y
dx = dx * dx + dy * dy
dy = SquareRoot(dx)
if dx ~= 0. and dy ~= 0. then
break
end
b.x = b.x + .01
b.z = b.z - GetLocZ(b.x - .01, b.y) + GetLocZ(b.x, b.y)
end
a.square = dx
a.distance = dy
a.angle = Atan2(b.y - a.y, b.x - a.x)
a.slope = (b.z - a.z) / dy
a.alpha = Atan(a.slope)
-- Set b.
if b.ref == a then
b.angle = a.angle + bj_PI
b.distance = dy
b.slope = -a.slope
b.alpha = -a.alpha
b.square = dx
end
end
function mt:link(a, b)
a.ref = b
b.ref = a
self:math(a, b)
end
function mt:move(toX, toY, toZ)
self.x = toX
self.y = toY
self.z = toZ + GetLocZ(toX, toY)
if self.ref ~= self then
self:math(self, self.ref)
end
end
function mt:create(x, y, z)
local c = {}
setmetatable(c, mt)
c.ref = c
c:move(x, y, z)
return c
end
end
-- -------------------------------------------------------------------------- --
-- Missiles --
-- -------------------------------------------------------------------------- --
Missiles = setmetatable({}, {})
local mt = getmetatable(Missiles)
mt.__index = mt
Missiles.collection = {}
Missiles.count = -1
local timer = CreateTimer()
local group = CreateGroup()
local id = -1
local pid = -1
local last = 0
local dilation = 1
local array = {}
local missiles = {}
local frozen = {}
local keys = {}
local index = 1
local yaw = 0
local pitch = 0
local travelled = 0
function mt:OnHit()
if self.onHit then
if self.allocated and self.collision > 0 then
GroupEnumUnitsInRange(group, self.x, self.y, self.collision + COLLISION_SIZE, nil)
local unit = FirstOfGroup(group)
while unit do
if array[self][unit] == nil then
if IsUnitInRangeXY(unit, self.x, self.y, self.collision) then
if self.collideZ then
local dx = GetLocZ(GetUnitX(unit), GetUnitY(unit)) + GetUnitFlyHeight(unit)
local dy = BlzGetUnitCollisionSize(unit)
if dx + dy >= self.z - self.collision and dx <= self.z + self.collision then
array[self][unit] = true
if self.allocated and self.onHit(unit) then
self:terminate()
break
end
end
else
array[self][unit] = true
if self.allocated and self.onHit(unit) then
self:terminate()
break
end
end
end
end
GroupRemoveUnit(group, unit)
unit = FirstOfGroup(group)
end
end
end
end
function mt:OnMissile()
if self.onMissile then
if self.allocated and self.collision > 0 then
for i = 0, Missiles.count do
local missile = Missiles.collection[i]
if missile ~= self then
if array[self][missile] == nil then
local dx = missile.x - self.x
local dy = missile.y - self.y
if SquareRoot(dx*dx + dy*dy) <= self.collision then
array[self][missile] = true
if self.allocated and self.onMissile(missile) then
self:terminate()
break
end
end
end
end
end
end
end
end
function mt:OnDestructable()
if self.onDestructable then
if self.allocated and self.collision > 0 then
local dx = self.collision
SetRect(rect, self.x - dx, self.y - dx, self.x + dx, self.y + dx)
EnumDestructablesInRect(rect, nil, function()
local destructable = GetEnumDestructable()
if array[self][destructable] == nil then
if self.collideZ then
local dz = GetLocZ(GetWidgetX(destructable), GetWidgetY(destructable))
local tz = GetDestructableOccluderHeight(destructable)
if dz + tz >= self.z - self.collision and dz <= self.z + self.collision then
array[self][destructable] = true
if self.allocated and self.onDestructable(destructable) then
self:terminate()
return
end
end
else
array[self][destructable] = true
if self.allocated and self.onDestructable(destructable) then
self:terminate()
return
end
end
end
end)
end
end
end
function mt:OnItem()
if self.onItem then
if self.allocated and self.collision > 0 then
local dx = self.collision
SetRect(rect, self.x - dx, self.y - dx, self.x + dx, self.y + dx)
EnumItemsInRect(rect, nil, function()
local item = GetEnumItem()
if array[self][item] == nil then
if self.collideZ then
local dz = GetLocZ(GetItemX(item), GetItemY(item))
if dz + ITEM_SIZE >= self.z - self.collision and dz <= self.z + self.collision then
array[self][item] = true
if self.allocated and self.onItem(item) then
self:terminate()
return
end
end
else
array[self][item] = true
if self.allocated and self.onItem(item) then
self:terminate()
return
end
end
end
end)
end
end
end
function mt:OnCliff()
if self.onCliff then
local dx = GetTerrainCliffLevel(self.nextX, self.nextY)
local dy = GetTerrainCliffLevel(self.x, self.y)
if dy < dx and self.z < (dx - GetMapCliffLevel())*bj_CLIFFHEIGHT then
if self.allocated and self.onCliff() then
self:terminate()
end
end
end
end
function mt:OnTerrain()
if self.onTerrain then
if GetLocZ(self.x, self.y) > self.z then
if self.allocated and self.onTerrain() then
self:terminate()
end
end
end
end
function mt:OnTileset()
if self.onTileset then
local type = GetTerrainType(self.x, self.y)
if type ~= self.tileset then
if self.allocated and self.onTileset(type) then
self:terminate()
end
end
self.tileset = type
end
end
function mt:OnPeriod()
if self.onPeriod then
if self.allocated and self.onPeriod() then
self:terminate()
end
end
end
function mt:OnOrient()
local a
-- Homing or not
if self.target and GetUnitTypeId(self.target) ~= 0 then
self.impact:move(GetUnitX(self.target), GetUnitY(self.target), GetUnitFlyHeight(self.target) + self.toZ)
local dx = self.impact.x - self.nextX
local dy = self.impact.y - self.nextY
a = Atan2(dy, dx)
self.travel = self.origin.distance - SquareRoot(dx*dx + dy*dy)
else
a = self.origin.angle
self.target = nil
end
-- turn rate
if self.turn ~= 0 and not (Cos(self.cA - a) >= Cos(self.turn)) then
if Sin(a - self.cA) >= 0 then
self.cA = self.cA + self.turn
else
self.cA = self.cA - self.turn
end
else
self.cA = a
end
local vel = self.veloc*dilation
yaw = self.cA
travelled = self.travel + vel
self.veloc = self.veloc + self.acceleration
self.travel = travelled
pitch = self.origin.alpha
self.prevX = self.x
self.prevY = self.y
self.prevZ = self.z
self.x = self.nextX
self.y = self.nextY
self.z = self.nextZ
self.nextX = self.x + vel*Cos(yaw)
self.nextY = self.y + vel*Sin(yaw)
-- arc calculation
local s = travelled
local d = self.origin.distance
local h = self.height
if h ~= 0 or self.origin.slope ~= 0 then
self.nextZ = 4*h*s*(d-s)/(d*d) + self.origin.slope*s + self.origin.z
pitch = pitch - Atan(((4*h)*(2*s - d))/(d*d))
end
-- curve calculation
local c = self.open
if c ~= 0 then
local dx = 4 * c * s * (d - s) / (d * d)
a = yaw + bj_PI / 2
self.x = self.x + dx * Cos(a)
self.y = self.y + dx * Sin(a)
yaw = yaw + Atan(-((4 * c) * (2 * s - d)) / (d * d))
end
end
function mt:OnFinish()
if travelled >= self.origin.distance - 0.0001 then
self.finished = true
if self.onFinish then
if self.allocated and self.onFinish() then
self:terminate()
else
if self.travel > 0 and not self.paused then
self:terminate()
end
end
else
self:terminate()
end
else
if not self.roll then
self.effect:orient(yaw, -pitch, 0)
else
self.effect:orient(yaw, -pitch, Atan2(self.open, self.height))
end
end
end
function mt:OnBoundaries()
if not self.effect:move(self.x, self.y, self.z) then
if self.onBoundaries then
if self.allocated and self.onBoundaries() then
self:terminate()
end
end
else
if self.dummy then
SetUnitX(self.dummy, self.x)
SetUnitY(self.dummy, self.y)
end
end
end
function mt:OnPause()
pid = pid + 1
self.pkey = pid
frozen[pid] = self
if self.onPause then
if self.allocated and self.onPause() then
self:terminate()
end
end
end
function mt:OnResume(flag)
local this
self.paused = flag
if not self.paused and self.pkey ~= -1 then
id = id + 1
missiles[id] = self
this = frozen[pid]
this.pkey = self.pkey
frozen[self.pkey] = frozen[pid]
pid = pid - 1
self.pkey = -1
if id + 1 > SWEET_SPOT and SWEET_SPOT > 0 then
dilation = (id + 1)/SWEET_SPOT
else
dilation = 1.
end
if id == 0 then
TimerStart(timer, PERIOD, true, function() Missiles:move() end)
end
if self.onResume then
if self.allocated and self.onResume() then
self:terminate()
else
if self.finished then
self:terminate()
end
end
else
if self.finished then
self:terminate()
end
end
end
end
function mt:OnRemove()
local this
if self.allocated and self.launched then
self.allocated = false
if self.pkey ~= -1 then
this = frozen[pid]
this.pkey = self.pkey
frozen[self.pkey] = frozen[pid]
pid = pid - 1
self.pkey = -1
end
if self.onRemove then
self.onRemove()
end
if self.dummy then
Pool:recycle(self.dummy)
end
this = Missiles.collection[Missiles.count]
this.index = self.index
Missiles.collection[self.index] = Missiles.collection[Missiles.count]
Missiles.count = Missiles.count - 1
self.index = -1
self.origin:destroy()
self.impact:destroy()
self.effect:destroy()
self:reset()
array[self] = nil
end
end
-- -------------------------- Model of the missile -------------------------- --
function mt:model(effect)
DestroyEffect(self.effect.effect)
self.effect.path = effect
self.Model = effect
self.effect.effect = AddSpecialEffect(effect, self.origin.x, self.origin.y)
BlzSetSpecialEffectZ(self.effect.effect, self.origin.z)
BlzSetSpecialEffectYaw(self.effect.effect, self.cA)
end
-- ----------------------------- Curved movement ---------------------------- --
function mt:curve(value)
self.open = Tan(value * bj_DEGTORAD) * self.origin.distance
self.Curve = value
end
-- ----------------------------- Arced Movement ----------------------------- --
function mt:arc(value)
self.height = Tan(value * bj_DEGTORAD) * self.origin.distance / 4
self.Arc = value
end
-- ------------------------------ Effect scale ------------------------------ --
function mt:scale(value)
self.effect.size = value
self.effect:scale(self.effect.effect, value)
self.Scale = value
end
-- ------------------------------ Missile Speed ----------------------------- --
function mt:speed(value)
self.veloc = value * PERIOD
self.Speed = value
local vel = self.veloc*dilation
local s = self.travel + vel
local d = self.origin.distance
self.nextX = self.x + vel*Cos(self.cA)
self.nextY = self.y + vel*Sin(self.cA)
if self.height ~= 0 or self.origin.slope ~= 0 then
self.nextZ = 4*self.height*s*(d-s)/(d*d) + self.origin.slope*s + self.origin.z
self.z = self.nextZ
end
end
-- ------------------------------- Flight Time ------------------------------ --
function mt:duration(value)
self.veloc = RMaxBJ(0.00000001, (self.origin.distance - self.travel) * PERIOD / RMaxBJ(0.00000001, value))
self.Duration = value
local vel = self.veloc*dilation
local s = self.travel + vel
local d = self.origin.distance
self.nextX = self.x + vel*Cos(self.cA)
self.nextY = self.y + vel*Sin(self.cA)
if self.height ~= 0 or self.origin.slope ~= 0 then
self.nextZ = 4*self.height*s*(d-s)/(d*d) + self.origin.slope*s + self.origin.z
self.z = self.nextZ
end
end
-- ------------------------------- Sight Range ------------------------------ --
function mt:vision(sightRange)
self.Vision = sightRange
if self.dummy then
SetUnitOwner(self.dummy, self.owner, false)
BlzSetUnitRealField(self.dummy, UNIT_RF_SIGHT_RADIUS, sightRange)
else
if not self.owner then
if self.source then
self.dummy = Pool:retrieve(self.x, self.y, self.z, 0)
SetUnitOwner(self.dummy, GetOwningPlayer(self.source), false)
BlzSetUnitRealField(self.dummy, UNIT_RF_SIGHT_RADIUS, sightRange)
end
else
self.dummy = Pool:retrieve(self.x, self.y, self.z, 0)
SetUnitOwner(self.dummy, self.owner, false)
BlzSetUnitRealField(self.dummy, UNIT_RF_SIGHT_RADIUS, sightRange)
end
end
end
-- ------------------------------- Time Scale ------------------------------- --
function mt:timeScale(real)
self.TimeScale = real
self.effect:timeScale(real)
end
-- ---------------------------------- Alpha --------------------------------- --
function mt:alpha(integer)
self.Alpha = integer
self.effect:alpha(integer)
end
-- ------------------------------ Player Color ------------------------------ --
function mt:playerColor(integer)
self.playercolor = integer
self.effect:playerColor(integer)
end
-- -------------------------------- Animation ------------------------------- --
function mt:animation(integer)
self.Animation = integer
self.effect:animation(integer)
end
-- --------------------------- Bounce and Deflect --------------------------- --
function mt:bounce()
self.origin:move(self.x, self.y, self.z - GetLocZ(self.x, self.y))
travelled = 0
self.travel = 0
self.finished = false
end
function mt:deflect(tx, ty, tz)
local locZ = GetLocZ(self.x, self.y)
if self.z < locZ and self.onTerrain then
self.nextX = self.prevX
self.nextY = self.prevY
self.nextZ = self.prevZ
end
self.toZ = tz
self.target = nil
self.impact:move(tx, ty, tz)
self.origin:move(self.x, self.y, self.z - locZ)
travelled = 0
self.travel = 0
self.finished = false
end
function mt:deflectTarget(unit)
self:deflect(GetUnitX(unit), GetUnitY(unit), self.toZ)
self.target = unit
end
-- ---------------------------- Flush hit targets --------------------------- --
function mt:flushAll()
array[self] = nil
array[self] = {}
end
function mt:flush(widget)
if widget then
array[self][widget] = nil
end
end
function mt:hitted(widget)
return array[self][widget]
end
-- ----------------------- Missile attachment methods ----------------------- --
function mt:attach(model, dx, dy, dz, scale)
return self.effect:attach(model, dx, dy, dz, scale)
end
function mt:detach(effect)
if effect then
self.effect:detach(effect)
end
end
-- ------------------------------ Missile Pause ----------------------------- --
function mt:pause(flag)
self:OnResume(flag)
end
-- ---------------------------------- Color --------------------------------- --
function mt:color(red, green, blue)
self.effect:setColor(red, green, blue)
end
-- ------------------------------ Reset members ----------------------------- --
function mt:reset()
self.launched = false
self.collideZ = false
self.finished = false
self.paused = false
self.roll = false
self.source = nil
self.target = nil
self.owner = nil
self.dummy = nil
self.open = 0.
self.height = 0.
self.veloc = 0.
self.acceleration = 0.
self.collision = 0.
self.damage = 0.
self.travel = 0.
self.turn = 0.
self.data = 0.
self.type = 0
self.tileset = 0
self.pkey = -1
self.index = -1
self.Model = ""
self.Duration = 0
self.Scale = 1
self.Speed = 0
self.Arc = 0
self.Curve = 0
self.Vision = 0
self.TimeScale = 0.
self.Alpha = 0
self.playercolor = 0
self.Animation = 0
self.onHit = nil
self.onMissile = nil
self.onDestructable = nil
self.onItem = nil
self.onCliff = nil
self.onTerrain = nil
self.onTileset = nil
self.onPeriod = nil
self.onFinish = nil
self.onBoundaries = nil
self.onPause = nil
self.onResume = nil
self.onRemove = nil
end
-- -------------------------------- Terminate ------------------------------- --
function mt:terminate()
self:OnRemove()
end
-- -------------------------- Destroys the missile -------------------------- --
function mt:remove(i)
if self.paused then
self:OnPause()
else
self:OnRemove()
end
missiles[i] = missiles[id]
id = id - 1
if id + 1 > SWEET_SPOT and SWEET_SPOT > 0 then
dilation = (id + 1) / SWEET_SPOT
else
dilation = 1
end
if id == -1 then
PauseTimer(timer)
end
if not self.allocated then
table.insert(keys, self.key)
self = nil
end
return i - 1
end
-- ---------------------------- Missiles movement --------------------------- --
function mt:move()
local i = 0
local j = 0
if SWEET_SPOT > 0 then
i = last
else
i = 0
end
while not ((j >= SWEET_SPOT and SWEET_SPOT > 0) or j > id) do
local this = missiles[i]
if this.allocated and not this.paused then
this:OnHit()
this:OnMissile()
this:OnDestructable()
this:OnItem()
this:OnCliff()
this:OnTerrain()
this:OnTileset()
this:OnPeriod()
this:OnOrient()
this:OnFinish()
this:OnBoundaries()
else
i = this:remove(i)
j = j - 1
end
i = i + 1
j = j + 1
if i > id and SWEET_SPOT > 0 then
i = 0
end
end
last = i
end
-- --------------------------- Launch the Missile --------------------------- --
function mt:launch()
if not self.launched and self.allocated then
self.launched = true
id = id + 1
missiles[id] = self
Missiles.count = Missiles.count + 1
self.index = Missiles.count
Missiles.collection[Missiles.count] = self
if id + 1 > SWEET_SPOT and SWEET_SPOT > 0 then
dilation = (id + 1) / SWEET_SPOT
else
dilation = 1.
end
if id == 0 then
TimerStart(timer, PERIOD, true, function() Missiles:move() end)
end
end
end
-- --------------------------- Main Creator method -------------------------- --
function mt:create(x, y, z, toX, toY, toZ)
local this = {}
setmetatable(this, mt)
array[this] = {}
if #keys > 0 then
this.key = keys[#keys]
keys[#keys] = nil
else
this.key = index
index = index + 1
end
this:reset()
this.origin = Coordinates:create(x, y, z)
this.impact = Coordinates:create(toX, toY, toZ)
this.effect = MissileEffect:create(x, y, this.origin.z)
Coordinates:link(this.origin, this.impact)
this.allocated = true
this.cA = this.origin.angle
this.x = x
this.y = y
this.z = this.impact.z
this.prevX = x
this.prevY = y
this.prevZ = this.impact.z
this.nextX = x
this.nextY = y
this.nextZ = this.impact.z
this.toZ = toZ
return this
end
end
if Debug and Debug.beginFile then Debug.beginFile("Missiles") end
--[[ requires MissileEffect, optional MissilesUtils
-- ---------------------------------------- Missiles v2.8 --------------------------------------- --
-- Thanks and Credits to BPower, Dirac and Vexorian for the Missile Library's at which i based
-- this Missiles library. Credits and thanks to AGD and for the effect orientation ideas.
-- This version of Missiles requires patch 1.31+. Thanks to Forsakn for the first translation
-- of the vJASS version of Missiles into LUA.
--
-- How to Import:
-- 1 - Copy this, MissileEffect and optionaly the MissileUtils libraries into your map
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
OnInit.root(function()
-- ---------------------------------------------------------------------------------------------- --
-- Configuration --
-- ---------------------------------------------------------------------------------------------- --
-- The update period of the system
local PERIOD = 1. / 40.
-- The max amount of Missiles processed in a PERIOD
-- You can play around with both these values to find
-- your sweet spot. If equal to 0, the system will
-- process all missiles at once every period.
local SWEET_SPOT = 600
-- the avarage collision size compensation when detecting collisions
local COLLISION_SIZE = 128.
-- item size used in z collision
local ITEM_SIZE = 16.
-- Raw code of the dummy unit used for vision
local DUMMY = FourCC('dumi')
-- Needed, dont touch. Seriously, dont touch!
local location = Location(0., 0.)
local rect = Rect(0., 0., 0., 0.)
local function GetLocZ(x, y)
MoveLocation(location, x, y)
return GetLocationZ(location)
end
local function GetUnitZ(unit)
return GetLocZ(GetUnitX(unit), GetUnitY(unit)) + GetUnitFlyHeight(unit)
end
local function SetUnitZ(unit, z)
SetUnitFlyHeight(unit, z - GetLocZ(GetUnitX(unit), GetUnitY(unit)), 0)
end
local function GetMapCliffLevel()
return GetTerrainCliffLevel(WorldBounds.maxX, WorldBounds.maxY)
end
do
Pool = setmetatable({}, {})
local mt = getmetatable(Pool)
mt.__index = mt
local player = Player(PLAYER_NEUTRAL_PASSIVE)
local group = CreateGroup()
function mt:recycle(unit)
if GetUnitTypeId(unit) == DUMMY then
GroupAddUnit(group, unit)
SetUnitX(unit, WorldBounds.maxX)
SetUnitY(unit, WorldBounds.maxY)
SetUnitOwner(unit, player, false)
PauseUnit(unit, true)
end
end
function mt:retrieve(x, y, z, face)
if BlzGroupGetSize(group) > 0 then
bj_lastCreatedUnit = FirstOfGroup(group)
PauseUnit(bj_lastCreatedUnit, false)
GroupRemoveUnit(group, bj_lastCreatedUnit)
SetUnitX(bj_lastCreatedUnit, x)
SetUnitY(bj_lastCreatedUnit, y)
SetUnitZ(bj_lastCreatedUnit, z)
BlzSetUnitFacingEx(bj_lastCreatedUnit, face)
else
bj_lastCreatedUnit = CreateUnit(player, DUMMY, x, y, face)
SetUnitZ(bj_lastCreatedUnit, z)
UnitRemoveAbility(bj_lastCreatedUnit, FourCC('Amrf'))
end
return bj_lastCreatedUnit
end
function mt:recycleTimed(unit, delay)
if GetUnitTypeId(unit) == DUMMY then
local timer = CreateTimer()
TimerStart(timer, delay, false, function()
self:recycle(unit)
PauseTimer(timer)
DestroyTimer(timer)
end)
end
end
OnInit.global(function()
local timer = CreateTimer()
TimerStart(timer, 0, false, function()
for i = 0, SWEET_SPOT do
local unit = CreateUnit(player, DUMMY, WorldBounds.maxX, WorldBounds.maxY, 0)
PauseUnit(unit, false)
GroupAddUnit(group, unit)
UnitRemoveAbility(unit, FourCC('Amrf'))
end
PauseTimer(timer)
DestroyTimer(timer)
end)
end)
end
do
Coordinates = setmetatable({}, {})
local mt = getmetatable(Coordinates)
mt.__index = mt
function mt:destroy()
self = nil
end
function mt:math(a, b)
local dx
local dy
while true do
dx = b.x - a.x
dy = b.y - a.y
dx = dx * dx + dy * dy
dy = SquareRoot(dx)
if dx ~= 0. and dy ~= 0. then
break
end
b.x = b.x + .01
b.z = b.z - GetLocZ(b.x - .01, b.y) + GetLocZ(b.x, b.y)
end
a.square = dx
a.distance = dy
a.angle = Atan2(b.y - a.y, b.x - a.x)
a.slope = (b.z - a.z) / dy
a.alpha = Atan(a.slope)
-- Set b.
if b.ref == a then
b.angle = a.angle + bj_PI
b.distance = dy
b.slope = -a.slope
b.alpha = -a.alpha
b.square = dx
end
end
function mt:link(a, b)
a.ref = b
b.ref = a
self:math(a, b)
end
function mt:move(toX, toY, toZ)
self.x = toX
self.y = toY
self.z = toZ + GetLocZ(toX, toY)
if self.ref ~= self then
self:math(self, self.ref)
end
end
function mt:create(x, y, z)
local c = {}
setmetatable(c, mt)
c.ref = c
c:move(x, y, z)
return c
end
end
-- -------------------------------------------------------------------------- --
-- Missiles --
-- -------------------------------------------------------------------------- --
Missiles = setmetatable({}, {})
local mt = getmetatable(Missiles)
mt.__index = mt
Missiles.collection = {}
Missiles.count = -1
local timer = CreateTimer()
local group = CreateGroup()
local id = -1
local pid = -1
local last = 0
local dilation = 1
local array = {}
local missiles = {}
local frozen = {}
local keys = {}
local index = 1
local yaw = 0
local pitch = 0
local travelled = 0
function mt:OnHit()
if self.onHit then
if self.allocated and self.collision > 0 then
GroupEnumUnitsInRange(group, self.x, self.y, self.collision + COLLISION_SIZE, nil)
local unit = FirstOfGroup(group)
while unit do
if array[self][unit] == nil then
if IsUnitInRangeXY(unit, self.x, self.y, self.collision) then
if self.collideZ then
local dx = GetLocZ(GetUnitX(unit), GetUnitY(unit)) + GetUnitFlyHeight(unit)
local dy = BlzGetUnitCollisionSize(unit)
if dx + dy >= self.z - self.collision and dx <= self.z + self.collision then
array[self][unit] = true
if self.allocated and self.onHit(unit) then
self:terminate()
break
end
end
else
array[self][unit] = true
if self.allocated and self.onHit(unit) then
self:terminate()
break
end
end
end
end
GroupRemoveUnit(group, unit)
unit = FirstOfGroup(group)
end
end
end
end
function mt:OnMissile()
if self.onMissile then
if self.allocated and self.collision > 0 then
for i = 0, Missiles.count do
local missile = Missiles.collection[i]
if missile ~= self then
if array[self][missile] == nil then
local dx = missile.x - self.x
local dy = missile.y - self.y
if SquareRoot(dx*dx + dy*dy) <= self.collision then
array[self][missile] = true
if self.allocated and self.onMissile(missile) then
self:terminate()
break
end
end
end
end
end
end
end
end
function mt:OnDestructable()
if self.onDestructable then
if self.allocated and self.collision > 0 then
local dx = self.collision
SetRect(rect, self.x - dx, self.y - dx, self.x + dx, self.y + dx)
EnumDestructablesInRect(rect, nil, function()
local destructable = GetEnumDestructable()
if array[self][destructable] == nil then
if self.collideZ then
local dz = GetLocZ(GetWidgetX(destructable), GetWidgetY(destructable))
local tz = GetDestructableOccluderHeight(destructable)
if dz + tz >= self.z - self.collision and dz <= self.z + self.collision then
array[self][destructable] = true
if self.allocated and self.onDestructable(destructable) then
self:terminate()
return
end
end
else
array[self][destructable] = true
if self.allocated and self.onDestructable(destructable) then
self:terminate()
return
end
end
end
end)
end
end
end
function mt:OnItem()
if self.onItem then
if self.allocated and self.collision > 0 then
local dx = self.collision
SetRect(rect, self.x - dx, self.y - dx, self.x + dx, self.y + dx)
EnumItemsInRect(rect, nil, function()
local item = GetEnumItem()
if array[self][item] == nil then
if self.collideZ then
local dz = GetLocZ(GetItemX(item), GetItemY(item))
if dz + ITEM_SIZE >= self.z - self.collision and dz <= self.z + self.collision then
array[self][item] = true
if self.allocated and self.onItem(item) then
self:terminate()
return
end
end
else
array[self][item] = true
if self.allocated and self.onItem(item) then
self:terminate()
return
end
end
end
end)
end
end
end
function mt:OnCliff()
if self.onCliff then
local dx = GetTerrainCliffLevel(self.nextX, self.nextY)
local dy = GetTerrainCliffLevel(self.x, self.y)
if dy < dx and self.z < (dx - GetMapCliffLevel())*bj_CLIFFHEIGHT then
if self.allocated and self.onCliff() then
self:terminate()
end
end
end
end
function mt:OnTerrain()
if self.onTerrain then
if GetLocZ(self.x, self.y) > self.z then
if self.allocated and self.onTerrain() then
self:terminate()
end
end
end
end
function mt:OnTileset()
if self.onTileset then
local type = GetTerrainType(self.x, self.y)
if type ~= self.tileset then
if self.allocated and self.onTileset(type) then
self:terminate()
end
end
self.tileset = type
end
end
function mt:OnPeriod()
if self.onPeriod then
if self.allocated and self.onPeriod() then
self:terminate()
end
end
end
function mt:OnOrient()
local a
-- Homing or not
if self.target and GetUnitTypeId(self.target) ~= 0 then
self.impact:move(GetUnitX(self.target), GetUnitY(self.target), GetUnitFlyHeight(self.target) + self.toZ)
local dx = self.impact.x - self.nextX
local dy = self.impact.y - self.nextY
a = Atan2(dy, dx)
self.travel = self.origin.distance - SquareRoot(dx*dx + dy*dy)
else
a = self.origin.angle
self.target = nil
end
-- turn rate
if self.turn ~= 0 and not (Cos(self.cA - a) >= Cos(self.turn)) then
if Sin(a - self.cA) >= 0 then
self.cA = self.cA + self.turn
else
self.cA = self.cA - self.turn
end
else
self.cA = a
end
local vel = self.veloc*dilation
yaw = self.cA
travelled = self.travel + vel
self.veloc = self.veloc + self.acceleration
self.travel = travelled
pitch = self.origin.alpha
self.prevX = self.x
self.prevY = self.y
self.prevZ = self.z
self.x = self.nextX
self.y = self.nextY
self.z = self.nextZ
self.nextX = self.x + vel*Cos(yaw)
self.nextY = self.y + vel*Sin(yaw)
-- arc calculation
local s = travelled
local d = self.origin.distance
local h = self.height
if h ~= 0 or self.origin.slope ~= 0 then
self.nextZ = 4*h*s*(d-s)/(d*d) + self.origin.slope*s + self.origin.z
pitch = pitch - Atan(((4*h)*(2*s - d))/(d*d))
end
-- curve calculation
local c = self.open
if c ~= 0 then
local dx = 4 * c * s * (d - s) / (d * d)
a = yaw + bj_PI / 2
self.x = self.x + dx * Cos(a)
self.y = self.y + dx * Sin(a)
yaw = yaw + Atan(-((4 * c) * (2 * s - d)) / (d * d))
end
end
function mt:OnFinish()
if travelled >= self.origin.distance - 0.0001 then
self.finished = true
if self.onFinish then
if self.allocated and self.onFinish() then
self:terminate()
else
if self.travel > 0 and not self.paused then
self:terminate()
end
end
else
self:terminate()
end
else
if not self.roll then
self.effect:orient(yaw, -pitch, 0)
else
self.effect:orient(yaw, -pitch, Atan2(self.open, self.height))
end
end
end
function mt:OnBoundaries()
if not self.effect:move(self.x, self.y, self.z) then
if self.onBoundaries then
if self.allocated and self.onBoundaries() then
self:terminate()
end
end
else
if self.dummy then
SetUnitX(self.dummy, self.x)
SetUnitY(self.dummy, self.y)
end
end
end
function mt:OnPause()
pid = pid + 1
self.pkey = pid
frozen[pid] = self
if self.onPause then
if self.allocated and self.onPause() then
self:terminate()
end
end
end
function mt:OnResume(flag)
local this
self.paused = flag
if not self.paused and self.pkey ~= -1 then
id = id + 1
missiles[id] = self
this = frozen[pid]
this.pkey = self.pkey
frozen[self.pkey] = frozen[pid]
pid = pid - 1
self.pkey = -1
if id + 1 > SWEET_SPOT and SWEET_SPOT > 0 then
dilation = (id + 1)/SWEET_SPOT
else
dilation = 1.
end
if id == 0 then
TimerStart(timer, PERIOD, true, function() Missiles:move() end)
end
if self.onResume then
if self.allocated and self.onResume() then
self:terminate()
else
if self.finished then
self:terminate()
end
end
else
if self.finished then
self:terminate()
end
end
end
end
function mt:OnRemove()
local this
if self.allocated and self.launched then
self.allocated = false
if self.pkey ~= -1 then
this = frozen[pid]
this.pkey = self.pkey
frozen[self.pkey] = frozen[pid]
pid = pid - 1
self.pkey = -1
end
if self.onRemove then
self.onRemove()
end
if self.dummy then
Pool:recycle(self.dummy)
end
this = Missiles.collection[Missiles.count]
this.index = self.index
Missiles.collection[self.index] = Missiles.collection[Missiles.count]
Missiles.count = Missiles.count - 1
self.index = -1
self.origin:destroy()
self.impact:destroy()
self.effect:destroy()
self:reset()
array[self] = nil
end
end
-- -------------------------- Model of the missile -------------------------- --
function mt:model(effect)
DestroyEffect(self.effect.effect)
self.effect.path = effect
self.Model = effect
self.effect.effect = AddSpecialEffect(effect, self.origin.x, self.origin.y)
BlzSetSpecialEffectZ(self.effect.effect, self.origin.z)
BlzSetSpecialEffectYaw(self.effect.effect, self.cA)
end
-- ----------------------------- Curved movement ---------------------------- --
function mt:curve(value)
self.open = Tan(value * bj_DEGTORAD) * self.origin.distance
self.Curve = value
end
-- ----------------------------- Arced Movement ----------------------------- --
function mt:arc(value)
self.height = Tan(value * bj_DEGTORAD) * self.origin.distance / 4
self.Arc = value
end
-- ------------------------------ Effect scale ------------------------------ --
function mt:scale(value)
self.effect.size = value
self.effect:scale(self.effect.effect, value)
self.Scale = value
end
-- ------------------------------ Missile Speed ----------------------------- --
function mt:speed(value)
self.veloc = value * PERIOD
self.Speed = value
local vel = self.veloc*dilation
local s = self.travel + vel
local d = self.origin.distance
self.nextX = self.x + vel*Cos(self.cA)
self.nextY = self.y + vel*Sin(self.cA)
if self.height ~= 0 or self.origin.slope ~= 0 then
self.nextZ = 4*self.height*s*(d-s)/(d*d) + self.origin.slope*s + self.origin.z
self.z = self.nextZ
end
end
-- ------------------------------- Flight Time ------------------------------ --
function mt:duration(value)
self.veloc = RMaxBJ(0.00000001, (self.origin.distance - self.travel) * PERIOD / RMaxBJ(0.00000001, value))
self.Duration = value
local vel = self.veloc*dilation
local s = self.travel + vel
local d = self.origin.distance
self.nextX = self.x + vel*Cos(self.cA)
self.nextY = self.y + vel*Sin(self.cA)
if self.height ~= 0 or self.origin.slope ~= 0 then
self.nextZ = 4*self.height*s*(d-s)/(d*d) + self.origin.slope*s + self.origin.z
self.z = self.nextZ
end
end
-- ------------------------------- Sight Range ------------------------------ --
function mt:vision(sightRange)
self.Vision = sightRange
if self.dummy then
SetUnitOwner(self.dummy, self.owner, false)
BlzSetUnitRealField(self.dummy, UNIT_RF_SIGHT_RADIUS, sightRange)
else
if not self.owner then
if self.source then
self.dummy = Pool:retrieve(self.x, self.y, self.z, 0)
SetUnitOwner(self.dummy, GetOwningPlayer(self.source), false)
BlzSetUnitRealField(self.dummy, UNIT_RF_SIGHT_RADIUS, sightRange)
end
else
self.dummy = Pool:retrieve(self.x, self.y, self.z, 0)
SetUnitOwner(self.dummy, self.owner, false)
BlzSetUnitRealField(self.dummy, UNIT_RF_SIGHT_RADIUS, sightRange)
end
end
end
-- ------------------------------- Time Scale ------------------------------- --
function mt:timeScale(real)
self.TimeScale = real
self.effect:timeScale(real)
end
-- ---------------------------------- Alpha --------------------------------- --
function mt:alpha(integer)
self.Alpha = integer
self.effect:alpha(integer)
end
-- ------------------------------ Player Color ------------------------------ --
function mt:playerColor(integer)
self.playercolor = integer
self.effect:playerColor(integer)
end
-- -------------------------------- Animation ------------------------------- --
function mt:animation(integer)
self.Animation = integer
self.effect:animation(integer)
end
-- --------------------------- Bounce and Deflect --------------------------- --
function mt:bounce()
self.origin:move(self.x, self.y, self.z - GetLocZ(self.x, self.y))
travelled = 0
self.travel = 0
self.finished = false
end
function mt:deflect(tx, ty, tz)
local locZ = GetLocZ(self.x, self.y)
if self.z < locZ and self.onTerrain then
self.nextX = self.prevX
self.nextY = self.prevY
self.nextZ = self.prevZ
end
self.toZ = tz
self.target = nil
self.impact:move(tx, ty, tz)
self.origin:move(self.x, self.y, self.z - locZ)
travelled = 0
self.travel = 0
self.finished = false
end
function mt:deflectTarget(unit)
self:deflect(GetUnitX(unit), GetUnitY(unit), self.toZ)
self.target = unit
end
-- ---------------------------- Flush hit targets --------------------------- --
function mt:flushAll()
array[self] = nil
array[self] = {}
end
function mt:flush(widget)
if widget then
array[self][widget] = nil
end
end
function mt:hitted(widget)
return array[self][widget]
end
-- ----------------------- Missile attachment methods ----------------------- --
function mt:attach(model, dx, dy, dz, scale)
return self.effect:attach(model, dx, dy, dz, scale)
end
function mt:detach(effect)
if effect then
self.effect:detach(effect)
end
end
-- ------------------------------ Missile Pause ----------------------------- --
function mt:pause(flag)
self:OnResume(flag)
end
-- ---------------------------------- Color --------------------------------- --
function mt:color(red, green, blue)
self.effect:setColor(red, green, blue)
end
-- ------------------------------ Reset members ----------------------------- --
function mt:reset()
self.launched = false
self.collideZ = false
self.finished = false
self.paused = false
self.roll = false
self.source = nil
self.target = nil
self.owner = nil
self.dummy = nil
self.open = 0.
self.height = 0.
self.veloc = 0.
self.acceleration = 0.
self.collision = 0.
self.damage = 0.
self.travel = 0.
self.turn = 0.
self.data = 0.
self.type = 0
self.tileset = 0
self.pkey = -1
self.index = -1
self.Model = ""
self.Duration = 0
self.Scale = 1
self.Speed = 0
self.Arc = 0
self.Curve = 0
self.Vision = 0
self.TimeScale = 0.
self.Alpha = 0
self.playercolor = 0
self.Animation = 0
self.onHit = nil
self.onMissile = nil
self.onDestructable = nil
self.onItem = nil
self.onCliff = nil
self.onTerrain = nil
self.onTileset = nil
self.onPeriod = nil
self.onFinish = nil
self.onBoundaries = nil
self.onPause = nil
self.onResume = nil
self.onRemove = nil
end
-- -------------------------------- Terminate ------------------------------- --
function mt:terminate()
self:OnRemove()
end
-- -------------------------- Destroys the missile -------------------------- --
function mt:remove(i)
if self.paused then
self:OnPause()
else
self:OnRemove()
end
missiles[i] = missiles[id]
id = id - 1
if id + 1 > SWEET_SPOT and SWEET_SPOT > 0 then
dilation = (id + 1) / SWEET_SPOT
else
dilation = 1
end
if id == -1 then
PauseTimer(timer)
end
if not self.allocated then
table.insert(keys, self.key)
self = nil
end
return i - 1
end
-- ---------------------------- Missiles movement --------------------------- --
function mt:move()
local i = 0
local j = 0
if SWEET_SPOT > 0 then
i = last
else
i = 0
end
while not ((j >= SWEET_SPOT and SWEET_SPOT > 0) or j > id) do
local this = missiles[i]
if this.allocated and not this.paused then
this:OnHit()
this:OnMissile()
this:OnDestructable()
this:OnItem()
this:OnCliff()
this:OnTerrain()
this:OnTileset()
this:OnPeriod()
this:OnOrient()
this:OnFinish()
this:OnBoundaries()
else
i = this:remove(i)
j = j - 1
end
i = i + 1
j = j + 1
if i > id and SWEET_SPOT > 0 then
i = 0
end
end
last = i
end
-- --------------------------- Launch the Missile --------------------------- --
function mt:launch()
if not self.launched and self.allocated then
self.launched = true
id = id + 1
missiles[id] = self
Missiles.count = Missiles.count + 1
self.index = Missiles.count
Missiles.collection[Missiles.count] = self
if id + 1 > SWEET_SPOT and SWEET_SPOT > 0 then
dilation = (id + 1) / SWEET_SPOT
else
dilation = 1.
end
if id == 0 then
TimerStart(timer, PERIOD, true, function() Missiles:move() end)
end
end
end
-- --------------------------- Main Creator method -------------------------- --
function mt:create(x, y, z, toX, toY, toZ)
local this = {}
setmetatable(this, mt)
array[this] = {}
if #keys > 0 then
this.key = keys[#keys]
keys[#keys] = nil
else
this.key = index
index = index + 1
end
this:reset()
this.origin = Coordinates:create(x, y, z)
this.impact = Coordinates:create(toX, toY, toZ)
this.effect = MissileEffect:create(x, y, this.origin.z)
Coordinates:link(this.origin, this.impact)
this.allocated = true
this.cA = this.origin.angle
this.x = x
this.y = y
this.z = this.impact.z
this.prevX = x
this.prevY = y
this.prevZ = this.impact.z
this.nextX = x
this.nextY = y
this.nextZ = this.impact.z
this.toZ = toZ
return this
end
end)
if Debug and Debug.endFile then Debug.endFile() end
--[[
-- ---------------------------------------- Tenacity v1.0 --------------------------------------- --
-- Intro
-- This library intension in to introduce to warcraft an easy way to
-- manipulate the duration of crowd control on units.
--
-- How it Works?
-- Working in conjuction with the Crowd Control Library this library allows you to control the
-- duration of disables provided in the Crowd Control library. It work similar to the Tenacity
-- status in League of Legends or the Status Resistence in Dota 2.
--
-- The are basically 3 types of tenacity: Normal (stacks multiplicatively),
-- Flat and Offset (stacks additively).The formula for calculation is:
-- newDuration = (duration - Offset) * [(1 - value1)*(1 - value2)*...] * (1 - Flat)
--
-- The system also allow negative values for Tenacity, resulting in increased
-- crowd control duration. Also note that tenacity will only work on CC applied through
-- the Crowd Control API
--
-- How to Import
-- 1. Copy the Indexer library into your map
-- 2. Copy this library into your map and you are done
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
do
-- ---------------------------------------------------------------------------------------------- --
-- LUA API --
-- ---------------------------------------------------------------------------------------------- --
function GetUnitTenacity(unit)
return Tenacity:get(unit, 0)
end
function GetUnitTenacityFlat(unit)
return Tenacity:get(unit, 1)
end
function GetUnitTenacityOffset(unit)
return Tenacity:get(unit, 2)
end
function SetUnitTenacity(unit, value)
Tenacity:set(unit, value, 0)
end
function SetUnitTenacityFlat(unit, value)
Tenacity:set(unit, value, 1)
end
function SetUnitTenacityOffset(unit, value)
Tenacity:set(unit, value, 2)
end
function UnitAddTenacity(unit, value)
Tenacity:add(unit, value, 0)
end
function UnitAddTenacityFlat(unit, value)
Tenacity:add(unit, value, 1)
end
function UnitAddTenacityOffset(unit, value)
Tenacity:add(unit, value, 2)
end
function UnitRemoveTenacity(unit, value)
Tenacity:remove(unit, value)
end
function GetTenacityDuration(unit, duration)
return Tenacity:calculate(unit, duration)
end
function RegisterTenacityUnit(unit)
return Tenacity:register(unit)
end
function DisplayTenacityStatus(unit)
Tenacity:print(unit)
end
-- ---------------------------------------------------------------------------------------------- --
-- System --
-- ---------------------------------------------------------------------------------------------- --
Tenacity = setmetatable({}, {})
local mt = getmetatable(Tenacity)
mt.__index = mt
local list = {}
function mt:get(unit, type)
if list[unit] then
if type == 0 then
if (#list[unit].normal or 0) > 0 then
return 1 - list[unit].tenacity
else
return 0
end
elseif type == 1 then
return list[unit].flat
else
return list[unit].offset
end
end
return 0
end
function mt:set(unit, value, type)
if not list[unit] then
self:register(unit)
end
if type == 0 then
list[unit].tenacity = value
elseif type == 1 then
list[unit].flat = value
else
list[unit].offset = value
end
end
function mt:add(unit, value, type)
if not list[unit] then
self:register(unit)
end
if type == 0 then
table.insert(list[unit].normal, value)
self:update(unit)
elseif type == 1 then
list[unit].flat = (list[unit].flat or 0) + value
else
list[unit].offset = (list[unit].offset or 0) + value
end
end
function mt:update(unit)
if list[unit] then
for i = 1, #list[unit].normal do
if i > 1 then
list[unit].tenacity = (list[unit].tenacity or 0) * (1 - list[unit].normal[i])
else
list[unit].tenacity = 1 - list[unit].normal[i]
end
end
end
end
function mt:remove(unit, value)
if value ~= 0 and list[unit] then
for i = 1, #list[unit].normal do
if list[unit].normal[i] == value then
table.remove(list[unit].normal, i)
break
end
end
self:update(unit)
end
end
function mt:calculate(unit, duration)
if duration ~= 0 and list[unit] then
if #list[unit].normal > 0 then
duration = (duration - list[unit].offset) * list[unit].tenacity * (1 - list[unit].flat)
else
duration = (duration - list[unit].offset) * (1 - list[unit].flat)
end
if duration <= 0 then
return 0
end
end
return duration
end
function mt:print(unit)
if list[unit] then
ClearTextMessages()
print("Tenacity Status for " .. GetUnitName(unit))
print("Tenacity List [" .. (list[unit].normal[1] or 0) .. " | " .. (list[unit].normal[2] or 0) .. " | " .. (list[unit].normal[3] or 0) .. " | " .. (list[unit].normal[4] or 0) .. " | " .. (list[unit].normal[5] or 0) .. " | " .. (list[unit].normal[6] or 0) .. " | ...] = " .. (self:get(unit, 0)))
print("Tenacity Flat [" .. (self:get(unit, 1)) .. "]")
print("Tenacity Offset [" .. (self:get(unit, 2)) .. "]")
end
end
function mt:register(unit)
if not list[unit] then
list[unit] = {}
list[unit].normal = {}
list[unit].flat = 0
list[unit].offset = 0
list[unit].tenacity = 0
end
return list[unit]
end
onInit(function()
RegisterUnitDeindexEvent(function()
list[GetIndexUnit()] = nil
end)
end)
end
if Debug and Debug.beginFile then Debug.beginFile("Tenacity") end
--[[
-- ---------------------------------------- Tenacity v1.0 --------------------------------------- --
-- Intro
-- This library intension in to introduce to warcraft an easy way to
-- manipulate the duration of crowd control on units.
--
-- How it Works?
-- Working in conjuction with the Crowd Control Library this library allows you to control the
-- duration of disables provided in the Crowd Control library. It work similar to the Tenacity
-- status in League of Legends or the Status Resistence in Dota 2.
--
-- The are basically 3 types of tenacity: Normal (stacks multiplicatively),
-- Flat and Offset (stacks additively).The formula for calculation is:
-- newDuration = (duration - Offset) * [(1 - value1)*(1 - value2)*...] * (1 - Flat)
--
-- The system also allow negative values for Tenacity, resulting in increased
-- crowd control duration. Also note that tenacity will only work on CC applied through
-- the Crowd Control API
--
-- How to Import
-- 1. Copy the Indexer library into your map
-- 2. Copy this library into your map and you are done
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
do
-- ---------------------------------------------------------------------------------------------- --
-- LUA API --
-- ---------------------------------------------------------------------------------------------- --
function GetUnitTenacity(unit)
return Tenacity:get(unit, 0)
end
function GetUnitTenacityFlat(unit)
return Tenacity:get(unit, 1)
end
function GetUnitTenacityOffset(unit)
return Tenacity:get(unit, 2)
end
function SetUnitTenacity(unit, value)
Tenacity:set(unit, value, 0)
end
function SetUnitTenacityFlat(unit, value)
Tenacity:set(unit, value, 1)
end
function SetUnitTenacityOffset(unit, value)
Tenacity:set(unit, value, 2)
end
function UnitAddTenacity(unit, value)
Tenacity:add(unit, value, 0)
end
function UnitAddTenacityFlat(unit, value)
Tenacity:add(unit, value, 1)
end
function UnitAddTenacityOffset(unit, value)
Tenacity:add(unit, value, 2)
end
function UnitRemoveTenacity(unit, value)
Tenacity:remove(unit, value)
end
function GetTenacityDuration(unit, duration)
return Tenacity:calculate(unit, duration)
end
function RegisterTenacityUnit(unit)
return Tenacity:register(unit)
end
function DisplayTenacityStatus(unit)
Tenacity:print(unit)
end
-- ---------------------------------------------------------------------------------------------- --
-- System --
-- ---------------------------------------------------------------------------------------------- --
Tenacity = setmetatable({}, {})
local mt = getmetatable(Tenacity)
mt.__index = mt
local list = {}
function mt:get(unit, type)
if list[unit] then
if type == 0 then
if (#list[unit].normal or 0) > 0 then
return 1 - list[unit].tenacity
else
return 0
end
elseif type == 1 then
return list[unit].flat
else
return list[unit].offset
end
end
return 0
end
function mt:set(unit, value, type)
if not list[unit] then
self:register(unit)
end
if type == 0 then
list[unit].tenacity = value
elseif type == 1 then
list[unit].flat = value
else
list[unit].offset = value
end
end
function mt:add(unit, value, type)
if not list[unit] then
self:register(unit)
end
if type == 0 then
table.insert(list[unit].normal, value)
self:update(unit)
elseif type == 1 then
list[unit].flat = (list[unit].flat or 0) + value
else
list[unit].offset = (list[unit].offset or 0) + value
end
end
function mt:update(unit)
if list[unit] then
for i = 1, #list[unit].normal do
if i > 1 then
list[unit].tenacity = (list[unit].tenacity or 0) * (1 - list[unit].normal[i])
else
list[unit].tenacity = 1 - list[unit].normal[i]
end
end
end
end
function mt:remove(unit, value)
if value ~= 0 and list[unit] then
for i = 1, #list[unit].normal do
if list[unit].normal[i] == value then
table.remove(list[unit].normal, i)
break
end
end
self:update(unit)
end
end
function mt:calculate(unit, duration)
if duration ~= 0 and list[unit] then
if #list[unit].normal > 0 then
duration = (duration - list[unit].offset) * list[unit].tenacity * (1 - list[unit].flat)
else
duration = (duration - list[unit].offset) * (1 - list[unit].flat)
end
if duration <= 0 then
return 0
end
end
return duration
end
function mt:print(unit)
if list[unit] then
ClearTextMessages()
print("Tenacity Status for " .. GetUnitName(unit))
print("Tenacity List [" .. (list[unit].normal[1] or 0) .. " | " .. (list[unit].normal[2] or 0) .. " | " .. (list[unit].normal[3] or 0) .. " | " .. (list[unit].normal[4] or 0) .. " | " .. (list[unit].normal[5] or 0) .. " | " .. (list[unit].normal[6] or 0) .. " | ...] = " .. (self:get(unit, 0)))
print("Tenacity Flat [" .. (self:get(unit, 1)) .. "]")
print("Tenacity Offset [" .. (self:get(unit, 2)) .. "]")
end
end
function mt:register(unit)
if not list[unit] then
list[unit] = {}
list[unit].normal = {}
list[unit].flat = 0
list[unit].offset = 0
list[unit].tenacity = 0
end
return list[unit]
end
OnInit.global(function()
RegisterUnitDeindexEvent(function()
list[GetIndexUnit()] = nil
end)
end)
end
if Debug and Debug.endFile then Debug.endFile() end
--[[ requires Tenacity
-- ------------------------------------- Tenacity Utils v1.0 ------------------------------------ --
-- Utility Library that include a few extra functions to deal with Tenacity
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
do
-- ---------------------------------------------------------------------------------------------- --
-- LUA API --
-- ---------------------------------------------------------------------------------------------- --
function UnitAddTenacityTimed(unit, value, duration)
TenacityUtils:addTimed(unit, value, duration, 0)
end
function UnitAddTenacityFlatTimed(unit, value, duration)
TenacityUtils:addTimed(unit, value, duration, 1)
end
function UnitAddTenacityOffsetTimed(unit, value, duration)
TenacityUtils:addTimed(unit, value, duration, 2)
end
-- ---------------------------------------------------------------------------------------------- --
-- System --
-- ---------------------------------------------------------------------------------------------- --
TenacityUtils = setmetatable({}, {})
local mt = getmetatable(TenacityUtils)
mt.__index = mt
local array = {}
local key = 0
local timer = CreateTimer()
local period = 0.03125000
function mt:remove(i)
if self.type == 0 then
Tenacity:remove(self.unit, self.value)
else
Tenacity:add(self.unit, -self.value, self.type)
end
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:addTimed(unit, value, duration, type)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.value = value
this.type = type
this.duration = duration
key = key + 1
array[key] = this
Tenacity:add(unit, value, type)
if key == 1 then
TimerStart(timer, period, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.duration <= 0 then
i = this:remove(i)
end
this.duration = this.duration - period
i = i + 1
end
end)
end
end
end
if Debug and Debug.beginFile then Debug.beginFile("TenacityUtils") end
--[[ requires Tenacity
-- ------------------------------------- Tenacity Utils v1.0 ------------------------------------ --
-- Utility Library that include a few extra functions to deal with Tenacity
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
OnInit.global(function()
-- ---------------------------------------------------------------------------------------------- --
-- LUA API --
-- ---------------------------------------------------------------------------------------------- --
function UnitAddTenacityTimed(unit, value, duration)
TenacityUtils:addTimed(unit, value, duration, 0)
end
function UnitAddTenacityFlatTimed(unit, value, duration)
TenacityUtils:addTimed(unit, value, duration, 1)
end
function UnitAddTenacityOffsetTimed(unit, value, duration)
TenacityUtils:addTimed(unit, value, duration, 2)
end
-- ---------------------------------------------------------------------------------------------- --
-- System --
-- ---------------------------------------------------------------------------------------------- --
TenacityUtils = setmetatable({}, {})
local mt = getmetatable(TenacityUtils)
mt.__index = mt
local array = {}
local key = 0
local timer = CreateTimer()
local period = 0.03125000
function mt:remove(i)
if self.type == 0 then
Tenacity:remove(self.unit, self.value)
else
Tenacity:add(self.unit, -self.value, self.type)
end
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:addTimed(unit, value, duration, type)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.value = value
this.type = type
this.duration = duration
key = key + 1
array[key] = this
Tenacity:add(unit, value, type)
if key == 1 then
TimerStart(timer, period, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.duration <= 0 then
i = this:remove(i)
end
this.duration = this.duration - period
i = i + 1
end
end)
end
end
end)
if Debug and Debug.endFile then Debug.endFile() end
--[[ requires optional DamageInterface, optional Evasion, optional CriticalStrike, optional SpellPower, optional LifeSteal, optional SpellVamp, optional CooldownReduction, optional Tenacity
-- ---------------------------------------- NewBonus v2.4 --------------------------------------- --
-- Since ObjectMerger is broken and we still have no means to edit
-- bonus values (green values) i decided to create a light weight
-- Bonus library that works in the same way that the original Bonus Mod
-- by Earth Fury did. NewBonus requires patch 1.31+.
-- Credits to Earth Fury for the original Bonus idea
-- How to Import?
-- Importing bonus mod is really simple. Just copy the abilities with the
-- prefix "NewBonus" from the Object Editor into your map and match their new raw
-- code to the bonus types in the global block below. Then create a trigger called
-- NewBonus, convert it to custom text and paste this code there. You done!
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
do
-- ---------------------------------------------------------------------------------------------- --
-- Configuration --
-- ---------------------------------------------------------------------------------------------- --
-- If true will use the extended version of the system.
-- Make sure you have the DamageInterface and Cooldown Reduction libraries
NewBonus_EXTENDED = true
-- This is the maximum recursion limit allowed by the system.
-- Its value must be greater than or equal to 0. When equal to 0
-- no recursion is allowed. Values too big can cause screen freezes.
local RECURSION_LIMIT = 8
-- The bonus types
BONUS_DAMAGE = 1
BONUS_ARMOR = 2
BONUS_AGILITY = 3
BONUS_STRENGTH = 4
BONUS_INTELLIGENCE = 5
BONUS_HEALTH = 6
BONUS_MANA = 7
BONUS_MOVEMENT_SPEED = 8
BONUS_SIGHT_RANGE = 9
BONUS_HEALTH_REGEN = 10
BONUS_MANA_REGEN = 11
BONUS_ATTACK_SPEED = 12
BONUS_MAGIC_RESISTANCE = 13
BONUS_EVASION_CHANCE = 14
BONUS_CRITICAL_DAMAGE = 15
BONUS_CRITICAL_CHANCE = 16
BONUS_LIFE_STEAL = 17
BONUS_MISS_CHANCE = 18
BONUS_SPELL_POWER_FLAT = 19
BONUS_SPELL_POWER_PERCENT = 20
BONUS_SPELL_VAMP = 21
BONUS_COOLDOWN_REDUCTION = 22
BONUS_COOLDOWN_REDUCTION_FLAT = 23
BONUS_COOLDOWN_OFFSET = 24
BONUS_TENACITY = 25
BONUS_TENACITY_FLAT = 26
BONUS_TENACITY_OFFSET = 27
-- The abilities codes for each bonus
-- When pasting the abilities over to your map
-- their raw code should match the bonus here
local DAMAGE_ABILITY = FourCC('Z001')
local ARMOR_ABILITY = FourCC('Z002')
local STATS_ABILITY = FourCC('Z003')
local HEALTH_ABILITY = FourCC('Z004')
local MANA_ABILITY = FourCC('Z005')
local HEALTHREGEN_ABILITY = FourCC('Z006')
local MANAREGEN_ABILITY = FourCC('Z007')
local ATTACKSPEED_ABILITY = FourCC('Z008')
local MOVEMENTSPEED_ABILITY = FourCC('Z009')
local SIGHT_RANGE_ABILITY = FourCC('Z00A')
local MAGIC_RESISTANCE_ABILITY = FourCC('Z00B')
local CRITICAL_STRIKE_ABILITY = FourCC('Z00C')
local EVASION_ABILITY = FourCC('Z00D')
local LIFE_STEAL_ABILITY = FourCC('Z00E')
-- The abilities fields that are modified. For the sake of readability
local DAMAGE_FIELD = ABILITY_ILF_ATTACK_BONUS
local ARMOR_FIELD = ABILITY_ILF_DEFENSE_BONUS_IDEF
local AGILITY_FIELD = ABILITY_ILF_AGILITY_BONUS
local STRENGTH_FIELD = ABILITY_ILF_STRENGTH_BONUS_ISTR
local INTELLIGENCE_FIELD = ABILITY_ILF_INTELLIGENCE_BONUS
local HEALTH_FIELD = ABILITY_ILF_MAX_LIFE_GAINED
local MANA_FIELD = ABILITY_ILF_MAX_MANA_GAINED
local MOVEMENTSPEED_FIELD = ABILITY_ILF_MOVEMENT_SPEED_BONUS
local SIGHT_RANGE_FIELD = ABILITY_ILF_SIGHT_RANGE_BONUS
local HEALTHREGEN_FIELD = ABILITY_RLF_AMOUNT_OF_HIT_POINTS_REGENERATED
local MANAREGEN_FIELD = ABILITY_RLF_AMOUNT_REGENERATED
local ATTACKSPEED_FIELD = ABILITY_RLF_ATTACK_SPEED_INCREASE_ISX1
local MAGIC_RESISTANCE_FIELD = ABILITY_RLF_DAMAGE_REDUCTION_ISR2
local CRITICAL_CHANCE_FIELD = ABILITY_RLF_CHANCE_TO_CRITICAL_STRIKE
local CRITICAL_DAMAGE_FIELD = ABILITY_RLF_DAMAGE_MULTIPLIER_OCR2
local EVASION_FIELD = ABILITY_RLF_CHANCE_TO_EVADE_EEV1
local LIFE_STEAL_FIELD = ABILITY_RLF_LIFE_STOLEN_PER_ATTACK
-- ---------------------------------------------------------------------------------------------- --
-- LUA API --
-- ---------------------------------------------------------------------------------------------- --
function GetUnitBonus(unit, type)
return NewBonus:get(unit, type)
end
function SetUnitBonus(unit, type, value)
return NewBonus:set(unit, type, value, false)
end
function RemoveUnitBonus(unit, type)
if type == BONUS_CRITICAL_DAMAGE then
NewBonus:set(unit, type, 1, false)
else
NewBonus:set(unit, type, 0, false)
end
if type == BONUS_LIFE_STEAL then
UnitRemoveAbility(unit, LIFE_STEAL_ABILITY)
end
end
function AddUnitBonus(unit, type, value)
return NewBonus:add(unit, type, value)
end
function RegisterBonusEvent(code)
NewBonus:register(code, 0)
end
function RegisterBonusTypeEvent(type, code)
NewBonus:register(code, type)
end
function GetBonusUnit()
return NewBonus.unit[NewBonus.key]
end
function GetBonusType()
return NewBonus.type[NewBonus.key]
end
function SetBonusType(type)
if type >= BONUS_DAMAGE and type <= NewBonus.last then
NewBonus.type[NewBonus.key] = type
end
end
function GetBonusAmount()
return NewBonus.amount[NewBonus.key]
end
function SetBonusAmount(real)
NewBonus.amount[NewBonus.key] = real
end
-- ---------------------------------------------------------------------------------------------- --
-- System --
-- ---------------------------------------------------------------------------------------------- --
NewBonus = setmetatable({}, {})
local mt = getmetatable(NewBonus)
mt.__index = mt
NewBonus.linkType = 0
NewBonus.last = 0
NewBonus.key = 0
NewBonus.unit = {}
NewBonus.type = {}
NewBonus.amount = {}
local trigger = {}
local event = {}
local count = 0
function mt:checkOverflow(current, value)
if value > 0 and current > 2147483647 - value then
return 2147483647 - current
elseif value < 0 and current < -2147483648 - value then
return -2147483648 - current
else
return value
end
end
function mt:onEvent(key)
local i = 0
local next = -1
local prev = -2
count = count + 1
if NewBonus.amount[key] ~= 0 and (count - NewBonus.last < RECURSION_LIMIT) then
while NewBonus.type[key] ~= next and (i - NewBonus.last < RECURSION_LIMIT) do
next = NewBonus.type[key]
if event[next] then
for j = 1, #event[next] do
event[next][j]()
end
end
if NewBonus.type[key] ~= next then
i = i + 1
else
if next ~= prev then
for j = 1, #trigger do
trigger[j]()
end
if NewBonus.type[key] ~= next then
i = i + 1
prev = next
end
end
end
end
end
count = count - 1
NewBonus.key = key
end
function mt:setAbility(unit, ability, field, value, integer, adding)
if GetUnitAbilityLevel(unit, ability) == 0 then
UnitAddAbility(unit, ability)
UnitMakeAbilityPermanent(unit, true, ability)
end
if integer then
if adding then
if BlzSetAbilityIntegerLevelField(BlzGetUnitAbility(unit, ability), field, 0, BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, ability), field, 0) + value) then
IncUnitAbilityLevel(unit, ability)
DecUnitAbilityLevel(unit, ability)
end
else
if BlzSetAbilityIntegerLevelField(BlzGetUnitAbility(unit, ability), field, 0, value) then
IncUnitAbilityLevel(unit, ability)
DecUnitAbilityLevel(unit, ability)
end
end
NewBonus.linkType = NewBonus.type[NewBonus.key]
if NewBonus.key > 0 then
NewBonus.key = NewBonus.key - 1
end
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, ability), field, 0)
else
if BlzSetAbilityRealLevelField(BlzGetUnitAbility(unit, ability), field, 0, value) then
IncUnitAbilityLevel(unit, ability)
DecUnitAbilityLevel(unit, ability)
end
NewBonus.linkType = NewBonus.type[NewBonus.key]
if NewBonus.key > 0 then
NewBonus.key = NewBonus.key - 1
end
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ability), field, 0)
end
end
function mt:get(unit, type)
if type == BONUS_DAMAGE then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, DAMAGE_ABILITY), DAMAGE_FIELD, 0)
elseif type == BONUS_ARMOR then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, ARMOR_ABILITY), ARMOR_FIELD, 0)
elseif type == BONUS_HEALTH then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, HEALTH_ABILITY), HEALTH_FIELD, 0)
elseif type == BONUS_MANA then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, MANA_ABILITY), MANA_FIELD, 0)
elseif type == BONUS_AGILITY then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, STATS_ABILITY), AGILITY_FIELD, 0)
elseif type == BONUS_STRENGTH then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, STATS_ABILITY), STRENGTH_FIELD, 0)
elseif type == BONUS_INTELLIGENCE then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, STATS_ABILITY), INTELLIGENCE_FIELD, 0)
elseif type == BONUS_MOVEMENT_SPEED then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, MOVEMENTSPEED_ABILITY), MOVEMENTSPEED_FIELD, 0)
elseif type == BONUS_SIGHT_RANGE then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, SIGHT_RANGE_ABILITY), SIGHT_RANGE_FIELD, 0)
elseif type == BONUS_HEALTH_REGEN then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, HEALTHREGEN_ABILITY), HEALTHREGEN_FIELD, 0)
elseif type == BONUS_MANA_REGEN then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, MANAREGEN_ABILITY), MANAREGEN_FIELD, 0)
elseif type == BONUS_ATTACK_SPEED then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ATTACKSPEED_ABILITY), ATTACKSPEED_FIELD, 0)
elseif type == BONUS_MAGIC_RESISTANCE then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, MAGIC_RESISTANCE_ABILITY), MAGIC_RESISTANCE_FIELD, 0)
elseif type >= BONUS_EVASION_CHANCE and type <= NewBonus.last then
if NewBonus_EXTENDED and Damage and Evasion and Critical and SpellPower and LifeSteal and SpellVamp and Tenacity then
if type == BONUS_EVASION_CHANCE then
return GetUnitEvasionChance(unit)
elseif type == BONUS_MISS_CHANCE then
return GetUnitMissChance(unit)
elseif type == BONUS_CRITICAL_CHANCE then
return GetUnitCriticalChance(unit)
elseif type == BONUS_CRITICAL_DAMAGE then
return GetUnitCriticalMultiplier(unit)
elseif type == BONUS_SPELL_POWER_FLAT then
return GetUnitSpellPowerFlat(unit)
elseif type == BONUS_SPELL_POWER_PERCENT then
return GetUnitSpellPowerPercent(unit)
elseif type == BONUS_LIFE_STEAL then
return GetUnitLifeSteal(unit)
elseif type == BONUS_SPELL_VAMP then
return GetUnitSpellVamp(unit)
elseif type == BONUS_COOLDOWN_REDUCTION then
return GetUnitCooldownReduction(unit)
elseif type == BONUS_COOLDOWN_REDUCTION_FLAT then
return GetUnitCooldownReductionFlat(unit)
elseif type == BONUS_COOLDOWN_OFFSET then
return GetUnitCooldownOffset(unit)
elseif type == BONUS_TENACITY then
return GetUnitTenacity(unit)
elseif type == BONUS_TENACITY_FLAT then
return GetUnitTenacityFlat(unit)
elseif type == BONUS_TENACITY_OFFSET then
return GetUnitTenacityOffset(unit)
end
else
if type == BONUS_CRITICAL_CHANCE then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, CRITICAL_STRIKE_ABILITY), CRITICAL_CHANCE_FIELD, 0)
elseif type == BONUS_CRITICAL_DAMAGE then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, CRITICAL_STRIKE_ABILITY), CRITICAL_DAMAGE_FIELD, 0)
elseif type == BONUS_EVASION_CHANCE then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, EVASION_ABILITY), EVASION_FIELD, 0)
elseif type == BONUS_LIFE_STEAL then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, LIFE_STEAL_ABILITY), LIFE_STEAL_FIELD, 0)
end
end
else
print("Invalid Bonus Type")
end
return -1
end
function mt:set(unit, type, value, adding)
if not adding then
NewBonus.key = NewBonus.key + 1
NewBonus.unit[NewBonus.key] = unit
NewBonus.type[NewBonus.key] = type
NewBonus.amount[NewBonus.key] = value
self:onEvent(NewBonus.key)
if NewBonus.amount[NewBonus.key] ~= value then
value = NewBonus.amount[NewBonus.key]
end
if NewBonus.type[NewBonus.key] ~= type then
return self:set(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key], NewBonus.amount[NewBonus.key], not adding)
end
else
NewBonus.unit[NewBonus.key] = unit
NewBonus.type[NewBonus.key] = type
NewBonus.amount[NewBonus.key] = value
end
if type == BONUS_DAMAGE then
return self:setAbility(unit, DAMAGE_ABILITY, DAMAGE_FIELD, value, true, adding)
elseif type == BONUS_ARMOR then
return self:setAbility(unit, ARMOR_ABILITY, ARMOR_FIELD, value, true, adding)
elseif type == BONUS_HEALTH then
local real = GetUnitLifePercent(unit)
if value == 0 and not adding then
BlzSetUnitMaxHP(unit, (BlzGetUnitMaxHP(unit) - self:get(unit, type)))
else
BlzSetUnitMaxHP(unit, (BlzGetUnitMaxHP(unit) + value))
end
self:setAbility(unit, HEALTH_ABILITY, HEALTH_FIELD, value, true, adding)
SetUnitLifePercentBJ(unit, real)
return value
elseif type == BONUS_MANA then
local real = GetUnitManaPercent(unit)
if value == 0 and not adding then
BlzSetUnitMaxMana(unit, (BlzGetUnitMaxMana(unit) - self:get(unit, type)))
else
BlzSetUnitMaxMana(unit, (BlzGetUnitMaxMana(unit) + value))
end
self:setAbility(unit, MANA_ABILITY, MANA_FIELD, value, true, adding)
SetUnitManaPercentBJ(unit, real)
return value
elseif type == BONUS_AGILITY then
return self:setAbility(unit, STATS_ABILITY, AGILITY_FIELD, value, true, adding)
elseif type == BONUS_STRENGTH then
return self:setAbility(unit, STATS_ABILITY, STRENGTH_FIELD, value, true, adding)
elseif type == BONUS_INTELLIGENCE then
return self:setAbility(unit, STATS_ABILITY, INTELLIGENCE_FIELD, value, true, adding)
elseif type == BONUS_MOVEMENT_SPEED then
return self:setAbility(unit, MOVEMENTSPEED_ABILITY, MOVEMENTSPEED_FIELD, value, true, adding)
elseif type == BONUS_SIGHT_RANGE then
if value == 0 and not adding then
BlzSetUnitRealField(unit, UNIT_RF_SIGHT_RADIUS, (BlzGetUnitRealField(unit, UNIT_RF_SIGHT_RADIUS) - self:get(unit, type)))
else
BlzSetUnitRealField(unit, UNIT_RF_SIGHT_RADIUS, (BlzGetUnitRealField(unit, UNIT_RF_SIGHT_RADIUS) + value))
end
self:setAbility(unit, SIGHT_RANGE_ABILITY, SIGHT_RANGE_FIELD, value, true, adding)
return value
elseif type == BONUS_HEALTH_REGEN then
return self:setAbility(unit, HEALTHREGEN_ABILITY, HEALTHREGEN_FIELD, value, false, adding)
elseif type == BONUS_MANA_REGEN then
return self:setAbility(unit, MANAREGEN_ABILITY, MANAREGEN_FIELD, value, false, adding)
elseif type == BONUS_ATTACK_SPEED then
return self:setAbility(unit, ATTACKSPEED_ABILITY, ATTACKSPEED_FIELD, value, false, adding)
elseif type == BONUS_MAGIC_RESISTANCE then
return self:setAbility(unit, MAGIC_RESISTANCE_ABILITY, MAGIC_RESISTANCE_FIELD, value, false, adding)
elseif type >= BONUS_EVASION_CHANCE and type <= NewBonus.last then
if NewBonus_EXTENDED and Damage and Evasion and Critical and SpellPower and LifeSteal and SpellVamp and Tenacity then
if type == BONUS_EVASION_CHANCE then
SetUnitEvasionChance(unit, value)
elseif type == BONUS_MISS_CHANCE then
SetUnitMissChance(unit, value)
elseif type == BONUS_CRITICAL_CHANCE then
SetUnitCriticalChance(unit, value)
elseif type == BONUS_CRITICAL_DAMAGE then
SetUnitCriticalMultiplier(unit, value)
elseif type == BONUS_SPELL_POWER_FLAT then
SetUnitSpellPowerFlat(unit, value)
elseif type == BONUS_SPELL_POWER_PERCENT then
SetUnitSpellPowerPercent(unit, value)
elseif type == BONUS_LIFE_STEAL then
SetUnitLifeSteal(unit, value)
elseif type == BONUS_SPELL_VAMP then
SetUnitSpellVamp(unit, value)
elseif type == BONUS_COOLDOWN_REDUCTION then
if adding then
UnitAddCooldownReduction(unit, value)
else
SetUnitCooldownReduction(unit, value)
end
elseif type == BONUS_COOLDOWN_REDUCTION_FLAT then
SetUnitCooldownReductionFlat(unit, value)
elseif type == BONUS_COOLDOWN_OFFSET then
SetUnitCooldownOffset(unit, value)
elseif type == BONUS_TENACITY then
if adding then
UnitAddTenacity(unit, value)
else
SetUnitTenacity(unit, value)
end
elseif type == BONUS_TENACITY_FLAT then
SetUnitTenacityFlat(unit, value)
elseif type == BONUS_TENACITY_OFFSET then
SetUnitTenacityOffset(unit, value)
end
NewBonus.linkType = type
if NewBonus.key > 0 then
NewBonus.key = NewBonus.key - 1
end
return value
else
if type == BONUS_CRITICAL_CHANCE then
return self:setAbility(unit, CRITICAL_STRIKE_ABILITY, CRITICAL_CHANCE_FIELD, value, false, adding)
elseif type == BONUS_CRITICAL_DAMAGE then
return self:setAbility(unit, CRITICAL_STRIKE_ABILITY, CRITICAL_DAMAGE_FIELD, value, false, adding)
elseif type == BONUS_EVASION_CHANCE then
return self:setAbility(unit, EVASION_ABILITY, EVASION_FIELD, value, false, adding)
elseif type == BONUS_LIFE_STEAL then
return self:setAbility(unit, LIFE_STEAL_ABILITY, LIFE_STEAL_FIELD, value, false, adding)
end
end
else
print("Invalid Bonus Type")
end
return -1
end
function mt:add(unit, type, value)
if value ~= 0 then
NewBonus.key = NewBonus.key + 1
NewBonus.unit[NewBonus.key] = unit
NewBonus.type[NewBonus.key] = type
NewBonus.amount[NewBonus.key] = value
if type <= BONUS_SIGHT_RANGE then
NewBonus.amount[NewBonus.key] = self:checkOverflow(self:get(unit, type), R2I(value))
end
self:onEvent(NewBonus.key)
value = NewBonus.amount[NewBonus.key]
if NewBonus.type[NewBonus.key] <= BONUS_SIGHT_RANGE then
self:set(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key], self:checkOverflow(self:get(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key]), R2I(NewBonus.amount[NewBonus.key])), true)
else
if NewBonus_EXTENDED and Damage and Evasion and Critical and SpellPower and LifeSteal and SpellVamp and Tenacity then
if NewBonus.type[NewBonus.key] == BONUS_COOLDOWN_REDUCTION or NewBonus.type[NewBonus.key] == BONUS_TENACITY then
self:set(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key], NewBonus.amount[NewBonus.key], true)
else
self:set(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key], self:get(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key]) + NewBonus.amount[NewBonus.key], true)
end
else
self:set(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key], self:get(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key]) + NewBonus.amount[NewBonus.key], true)
end
end
return value
end
return -1
end
function mt:register(code, bonus)
if type(code) == "function" then
if bonus >= BONUS_DAMAGE and bonus <= NewBonus.last then
if not event[bonus] then event[bonus] = {} end
table.insert(event[bonus], code)
else
table.insert(trigger, code)
end
end
end
onInit(function()
if NewBonus_EXTENDED and Damage and Evasion and Critical and SpellPower and LifeSteal and SpellVamp and Tenacity then
NewBonus.last = BONUS_TENACITY_OFFSET
else
NewBonus.last = BONUS_LIFE_STEAL
end
end)
end
if Debug and Debug.beginFile then Debug.beginFile("NewBonus") end
--[[ requires optional DamageInterface, optional Evasion, optional CriticalStrike, optional SpellPower, optional LifeSteal, optional SpellVamp, optional CooldownReduction, optional Tenacity
-- ---------------------------------------- NewBonus v2.4 --------------------------------------- --
-- Since ObjectMerger is broken and we still have no means to edit
-- bonus values (green values) i decided to create a light weight
-- Bonus library that works in the same way that the original Bonus Mod
-- by Earth Fury did. NewBonus requires patch 1.31+.
-- Credits to Earth Fury for the original Bonus idea
-- How to Import?
-- Importing bonus mod is really simple. Just copy the abilities with the
-- prefix "NewBonus" from the Object Editor into your map and match their new raw
-- code to the bonus types in the global block below. Then create a trigger called
-- NewBonus, convert it to custom text and paste this code there. You done!
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
do
-- ---------------------------------------------------------------------------------------------- --
-- Configuration --
-- ---------------------------------------------------------------------------------------------- --
-- If true will use the extended version of the system.
-- Make sure you have the DamageInterface and Cooldown Reduction libraries
NewBonus_EXTENDED = true
-- This is the maximum recursion limit allowed by the system.
-- Its value must be greater than or equal to 0. When equal to 0
-- no recursion is allowed. Values too big can cause screen freezes.
local RECURSION_LIMIT = 8
-- The bonus types
BONUS_DAMAGE = 1
BONUS_ARMOR = 2
BONUS_AGILITY = 3
BONUS_STRENGTH = 4
BONUS_INTELLIGENCE = 5
BONUS_HEALTH = 6
BONUS_MANA = 7
BONUS_MOVEMENT_SPEED = 8
BONUS_SIGHT_RANGE = 9
BONUS_HEALTH_REGEN = 10
BONUS_MANA_REGEN = 11
BONUS_ATTACK_SPEED = 12
BONUS_MAGIC_RESISTANCE = 13
BONUS_EVASION_CHANCE = 14
BONUS_CRITICAL_DAMAGE = 15
BONUS_CRITICAL_CHANCE = 16
BONUS_LIFE_STEAL = 17
BONUS_MISS_CHANCE = 18
BONUS_SPELL_POWER_FLAT = 19
BONUS_SPELL_POWER_PERCENT = 20
BONUS_SPELL_VAMP = 21
BONUS_COOLDOWN_REDUCTION = 22
BONUS_COOLDOWN_REDUCTION_FLAT = 23
BONUS_COOLDOWN_OFFSET = 24
BONUS_TENACITY = 25
BONUS_TENACITY_FLAT = 26
BONUS_TENACITY_OFFSET = 27
-- The abilities codes for each bonus
-- When pasting the abilities over to your map
-- their raw code should match the bonus here
local DAMAGE_ABILITY = FourCC('Z001')
local ARMOR_ABILITY = FourCC('Z002')
local STATS_ABILITY = FourCC('Z003')
local HEALTH_ABILITY = FourCC('Z004')
local MANA_ABILITY = FourCC('Z005')
local HEALTHREGEN_ABILITY = FourCC('Z006')
local MANAREGEN_ABILITY = FourCC('Z007')
local ATTACKSPEED_ABILITY = FourCC('Z008')
local MOVEMENTSPEED_ABILITY = FourCC('Z009')
local SIGHT_RANGE_ABILITY = FourCC('Z00A')
local MAGIC_RESISTANCE_ABILITY = FourCC('Z00B')
local CRITICAL_STRIKE_ABILITY = FourCC('Z00C')
local EVASION_ABILITY = FourCC('Z00D')
local LIFE_STEAL_ABILITY = FourCC('Z00E')
-- The abilities fields that are modified. For the sake of readability
local DAMAGE_FIELD = ABILITY_ILF_ATTACK_BONUS
local ARMOR_FIELD = ABILITY_ILF_DEFENSE_BONUS_IDEF
local AGILITY_FIELD = ABILITY_ILF_AGILITY_BONUS
local STRENGTH_FIELD = ABILITY_ILF_STRENGTH_BONUS_ISTR
local INTELLIGENCE_FIELD = ABILITY_ILF_INTELLIGENCE_BONUS
local HEALTH_FIELD = ABILITY_ILF_MAX_LIFE_GAINED
local MANA_FIELD = ABILITY_ILF_MAX_MANA_GAINED
local MOVEMENTSPEED_FIELD = ABILITY_ILF_MOVEMENT_SPEED_BONUS
local SIGHT_RANGE_FIELD = ABILITY_ILF_SIGHT_RANGE_BONUS
local HEALTHREGEN_FIELD = ABILITY_RLF_AMOUNT_OF_HIT_POINTS_REGENERATED
local MANAREGEN_FIELD = ABILITY_RLF_AMOUNT_REGENERATED
local ATTACKSPEED_FIELD = ABILITY_RLF_ATTACK_SPEED_INCREASE_ISX1
local MAGIC_RESISTANCE_FIELD = ABILITY_RLF_DAMAGE_REDUCTION_ISR2
local CRITICAL_CHANCE_FIELD = ABILITY_RLF_CHANCE_TO_CRITICAL_STRIKE
local CRITICAL_DAMAGE_FIELD = ABILITY_RLF_DAMAGE_MULTIPLIER_OCR2
local EVASION_FIELD = ABILITY_RLF_CHANCE_TO_EVADE_EEV1
local LIFE_STEAL_FIELD = ABILITY_RLF_LIFE_STOLEN_PER_ATTACK
-- ---------------------------------------------------------------------------------------------- --
-- LUA API --
-- ---------------------------------------------------------------------------------------------- --
function GetUnitBonus(unit, type)
return NewBonus:get(unit, type)
end
function SetUnitBonus(unit, type, value)
return NewBonus:set(unit, type, value, false)
end
function RemoveUnitBonus(unit, type)
if type == BONUS_CRITICAL_DAMAGE then
NewBonus:set(unit, type, 1, false)
else
NewBonus:set(unit, type, 0, false)
end
if type == BONUS_LIFE_STEAL then
UnitRemoveAbility(unit, LIFE_STEAL_ABILITY)
end
end
function AddUnitBonus(unit, type, value)
return NewBonus:add(unit, type, value)
end
function RegisterBonusEvent(code)
NewBonus:register(code, 0)
end
function RegisterBonusTypeEvent(type, code)
NewBonus:register(code, type)
end
function GetBonusUnit()
return NewBonus.unit[NewBonus.key]
end
function GetBonusType()
return NewBonus.type[NewBonus.key]
end
function SetBonusType(type)
if type >= BONUS_DAMAGE and type <= NewBonus.last then
NewBonus.type[NewBonus.key] = type
end
end
function GetBonusAmount()
return NewBonus.amount[NewBonus.key]
end
function SetBonusAmount(real)
NewBonus.amount[NewBonus.key] = real
end
-- ---------------------------------------------------------------------------------------------- --
-- System --
-- ---------------------------------------------------------------------------------------------- --
NewBonus = setmetatable({}, {})
local mt = getmetatable(NewBonus)
mt.__index = mt
NewBonus.linkType = 0
NewBonus.last = 0
NewBonus.key = 0
NewBonus.unit = {}
NewBonus.type = {}
NewBonus.amount = {}
local trigger = {}
local event = {}
local count = 0
function mt:checkOverflow(current, value)
if value > 0 and current > 2147483647 - value then
return 2147483647 - current
elseif value < 0 and current < -2147483648 - value then
return -2147483648 - current
else
return value
end
end
function mt:onEvent(key)
local i = 0
local next = -1
local prev = -2
count = count + 1
if NewBonus.amount[key] ~= 0 and (count - NewBonus.last < RECURSION_LIMIT) then
while NewBonus.type[key] ~= next and (i - NewBonus.last < RECURSION_LIMIT) do
next = NewBonus.type[key]
if event[next] then
for j = 1, #event[next] do
event[next][j]()
end
end
if NewBonus.type[key] ~= next then
i = i + 1
else
if next ~= prev then
for j = 1, #trigger do
trigger[j]()
end
if NewBonus.type[key] ~= next then
i = i + 1
prev = next
end
end
end
end
end
count = count - 1
NewBonus.key = key
end
function mt:setAbility(unit, ability, field, value, integer, adding)
if GetUnitAbilityLevel(unit, ability) == 0 then
UnitAddAbility(unit, ability)
UnitMakeAbilityPermanent(unit, true, ability)
end
if integer then
if adding then
if BlzSetAbilityIntegerLevelField(BlzGetUnitAbility(unit, ability), field, 0, BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, ability), field, 0) + value) then
IncUnitAbilityLevel(unit, ability)
DecUnitAbilityLevel(unit, ability)
end
else
if BlzSetAbilityIntegerLevelField(BlzGetUnitAbility(unit, ability), field, 0, value) then
IncUnitAbilityLevel(unit, ability)
DecUnitAbilityLevel(unit, ability)
end
end
NewBonus.linkType = NewBonus.type[NewBonus.key]
if NewBonus.key > 0 then
NewBonus.key = NewBonus.key - 1
end
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, ability), field, 0)
else
if BlzSetAbilityRealLevelField(BlzGetUnitAbility(unit, ability), field, 0, value) then
IncUnitAbilityLevel(unit, ability)
DecUnitAbilityLevel(unit, ability)
end
NewBonus.linkType = NewBonus.type[NewBonus.key]
if NewBonus.key > 0 then
NewBonus.key = NewBonus.key - 1
end
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ability), field, 0)
end
end
function mt:get(unit, type)
if type == BONUS_DAMAGE then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, DAMAGE_ABILITY), DAMAGE_FIELD, 0)
elseif type == BONUS_ARMOR then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, ARMOR_ABILITY), ARMOR_FIELD, 0)
elseif type == BONUS_HEALTH then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, HEALTH_ABILITY), HEALTH_FIELD, 0)
elseif type == BONUS_MANA then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, MANA_ABILITY), MANA_FIELD, 0)
elseif type == BONUS_AGILITY then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, STATS_ABILITY), AGILITY_FIELD, 0)
elseif type == BONUS_STRENGTH then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, STATS_ABILITY), STRENGTH_FIELD, 0)
elseif type == BONUS_INTELLIGENCE then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, STATS_ABILITY), INTELLIGENCE_FIELD, 0)
elseif type == BONUS_MOVEMENT_SPEED then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, MOVEMENTSPEED_ABILITY), MOVEMENTSPEED_FIELD, 0)
elseif type == BONUS_SIGHT_RANGE then
return BlzGetAbilityIntegerLevelField(BlzGetUnitAbility(unit, SIGHT_RANGE_ABILITY), SIGHT_RANGE_FIELD, 0)
elseif type == BONUS_HEALTH_REGEN then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, HEALTHREGEN_ABILITY), HEALTHREGEN_FIELD, 0)
elseif type == BONUS_MANA_REGEN then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, MANAREGEN_ABILITY), MANAREGEN_FIELD, 0)
elseif type == BONUS_ATTACK_SPEED then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ATTACKSPEED_ABILITY), ATTACKSPEED_FIELD, 0)
elseif type == BONUS_MAGIC_RESISTANCE then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, MAGIC_RESISTANCE_ABILITY), MAGIC_RESISTANCE_FIELD, 0)
elseif type >= BONUS_EVASION_CHANCE and type <= NewBonus.last then
if NewBonus_EXTENDED and Damage and Evasion and Critical and SpellPower and LifeSteal and SpellVamp and Tenacity then
if type == BONUS_EVASION_CHANCE then
return GetUnitEvasionChance(unit)
elseif type == BONUS_MISS_CHANCE then
return GetUnitMissChance(unit)
elseif type == BONUS_CRITICAL_CHANCE then
return GetUnitCriticalChance(unit)
elseif type == BONUS_CRITICAL_DAMAGE then
return GetUnitCriticalMultiplier(unit)
elseif type == BONUS_SPELL_POWER_FLAT then
return GetUnitSpellPowerFlat(unit)
elseif type == BONUS_SPELL_POWER_PERCENT then
return GetUnitSpellPowerPercent(unit)
elseif type == BONUS_LIFE_STEAL then
return GetUnitLifeSteal(unit)
elseif type == BONUS_SPELL_VAMP then
return GetUnitSpellVamp(unit)
elseif type == BONUS_COOLDOWN_REDUCTION then
return GetUnitCooldownReduction(unit)
elseif type == BONUS_COOLDOWN_REDUCTION_FLAT then
return GetUnitCooldownReductionFlat(unit)
elseif type == BONUS_COOLDOWN_OFFSET then
return GetUnitCooldownOffset(unit)
elseif type == BONUS_TENACITY then
return GetUnitTenacity(unit)
elseif type == BONUS_TENACITY_FLAT then
return GetUnitTenacityFlat(unit)
elseif type == BONUS_TENACITY_OFFSET then
return GetUnitTenacityOffset(unit)
end
else
if type == BONUS_CRITICAL_CHANCE then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, CRITICAL_STRIKE_ABILITY), CRITICAL_CHANCE_FIELD, 0)
elseif type == BONUS_CRITICAL_DAMAGE then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, CRITICAL_STRIKE_ABILITY), CRITICAL_DAMAGE_FIELD, 0)
elseif type == BONUS_EVASION_CHANCE then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, EVASION_ABILITY), EVASION_FIELD, 0)
elseif type == BONUS_LIFE_STEAL then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, LIFE_STEAL_ABILITY), LIFE_STEAL_FIELD, 0)
end
end
else
print("Invalid Bonus Type")
end
return -1
end
function mt:set(unit, type, value, adding)
if not adding then
NewBonus.key = NewBonus.key + 1
NewBonus.unit[NewBonus.key] = unit
NewBonus.type[NewBonus.key] = type
NewBonus.amount[NewBonus.key] = value
self:onEvent(NewBonus.key)
if NewBonus.amount[NewBonus.key] ~= value then
value = NewBonus.amount[NewBonus.key]
end
if NewBonus.type[NewBonus.key] ~= type then
return self:set(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key], NewBonus.amount[NewBonus.key], not adding)
end
else
NewBonus.unit[NewBonus.key] = unit
NewBonus.type[NewBonus.key] = type
NewBonus.amount[NewBonus.key] = value
end
if type == BONUS_DAMAGE then
return self:setAbility(unit, DAMAGE_ABILITY, DAMAGE_FIELD, value, true, adding)
elseif type == BONUS_ARMOR then
return self:setAbility(unit, ARMOR_ABILITY, ARMOR_FIELD, value, true, adding)
elseif type == BONUS_HEALTH then
local real = GetUnitLifePercent(unit)
if value == 0 and not adding then
BlzSetUnitMaxHP(unit, (BlzGetUnitMaxHP(unit) - self:get(unit, type)))
else
BlzSetUnitMaxHP(unit, (BlzGetUnitMaxHP(unit) + value))
end
self:setAbility(unit, HEALTH_ABILITY, HEALTH_FIELD, value, true, adding)
SetUnitLifePercentBJ(unit, real)
return value
elseif type == BONUS_MANA then
local real = GetUnitManaPercent(unit)
if value == 0 and not adding then
BlzSetUnitMaxMana(unit, (BlzGetUnitMaxMana(unit) - self:get(unit, type)))
else
BlzSetUnitMaxMana(unit, (BlzGetUnitMaxMana(unit) + value))
end
self:setAbility(unit, MANA_ABILITY, MANA_FIELD, value, true, adding)
SetUnitManaPercentBJ(unit, real)
return value
elseif type == BONUS_AGILITY then
return self:setAbility(unit, STATS_ABILITY, AGILITY_FIELD, value, true, adding)
elseif type == BONUS_STRENGTH then
return self:setAbility(unit, STATS_ABILITY, STRENGTH_FIELD, value, true, adding)
elseif type == BONUS_INTELLIGENCE then
return self:setAbility(unit, STATS_ABILITY, INTELLIGENCE_FIELD, value, true, adding)
elseif type == BONUS_MOVEMENT_SPEED then
return self:setAbility(unit, MOVEMENTSPEED_ABILITY, MOVEMENTSPEED_FIELD, value, true, adding)
elseif type == BONUS_SIGHT_RANGE then
if value == 0 and not adding then
BlzSetUnitRealField(unit, UNIT_RF_SIGHT_RADIUS, (BlzGetUnitRealField(unit, UNIT_RF_SIGHT_RADIUS) - self:get(unit, type)))
else
BlzSetUnitRealField(unit, UNIT_RF_SIGHT_RADIUS, (BlzGetUnitRealField(unit, UNIT_RF_SIGHT_RADIUS) + value))
end
self:setAbility(unit, SIGHT_RANGE_ABILITY, SIGHT_RANGE_FIELD, value, true, adding)
return value
elseif type == BONUS_HEALTH_REGEN then
return self:setAbility(unit, HEALTHREGEN_ABILITY, HEALTHREGEN_FIELD, value, false, adding)
elseif type == BONUS_MANA_REGEN then
return self:setAbility(unit, MANAREGEN_ABILITY, MANAREGEN_FIELD, value, false, adding)
elseif type == BONUS_ATTACK_SPEED then
return self:setAbility(unit, ATTACKSPEED_ABILITY, ATTACKSPEED_FIELD, value, false, adding)
elseif type == BONUS_MAGIC_RESISTANCE then
return self:setAbility(unit, MAGIC_RESISTANCE_ABILITY, MAGIC_RESISTANCE_FIELD, value, false, adding)
elseif type >= BONUS_EVASION_CHANCE and type <= NewBonus.last then
if NewBonus_EXTENDED and Damage and Evasion and Critical and SpellPower and LifeSteal and SpellVamp and Tenacity then
if type == BONUS_EVASION_CHANCE then
SetUnitEvasionChance(unit, value)
elseif type == BONUS_MISS_CHANCE then
SetUnitMissChance(unit, value)
elseif type == BONUS_CRITICAL_CHANCE then
SetUnitCriticalChance(unit, value)
elseif type == BONUS_CRITICAL_DAMAGE then
SetUnitCriticalMultiplier(unit, value)
elseif type == BONUS_SPELL_POWER_FLAT then
SetUnitSpellPowerFlat(unit, value)
elseif type == BONUS_SPELL_POWER_PERCENT then
SetUnitSpellPowerPercent(unit, value)
elseif type == BONUS_LIFE_STEAL then
SetUnitLifeSteal(unit, value)
elseif type == BONUS_SPELL_VAMP then
SetUnitSpellVamp(unit, value)
elseif type == BONUS_COOLDOWN_REDUCTION then
if adding then
UnitAddCooldownReduction(unit, value)
else
SetUnitCooldownReduction(unit, value)
end
elseif type == BONUS_COOLDOWN_REDUCTION_FLAT then
SetUnitCooldownReductionFlat(unit, value)
elseif type == BONUS_COOLDOWN_OFFSET then
SetUnitCooldownOffset(unit, value)
elseif type == BONUS_TENACITY then
if adding then
UnitAddTenacity(unit, value)
else
SetUnitTenacity(unit, value)
end
elseif type == BONUS_TENACITY_FLAT then
SetUnitTenacityFlat(unit, value)
elseif type == BONUS_TENACITY_OFFSET then
SetUnitTenacityOffset(unit, value)
end
NewBonus.linkType = type
if NewBonus.key > 0 then
NewBonus.key = NewBonus.key - 1
end
return value
else
if type == BONUS_CRITICAL_CHANCE then
return self:setAbility(unit, CRITICAL_STRIKE_ABILITY, CRITICAL_CHANCE_FIELD, value, false, adding)
elseif type == BONUS_CRITICAL_DAMAGE then
return self:setAbility(unit, CRITICAL_STRIKE_ABILITY, CRITICAL_DAMAGE_FIELD, value, false, adding)
elseif type == BONUS_EVASION_CHANCE then
return self:setAbility(unit, EVASION_ABILITY, EVASION_FIELD, value, false, adding)
elseif type == BONUS_LIFE_STEAL then
return self:setAbility(unit, LIFE_STEAL_ABILITY, LIFE_STEAL_FIELD, value, false, adding)
end
end
else
print("Invalid Bonus Type")
end
return -1
end
function mt:add(unit, type, value)
if value ~= 0 then
NewBonus.key = NewBonus.key + 1
NewBonus.unit[NewBonus.key] = unit
NewBonus.type[NewBonus.key] = type
NewBonus.amount[NewBonus.key] = value
if type <= BONUS_SIGHT_RANGE then
NewBonus.amount[NewBonus.key] = self:checkOverflow(self:get(unit, type), R2I(value))
end
self:onEvent(NewBonus.key)
value = NewBonus.amount[NewBonus.key]
if NewBonus.type[NewBonus.key] <= BONUS_SIGHT_RANGE then
self:set(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key], self:checkOverflow(self:get(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key]), R2I(NewBonus.amount[NewBonus.key])), true)
else
if NewBonus_EXTENDED and Damage and Evasion and Critical and SpellPower and LifeSteal and SpellVamp and Tenacity then
if NewBonus.type[NewBonus.key] == BONUS_COOLDOWN_REDUCTION or NewBonus.type[NewBonus.key] == BONUS_TENACITY then
self:set(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key], NewBonus.amount[NewBonus.key], true)
else
self:set(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key], self:get(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key]) + NewBonus.amount[NewBonus.key], true)
end
else
self:set(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key], self:get(NewBonus.unit[NewBonus.key], NewBonus.type[NewBonus.key]) + NewBonus.amount[NewBonus.key], true)
end
end
return value
end
return -1
end
function mt:register(code, bonus)
if type(code) == "function" then
if bonus >= BONUS_DAMAGE and bonus <= NewBonus.last then
if not event[bonus] then event[bonus] = {} end
table.insert(event[bonus], code)
else
table.insert(trigger, code)
end
end
end
OnInit.main(function(require)
require.optionally "DamageInterface "
require.optionally "Evasion "
require.optionally "CriticalStrike"
require.optionally "SpellPower"
require.optionally "LifeSteal"
require.optionally "SpellVamp"
require.optionally "Tenacity"
if NewBonus_EXTENDED and Damage and Evasion and Critical and SpellPower and LifeSteal and SpellVamp and Tenacity then
NewBonus.last = BONUS_TENACITY_OFFSET
else
NewBonus.last = BONUS_LIFE_STEAL
end
end)
end
if Debug and Debug.endFile then Debug.endFile() end
--[[ requires NewBonus, RegisterPlayerUnitEvent
-- ------------------------------------- NewBonusUtils v2.4 ------------------------------------- --
-- API:
-- function AddUnitBonusTimed takes unit u, integer bonus_type, real amount, real duration returns nothing
-- -> Add the specified amount for the specified bonus type for unit for a duration
-- -> Example: call AddUnitBonusTimed(GetTriggerUnit(), BONUS_ARMOR, 13, 10.5)
-- function LinkBonusToBuff takes unit u, integer bonus_type, real amount, integer buffId returns nothing
-- -> Links the bonus amount specified to a buff or ability. As long as the unit has the buff or
-- -> the ability represented by the parameter buffId the bonus is not removed.
-- -> Example: call LinkBonusToBuff(GetTriggerUnit(), BONUS_ARMOR, 10, 'B000')
-- function LinkBonusToItem takes unit u, integer bonus_type, real amount, item i returns nothing
-- -> Links the bonus amount specified to an item. As long as the unit has that item the bonus is not removed.
-- -> Note that it will work for items with the same id, because it takes as parameter the item object.
-- -> Example: call LinkBonusToItem(GetManipulatingUnit(), BONUS_ARMOR, 10, GetManipulatedItem())
-- function UnitCopyBonuses takes unit source, unit target returns nothing
-- -> Copy the source unit bonuses using the Add functionality to the target unit
-- -> Example: call UnitCopyBonuses(GetTriggerUnit(), GetSummonedUnit())
-- function UnitMirrorBonuses takes unit source, unit target returns nothing
-- -> Copy the source unit bonuses using the Set functionality to the target unit
-- -> Example: call UnitMirrorBonuses(GetTriggerUnit(), GetSummonedUnit())
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
do
-- ---------------------------------------------------------------------------------------------- --
-- Configuration --
-- ---------------------------------------------------------------------------------------------- --
local PERIOD = 0.03125000
-- ---------------------------------------------------------------------------------------------- --
-- LUA API --
-- ---------------------------------------------------------------------------------------------- --
function AddUnitBonusTimed(unit, type, amount, duration)
NewBonusUtils:linkTimed(unit, type, amount, duration, true)
end
function LinkBonusToBuff(unit, type, amount, buff)
NewBonusUtils:linkBuff(unit, type, amount, buff, false)
end
function LinkBonusToItem(unit, type, amount, item)
NewBonusUtils:linkItem(unit, type, amount, item)
end
function UnitCopyBonuses(source, target)
NewBonusUtils:copy(source, target)
end
function UnitMirrorBonuses(source, target)
NewBonusUtils:mirror(source, target)
end
-- ---------------------------------------------------------------------------------------------- --
-- System --
-- ---------------------------------------------------------------------------------------------- --
NewBonusUtils = setmetatable({}, {})
local mt = getmetatable(NewBonusUtils)
mt.__index = mt
local array = {}
local key = 0
local items = {}
local k = 0
local timer = CreateTimer()
function mt:destroy(i, item)
if NewBonus_EXTENDED and Damage and Evasion and Critical and SpellPower and LifeSteal and SpellVamp and Tenacity then
if self.type == BONUS_COOLDOWN_REDUCTION then
UnitRemoveCooldownReduction(self.unit, self.amount)
elseif self.type == BONUS_TENACITY then
UnitRemoveTenacity(self.unit, self.amount)
else
AddUnitBonus(self.unit, self.type, -self.amount)
end
else
AddUnitBonus(self.unit, self.type, -self.amount)
end
if item then
items[i] = items[k]
k = k - 1
else
array[i] = array[key]
key = key - 1
if key == 0 then
PauseTimer(timer)
end
end
self = nil
return i - 1
end
function mt:onPeriod()
local i = 1
local this
while i <= key do
this = array[i]
if this.timed then
if this.duration <= 0 then
i = this:destroy(i, false)
end
this.duration = this.duration - PERIOD
else
if GetUnitAbilityLevel(this.unit, this.buff) == 0 then
i = this:destroy(i, false)
end
end
i = i + 1
end
end
function mt:linkItem(unit, type, amount, item)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.item = item
this.amount = AddUnitBonus(unit, type, amount)
this.type = NewBonus.linkType
k = k + 1
items[k] = this
end
function mt:linkTimed(unit, type, amount, duration, timed)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.timed = timed
this.duration = duration
this.amount = AddUnitBonus(unit, type, amount)
this.type = NewBonus.linkType
key = key + 1
array[key] = this
if key == 1 then
TimerStart(timer, PERIOD, true, function() self:onPeriod() end)
end
end
function mt:linkBuff(unit, type, amount, buff, timed)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.timed = timed
this.buff = buff
this.amount = AddUnitBonus(unit, type, amount)
this.type = NewBonus.linkType
key = key + 1
array[key] = this
if key == 1 then
TimerStart(timer, PERIOD, true, function() self:onPeriod() end)
end
end
function mt:copy(source, target)
for i = 1, NewBonus.last do
if GetUnitBonus(source, i) ~= 0 then
AddUnitBonus(target, i, GetUnitBonus(source, i))
end
end
end
function mt:mirror(source, target)
for i = 1, NewBonus.last do
SetUnitBonus(target, i, GetUnitBonus(source, i))
end
end
onInit(function()
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DROP_ITEM, function()
local item = GetManipulatedItem()
local i = 1
local this
while i <= k do
this = items[i]
if this.item == item then
i = this:destroy(i, true)
end
i = i + 1
end
end)
end)
end
if Debug and Debug.beginFile then Debug.beginFile("NewBonusUtils") end
--[[ requires NewBonus, RegisterPlayerUnitEvent
-- ------------------------------------- NewBonusUtils v2.4 ------------------------------------- --
-- API:
-- function AddUnitBonusTimed takes unit u, integer bonus_type, real amount, real duration returns nothing
-- -> Add the specified amount for the specified bonus type for unit for a duration
-- -> Example: call AddUnitBonusTimed(GetTriggerUnit(), BONUS_ARMOR, 13, 10.5)
-- function LinkBonusToBuff takes unit u, integer bonus_type, real amount, integer buffId returns nothing
-- -> Links the bonus amount specified to a buff or ability. As long as the unit has the buff or
-- -> the ability represented by the parameter buffId the bonus is not removed.
-- -> Example: call LinkBonusToBuff(GetTriggerUnit(), BONUS_ARMOR, 10, 'B000')
-- function LinkBonusToItem takes unit u, integer bonus_type, real amount, item i returns nothing
-- -> Links the bonus amount specified to an item. As long as the unit has that item the bonus is not removed.
-- -> Note that it will work for items with the same id, because it takes as parameter the item object.
-- -> Example: call LinkBonusToItem(GetManipulatingUnit(), BONUS_ARMOR, 10, GetManipulatedItem())
-- function UnitCopyBonuses takes unit source, unit target returns nothing
-- -> Copy the source unit bonuses using the Add functionality to the target unit
-- -> Example: call UnitCopyBonuses(GetTriggerUnit(), GetSummonedUnit())
-- function UnitMirrorBonuses takes unit source, unit target returns nothing
-- -> Copy the source unit bonuses using the Set functionality to the target unit
-- -> Example: call UnitMirrorBonuses(GetTriggerUnit(), GetSummonedUnit())
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
OnInit.root(function()
-- ---------------------------------------------------------------------------------------------- --
-- Configuration --
-- ---------------------------------------------------------------------------------------------- --
local PERIOD = 0.03125000
-- ---------------------------------------------------------------------------------------------- --
-- LUA API --
-- ---------------------------------------------------------------------------------------------- --
function AddUnitBonusTimed(unit, type, amount, duration)
NewBonusUtils:linkTimed(unit, type, amount, duration, true)
end
function LinkBonusToBuff(unit, type, amount, buff)
NewBonusUtils:linkBuff(unit, type, amount, buff, false)
end
function LinkBonusToItem(unit, type, amount, item)
NewBonusUtils:linkItem(unit, type, amount, item)
end
function UnitCopyBonuses(source, target)
NewBonusUtils:copy(source, target)
end
function UnitMirrorBonuses(source, target)
NewBonusUtils:mirror(source, target)
end
-- ---------------------------------------------------------------------------------------------- --
-- System --
-- ---------------------------------------------------------------------------------------------- --
NewBonusUtils = setmetatable({}, {})
local mt = getmetatable(NewBonusUtils)
mt.__index = mt
local array = {}
local key = 0
local items = {}
local k = 0
local timer = CreateTimer()
function mt:destroy(i, item)
if NewBonus_EXTENDED and Damage and Evasion and Critical and SpellPower and LifeSteal and SpellVamp and Tenacity then
if self.type == BONUS_COOLDOWN_REDUCTION then
UnitRemoveCooldownReduction(self.unit, self.amount)
elseif self.type == BONUS_TENACITY then
UnitRemoveTenacity(self.unit, self.amount)
else
AddUnitBonus(self.unit, self.type, -self.amount)
end
else
AddUnitBonus(self.unit, self.type, -self.amount)
end
if item then
items[i] = items[k]
k = k - 1
else
array[i] = array[key]
key = key - 1
if key == 0 then
PauseTimer(timer)
end
end
self = nil
return i - 1
end
function mt:onPeriod()
local i = 1
local this
while i <= key do
this = array[i]
if this.timed then
if this.duration <= 0 then
i = this:destroy(i, false)
end
this.duration = this.duration - PERIOD
else
if GetUnitAbilityLevel(this.unit, this.buff) == 0 then
i = this:destroy(i, false)
end
end
i = i + 1
end
end
function mt:linkItem(unit, type, amount, item)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.item = item
this.amount = AddUnitBonus(unit, type, amount)
this.type = NewBonus.linkType
k = k + 1
items[k] = this
end
function mt:linkTimed(unit, type, amount, duration, timed)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.timed = timed
this.duration = duration
this.amount = AddUnitBonus(unit, type, amount)
this.type = NewBonus.linkType
key = key + 1
array[key] = this
if key == 1 then
TimerStart(timer, PERIOD, true, function() self:onPeriod() end)
end
end
function mt:linkBuff(unit, type, amount, buff, timed)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.timed = timed
this.buff = buff
this.amount = AddUnitBonus(unit, type, amount)
this.type = NewBonus.linkType
key = key + 1
array[key] = this
if key == 1 then
TimerStart(timer, PERIOD, true, function() self:onPeriod() end)
end
end
function mt:copy(source, target)
for i = 1, NewBonus.last do
if GetUnitBonus(source, i) ~= 0 then
AddUnitBonus(target, i, GetUnitBonus(source, i))
end
end
end
function mt:mirror(source, target)
for i = 1, NewBonus.last do
SetUnitBonus(target, i, GetUnitBonus(source, i))
end
end
OnInit.global(function()
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DROP_ITEM, function()
local item = GetManipulatedItem()
local i = 1
local this
while i <= k do
this = items[i]
if this.item == item then
i = this:destroy(i, true)
end
i = i + 1
end
end)
end)
end)
if Debug and Debug.endFile then Debug.endFile() end
--[[ requires Utilities, WorldBounds, Indexer, TimerUtils, RegisterPlayerUnitEvent, optional Tenacity
-- ------------------------------------- Crowd Control v1.0 ------------------------------------- --
-- How to Import:
-- 1 - Copy the Utilities library over to your map and follow its install instructions
-- 2 - Copy the WorldBounds library over to your map and follow its install instructions
-- 3 - Copy the Indexer library over to your map and follow its install instructions
-- 4 - Copy the RegisterPlayerUnitEvent library over to your map and follow its install instructions
-- 5 - Copy the Tenacity library over to your map and follow its install instructions
-- 6 - Copy this library into your map
-- 7 - Copy the 14 buffs and 15 abilities with the CC prefix and match their raw code below.
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
do
-- ---------------------------------------------------------------------------------------------- --
-- Configuration --
-- ---------------------------------------------------------------------------------------------- --
-- The raw code of the silence ability
local SILENCE = FourCC('U000')
-- The raw code of the stun ability
local STUN = FourCC('U001')
-- The raw code of the attack slow ability
local ATTACK_SLOW = FourCC('U002')
-- The raw code of the movement slow ability
local MOVEMENT_SLOW = FourCC('U003')
-- The raw code of the banish ability
local BANISH = FourCC('U004')
-- The raw code of the ensnare ability
local ENSNARE = FourCC('U005')
-- The raw code of the purge ability
local PURGE = FourCC('U006')
-- The raw code of the hex ability
local HEX = FourCC('U007')
-- The raw code of the sleep ability
local SLEEP = FourCC('U008')
-- The raw code of the cyclone ability
local CYCLONE = FourCC('U009')
-- The raw code of the entangle ability
local ENTANGLE = FourCC('U010')
-- The raw code of the disarm ability
local DISARM = FourCC('U011')
-- The raw code of the fear ability
local FEAR = FourCC('U012')
-- The raw code of the taunt ability
local TAUNT = FourCC('U013')
-- The raw code of the true sight ability
local TRUE_SIGHT = FourCC('U014')
-- The raw code of the silence buff
local SILENCE_BUFF = FourCC('BU00')
-- The raw code of the stun buff
local STUN_BUFF = FourCC('BU01')
-- The raw code of the attack slow buff
local ATTACK_SLOW_BUFF = FourCC('BU02')
-- The raw code of the movement slow buff
local MOVEMENT_SLOW_BUFF = FourCC('BU03')
-- The raw code of the banish buff
local BANISH_BUFF = FourCC('BU04')
-- The raw code of the ensnare buff
local ENSNARE_BUFF = FourCC('BU05')
-- The raw code of the purge buff
local PURGE_BUFF = FourCC('BU06')
-- The raw code of the hex buff
local HEX_BUFF = FourCC('BU07')
-- The raw code of the sleep buff
local SLEEP_BUFF = FourCC('BU08')
-- The raw code of the cyclone buff
local CYCLONE_BUFF = FourCC('BU09')
-- The raw code of the entangle buff
local ENTANGLE_BUFF = FourCC('BU10')
-- The raw code of the disarm buff
local DISARM_BUFF = FourCC('BU11')
-- The raw code of the fear buff
local FEAR_BUFF = FourCC('BU12')
-- The raw code of the taunt buff
local TAUNT_BUFF = FourCC('BU13')
-- This is the maximum recursion limit allowed by the system.
-- Its value must be greater than or equal to 0. When equal to 0
-- no recursion is allowed. Values too big can cause screen freezes.
local RECURSION_LIMIT = 8
-- The Crowd Control types
CROWD_CONTROL_SILENCE = 0
CROWD_CONTROL_STUN = 1
CROWD_CONTROL_SLOW = 2
CROWD_CONTROL_SLOW_ATTACK = 3
CROWD_CONTROL_BANISH = 4
CROWD_CONTROL_ENSNARE = 5
CROWD_CONTROL_PURGE = 6
CROWD_CONTROL_HEX = 7
CROWD_CONTROL_SLEEP = 8
CROWD_CONTROL_CYCLONE = 9
CROWD_CONTROL_ENTANGLE = 10
CROWD_CONTROL_DISARM = 11
CROWD_CONTROL_FEAR = 12
CROWD_CONTROL_TAUNT = 13
CROWD_CONTROL_KNOCKBACK = 14
CROWD_CONTROL_KNOCKUP = 15
-- ---------------------------------------------------------------------------------------------- --
-- LUA API --
-- ---------------------------------------------------------------------------------------------- --
-- ------------------------------------------- Disarm ------------------------------------------- --
function DisarmUnit(unit, duration, model, point, stack)
CrowdControl:disarm(unit, duration, model, point, stack)
end
function IsUnitDisarmed(unit)
return CrowdControl:disarmed(unit)
end
-- -------------------------------------------- Fear -------------------------------------------- --
function FearUnit(unit, duration, model, point, stack)
CrowdControl:fear(unit, duration, model, point, stack)
end
function IsUnitFeared(unit)
return CrowdControl:feared(unit)
end
-- -------------------------------------------- Taunt ------------------------------------------- --
function TauntUnit(source, target, duration, model, point, stack)
CrowdControl:taunt(source, target, duration, model, point, stack)
end
function IsUnitTaunted(unit)
return CrowdControl:taunted(unit)
end
-- ------------------------------------------ Knockback ----------------------------------------- --
function KnockbackUnit(unit, angle, distance, duration, model, point, onCliff, onDestructable, onUnit, stack)
CrowdControl:knockback(unit, angle, distance, duration, model, point, onCliff, onDestructable, onUnit, stack)
end
function IsUnitKnockedBack(unit)
return CrowdControl:knockedback(unit)
end
-- ------------------------------------------- Knockup ------------------------------------------ --
function KnockupUnit(unit, height, duration, model, point, stack)
CrowdControl:knockup(unit, height, duration, model, point, stack)
end
function IsUnitKnockedUp(unit)
return CrowdControl:knockedup(unit)
end
-- ------------------------------------------- Silence ------------------------------------------ --
function SilenceUnit(unit, duration, model, point, stack)
CrowdControl:silence(unit, duration, model, point, stack)
end
function IsUnitSilenced(unit)
return CrowdControl:silenced(unit)
end
-- -------------------------------------------- Stun -------------------------------------------- --
function StunUnit(unit, duration, model, point, stack)
CrowdControl:stun(unit, duration, model, point, stack)
end
function IsUnitStunned(unit)
return CrowdControl:stunned(unit)
end
-- ---------------------------------------- Movement Slow --------------------------------------- --
function SlowUnit(unit, amount, duration, model, point, stack)
CrowdControl:slow(unit, amount, duration, model, point, stack)
end
function IsUnitSlowed(unit)
return CrowdControl:slowed(unit)
end
-- ----------------------------------------- Attack Slow ---------------------------------------- --
function SlowUnitAttack(unit, amount, duration, model, point, stack)
CrowdControl:slowAttack(unit, amount, duration, model, point, stack)
end
function IsUnitAttackSlowed(unit)
return CrowdControl:attackSlowed(unit)
end
-- ------------------------------------------- Banish ------------------------------------------- --
function BanishUnit(unit, duration, model, point, stack)
CrowdControl:banish(unit, duration, model, point, stack)
end
function IsUnitBanished(unit)
return CrowdControl:banished(unit)
end
-- ------------------------------------------- Ensnare ------------------------------------------ --
function EnsnareUnit(unit, duration, model, point, stack)
CrowdControl:ensnare(unit, duration, model, point, stack)
end
function IsUnitEnsnared(unit)
return CrowdControl:ensnared(unit)
end
-- -------------------------------------------- Purge ------------------------------------------- --
function PurgeUnit(unit, duration, model, point, stack)
CrowdControl:purge(unit, duration, model, point, stack)
end
function IsUnitPurged(unit)
return CrowdControl:purged(unit)
end
-- --------------------------------------------- Hex -------------------------------------------- --
function HexUnit(unit, duration, model, point, stack)
CrowdControl:hex(unit, duration, model, point, stack)
end
function IsUnitHexed(unit)
return CrowdControl:hexed(unit)
end
-- -------------------------------------------- Sleep ------------------------------------------- --
function SleepUnit(unit, duration, model, point, stack)
CrowdControl:sleep(unit, duration, model, point, stack)
end
function IsUnitSleeping(unit)
return CrowdControl:sleeping(unit)
end
-- ------------------------------------------- Cyclone ------------------------------------------ --
function CycloneUnit(unit, duration, model, point, stack)
CrowdControl:cyclone(unit, duration, model, point, stack)
end
function IsUnitCycloned(unit)
return CrowdControl:cycloned(unit)
end
-- ------------------------------------------ Entangle ------------------------------------------ --
function EntangleUnit(unit, duration, model, point, stack)
CrowdControl:entangle(unit, duration, model, point, stack)
end
function IsUnitEntangled(unit)
return CrowdControl:entangled(unit)
end
-- ------------------------------------------- Dispel ------------------------------------------- --
function UnitDispelCrowdControl(unit, type)
CrowdControl:dispel(unit, type)
end
function UnitDispelAllCrowdControl(unit)
CrowdControl:dispelAll(unit)
end
-- ------------------------------------------- Events ------------------------------------------- --
function RegisterCrowdControlEvent(type, code)
CrowdControl:register(type, code)
end
function RegisterAnyCrowdControlEvent(code)
CrowdControl:register(-1, code)
end
function GetCrowdControlUnit()
return CrowdControl.unit[CrowdControl.key]
end
function GetCrowdControlType()
return CrowdControl.type[CrowdControl.key]
end
function GetCrowdControlDuration()
return CrowdControl.duration[CrowdControl.key]
end
function GetCrowdControlAmount()
return CrowdControl.amount[CrowdControl.key]
end
function GetCrowdControlModel()
return CrowdControl.model[CrowdControl.key]
end
function GetCrowdControlBone()
return CrowdControl.point[CrowdControl.key]
end
function GetCrowdControlStack()
return CrowdControl.stack[CrowdControl.key]
end
function GetCrowdControlRemaining(unit, type)
return CrowdControl:remaining(unit, type)
end
function GetTauntSource()
return CrowdControl.source[CrowdControl.key]
end
function GetKnockbackAngle()
return CrowdControl.angle[CrowdControl.key]
end
function GetKnockbackDistance()
return CrowdControl.distance[CrowdControl.key]
end
function GetKnockupHeight()
return CrowdControl.height[CrowdControl.key]
end
function GetKnockbackOnCliff()
return CrowdControl.cliff[CrowdControl.key]
end
function GetKnockbackOnDestructable()
return CrowdControl.destructable[CrowdControl.key]
end
function GetKnockbackOnUnit()
return CrowdControl.agent[CrowdControl.key]
end
function SetCrowdControlUnit(unit)
CrowdControl.unit[CrowdControl.key] = unit
end
function SetCrowdControlType(type)
if type >= CROWD_CONTROL_SILENCE and type <= CROWD_CONTROL_KNOCKUP then
CrowdControl.type[CrowdControl.key] = type
end
end
function SetCrowdControlDuration(duration)
CrowdControl.duration[CrowdControl.key] = duration
end
function SetCrowdControlAmount(amount)
CrowdControl.amount[CrowdControl.key] = amount
end
function SetCrowdControlModel(model)
CrowdControl.model[CrowdControl.key] = model
end
function SetCrowdControlBone(point)
CrowdControl.point[CrowdControl.key] = point
end
function SetCrowdControlStack(stack)
CrowdControl.stack[CrowdControl.key] = stack
end
function SetTauntSource(unit)
CrowdControl.source[CrowdControl.key] = unit
end
function SetKnockbackAngle(angle)
CrowdControl.angle[CrowdControl.key] = angle
end
function SetKnockbackDistance(distance)
CrowdControl.distance[CrowdControl.key] = distance
end
function SetKnockupHeight(height)
CrowdControl.height[CrowdControl.key] = height
end
function SetKnockbackOnCliff(onCliff)
CrowdControl.cliff[CrowdControl.key] = onCliff
end
function SetKnockbackOnDestructable(onDestructable)
CrowdControl.destructable[CrowdControl.key] = onDestructable
end
function SetKnockbackOnUnit(onUnit)
CrowdControl.agent[CrowdControl.key] = onUnit
end
-- ---------------------------------------------------------------------------------------------- --
-- Systems --
-- ---------------------------------------------------------------------------------------------- --
-- ------------------------------------------ Knockback ----------------------------------------- --
do
Knockback = setmetatable({}, {})
local mt = getmetatable(Knockback)
mt.__index = mt
local timer = CreateTimer()
local rect = Rect(0., 0., 0., 0.)
local period = 0.03125
local array = {}
local struct = {}
local key = 0
function mt:destroy(i)
DestroyGroup(self.group)
DestroyEffect(self.effect)
BlzPauseUnitEx(self.unit, false)
struct[self.unit] = nil
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:knocked(unit)
return struct[unit] ~= nil
end
function mt:apply(target, angle, distance, duration, model, point, cliff, dest, hit)
local this
if duration > 0 and UnitAlive(target) then
if struct[target] then
this = struct[target]
else
this = {}
setmetatable(this, mt)
this.unit = target
this.collision = 2*BlzGetUnitCollisionSize(target)
this.group = CreateGroup()
key = key + 1
array[key] = this
struct[target] = this
BlzPauseUnitEx(target, true)
if model and point then
this.effect = AddSpecialEffectTarget(model, target, point)
end
if key == 1 then
TimerStart(timer, period, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.duration > 0 and UnitAlive(this.unit) then
local x = GetUnitX(this.unit) + this.offset*Cos(this.angle)
local y = GetUnitY(this.unit) + this.offset*Sin(this.angle)
this.duration = this.duration - period
if this.onUnit and this.collision > 0 then
GroupEnumUnitsInRange(this.group, x, y, this.collision, nil)
GroupRemoveUnit(this.group, this.unit)
for j = 0, BlzGroupGetSize(this.group) - 1 do
if UnitAlive(BlzGroupUnitAt(this.group, j)) then
this.duration = 0
break
end
end
end
if this.onDestructable and this.duration > 0 and this.collision > 0 then
SetRect(rect, x - this.collision, y - this.collision, x + this.collision, y + this.collision)
EnumDestructablesInRect(rect, nil, function()
if GetDestructableLife(GetEnumDestructable()) > 0 then
this.duration = 0
return
end
end)
end
if this.onCliff and this.duration > 0 then
if GetTerrainCliffLevel(GetUnitX(this.unit), GetUnitY(this.unit)) < GetTerrainCliffLevel(x, y) and GetUnitZ(this.unit) < (GetTerrainCliffLevel(x, y) - GetTerrainCliffLevel(WorldBounds.maxX, WorldBounds.maxY))*bj_CLIFFHEIGHT then
this.duration = 0
end
end
if this.duration > 0 then
SetUnitX(this.unit, x)
SetUnitY(this.unit, y)
end
else
i = this:destroy(i)
end
i = i + 1
end
end)
end
end
this.angle = angle
this.distance = distance
this.duration = duration
this.onCliff = cliff
this.onDestructable = dest
this.onUnit = hit
this.offset = RMaxBJ(0.00000001, distance*period/RMaxBJ(0.00000001, duration))
end
end
end
-- ------------------------------------------- Knockup ------------------------------------------ --
do
Knockup = setmetatable({}, {})
local mt = getmetatable(Knockup)
mt.__index = mt
local knocked = {}
function mt:isUnitKnocked(unit)
return (knocked[unit] or 0) > 0
end
function mt:apply(unit, duration, height, model, point)
if duration > 0 then
local timer = CreateTimer()
local rate = height/duration
local effect
knocked[unit] = (knocked[unit] or 0) + 1
if model and point then
effect = AddSpecialEffect(model, unit, point)
end
if knocked[unit] == 1 then
BlzPauseUnitEx(unit, true)
end
UnitAddAbility(unit, FourCC('Amrf'))
UnitRemoveAbility(unit, FourCC('Amrf'))
SetUnitFlyHeight(unit, (GetUnitDefaultFlyHeight(unit) + height), rate)
TimerStart(timer, duration/2, false, function()
SetUnitFlyHeight(unit, GetUnitDefaultFlyHeight(unit), rate)
TimerStart(timer, duration/2, false, function()
DestroyEffect(effect)
PauseTimer(timer)
DestroyTimer(timer)
knocked[unit] = knocked[unit] - 1
if knocked[unit] == 0 then
BlzPauseUnitEx(unit, false)
end
end)
end)
end
end
end
-- -------------------------------------------- Fear -------------------------------------------- --
do
Fear = setmetatable({}, {})
local mt = getmetatable(Fear)
mt.__index = mt
local timer = CreateTimer()
local array = {}
local struct = {}
local flag = {}
local x = {}
local y = {}
local key = 0
local UPDATE = 0.2
local DIRECTION_CHANGE = 5
local MAX_CHANGE = 200.
local dummy
local ability
function mt:feared(unit)
return GetUnitAbilityLevel(unit, FEAR_BUFF) > 0
end
function mt:destroy(i)
flag[self.target] = true
IssueImmediateOrder(self.target, "stop")
DestroyEffect(self.effect)
array[i] = array[key]
key = key - 1
struct[self.target] = nil
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:apply(target, duration, effect, attach)
local this
if duration > 0 then
BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_NORMAL, 0, duration)
BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_HERO, 0, duration)
IncUnitAbilityLevel(dummy, FEAR)
DecUnitAbilityLevel(dummy, FEAR)
if IssueTargetOrder(dummy, "drunkenhaze", target) then
if struct[target] then
this = struct[target]
else
this = {}
setmetatable(this, mt)
key = key + 1
array[key] = this
struct[target] = this
this.target = target
this.change = 0
if effect and attach then
this.effect = AddSpecialEffectTarget(effect, target, attach)
end
if key == 1 then
TimerStart(timer, UPDATE, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if GetUnitAbilityLevel(this.target, FEAR_BUFF) > 0 then
this.change = this.change + 1
if this.change == DIRECTION_CHANGE then
this.change = 0
flag[this.target] = true
x[this.target] = GetRandomReal(GetUnitX(this.target) - MAX_CHANGE, GetUnitX(this.target) + MAX_CHANGE)
y[this.target] = GetRandomReal(GetUnitY(this.target) - MAX_CHANGE, GetUnitY(this.target) + MAX_CHANGE)
IssuePointOrder(this.target, "move", x[this.target], y[this.target])
end
else
i = this:destroy(i)
end
i = i + 1
end
end)
end
end
flag[target] = true
x[target] = GetRandomReal(GetUnitX(target) - MAX_CHANGE, GetUnitX(target) + MAX_CHANGE)
y[target] = GetRandomReal(GetUnitY(target) - MAX_CHANGE, GetUnitY(target) + MAX_CHANGE)
IssuePointOrder(target, "move", x[target], y[target])
end
end
end
function mt:onOrder()
local unit = GetOrderedUnit()
if self:feared(unit) and GetIssuedOrderId() ~= 851973 then
if not flag[unit] then
flag[unit] = true
IssuePointOrder(unit, "move", x[unit], y[unit])
else
flag[unit] = false
end
end
end
onInit(function()
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function()
Fear:onOrder()
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function()
Fear:onOrder()
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function()
Fear:onOrder()
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_UNIT_ORDER, function()
Fear:onOrder()
end)
TimerStart(CreateTimer(), 0, false, function()
dummy = DummyRetrieve(Player(PLAYER_NEUTRAL_PASSIVE), GetRectCenterX(GetWorldBounds()), GetRectCenterY(GetWorldBounds()), 0, 0)
UnitAddAbility(dummy, TRUE_SIGHT)
UnitAddAbility(dummy, FEAR)
PauseTimer(GetExpiredTimer())
DestroyTimer(GetExpiredTimer())
ability = BlzGetUnitAbility(dummy, FEAR)
end)
end)
end
-- -------------------------------------------- Taunt ------------------------------------------- --
do
Taunt = setmetatable({}, {})
local mt = getmetatable(Taunt)
mt.__index = mt
Taunt.source = {}
local timer = CreateTimer()
local PERIOD = 0.2
local key = 0
local array = {}
local struct = {}
local dummy
local ability
function mt:taunted(unit)
return GetUnitAbilityLevel(unit, TAUNT_BUFF) > 0
end
function mt:destroy(i)
UnitDispelCrowdControl(self.target, CROWD_CONTROL_TAUNT)
IssueImmediateOrder(self.target, "stop")
DestroyEffect(self.effect)
if self.selected then
SelectUnitAddForPlayer(self.target, GetOwningPlayer(self.target))
end
array[i] = array[key]
key = key - 1
struct[self.target] = nil
Taunt.source[self.target] = nil
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:apply(source, target, duration, model, point)
local this
if duration > 0 and UnitAlive(source) and UnitAlive(target) then
BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_NORMAL, 0, duration)
BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_HERO, 0, duration)
IncUnitAbilityLevel(dummy, TAUNT)
DecUnitAbilityLevel(dummy, TAUNT)
if IssueTargetOrder(dummy, "drunkenhaze", target) then
if struct[target] then
this = struct[target]
else
this = {}
setmetatable(this, mt)
key = key + 1
array[key] = this
struct[target] = this
this.target = target
this.selected = IsUnitSelected(target, GetOwningPlayer(target))
if this.selected then
SelectUnit(target, false)
end
if model and point then
this.effect = AddSpecialEffectTarget(model, target, point)
end
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if GetUnitAbilityLevel(this.target, TAUNT_BUFF) > 0 and UnitAlive(Taunt.source[this.target]) and UnitAlive(this.target) then
if IsUnitVisible(Taunt.source[this.target], GetOwningPlayer(this.target)) then
IssueTargetOrderById(this.target, 851983, Taunt.source[this.target])
else
IssuePointOrderById(this.target, 851986, GetUnitX(Taunt.source[this.target]), GetUnitY(Taunt.source[this.target]))
end
else
i = this:destroy(i)
end
i = i + 1
end
end)
end
end
Taunt.source[target] = source
if IsUnitVisible(source, GetOwningPlayer(target)) then
IssueTargetOrderById(target, 851983, source)
else
IssuePointOrderById(target, 851986, GetUnitX(source), GetUnitY(source))
end
end
end
end
function mt:onOrder()
local unit = GetOrderedUnit()
local order = GetIssuedOrderId()
if self:taunted(unit) and order ~= 851973 then
if order ~= 851983 and order ~= 851986 then
if IsUnitVisible(Taunt.source[unit], GetOwningPlayer(unit)) then
IssueTargetOrderById(unit, 851983, Taunt.source[unit])
else
IssuePointOrderById(unit, 851986, GetUnitX(Taunt.source[unit]), GetUnitY(Taunt.source[unit]))
end
else
if GetOrderTargetUnit() ~= Taunt.source[unit] and GetOrderTargetUnit() ~= nil then
if IsUnitVisible(source[id], GetOwningPlayer(target)) then
IssueTargetOrderById(unit, 851983, Taunt.source[unit])
else
IssuePointOrderById(unit, 851986, GetUnitX(Taunt.source[unit]), GetUnitY(Taunt.source[unit]))
end
end
end
end
end
function mt:onSelect()
local unit = GetTriggerUnit()
if self:taunted(unit) then
if IsUnitSelected(unit, GetOwningPlayer(unit)) then
SelectUnit(unit, false)
end
end
end
onInit(function()
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function()
Taunt:onOrder()
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function()
Taunt:onOrder()
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function()
Taunt:onOrder()
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_UNIT_ORDER, function()
Taunt:onOrder()
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SELECTED, function()
Taunt:onSelect()
end)
TimerStart(CreateTimer(), 0, false, function()
dummy = DummyRetrieve(Player(PLAYER_NEUTRAL_PASSIVE), GetRectCenterX(GetWorldBounds()), GetRectCenterY(GetWorldBounds()), 0, 0)
UnitAddAbility(dummy, TRUE_SIGHT)
UnitAddAbility(dummy, TAUNT)
PauseTimer(GetExpiredTimer())
DestroyTimer(GetExpiredTimer())
ability = BlzGetUnitAbility(dummy, TAUNT)
end)
end)
end
-- ---------------------------------------- Crowd Control --------------------------------------- --
do
CrowdControl = setmetatable({}, {})
local mt = getmetatable(CrowdControl)
mt.__index = mt
CrowdControl.key = 0
CrowdControl.unit = {}
CrowdControl.source = {}
CrowdControl.amount = {}
CrowdControl.duration = {}
CrowdControl.angle = {}
CrowdControl.distance = {}
CrowdControl.height = {}
CrowdControl.model = {}
CrowdControl.point = {}
CrowdControl.stack = {}
CrowdControl.cliff = {}
CrowdControl.destructable = {}
CrowdControl.agent = {}
CrowdControl.type = {}
local trigger = {}
local timer = {}
local event = {}
local ability = {}
local buff = {}
local order = {}
local dummy
local count = 0
onInit(function()
local t = CreateTimer()
TimerStart(t, 0, false, function()
dummy = DummyRetrieve(Player(PLAYER_NEUTRAL_PASSIVE), GetRectCenterX(GetWorldBounds()), GetRectCenterY(GetWorldBounds()), 0, 0)
UnitAddAbility(dummy, SILENCE)
UnitAddAbility(dummy, STUN)
UnitAddAbility(dummy, ATTACK_SLOW)
UnitAddAbility(dummy, MOVEMENT_SLOW)
UnitAddAbility(dummy, BANISH)
UnitAddAbility(dummy, ENSNARE)
UnitAddAbility(dummy, PURGE)
UnitAddAbility(dummy, HEX)
UnitAddAbility(dummy, SLEEP)
UnitAddAbility(dummy, CYCLONE)
UnitAddAbility(dummy, ENTANGLE)
UnitAddAbility(dummy, DISARM)
UnitAddAbility(dummy, TRUE_SIGHT)
BlzUnitDisableAbility(dummy, SILENCE, true, true)
BlzUnitDisableAbility(dummy, STUN, true, true)
BlzUnitDisableAbility(dummy, ATTACK_SLOW, true, true)
BlzUnitDisableAbility(dummy, MOVEMENT_SLOW, true, true)
BlzUnitDisableAbility(dummy, BANISH, true, true)
BlzUnitDisableAbility(dummy, ENSNARE, true, true)
BlzUnitDisableAbility(dummy, PURGE, true, true)
BlzUnitDisableAbility(dummy, HEX, true, true)
BlzUnitDisableAbility(dummy, SLEEP, true, true)
BlzUnitDisableAbility(dummy, CYCLONE, true, true)
BlzUnitDisableAbility(dummy, ENTANGLE, true, true)
BlzUnitDisableAbility(dummy, DISARM, true, true)
ability[CROWD_CONTROL_SILENCE] = SILENCE
ability[CROWD_CONTROL_STUN] = STUN
ability[CROWD_CONTROL_SLOW] = MOVEMENT_SLOW
ability[CROWD_CONTROL_SLOW_ATTACK] = ATTACK_SLOW
ability[CROWD_CONTROL_BANISH] = BANISH
ability[CROWD_CONTROL_ENSNARE] = ENSNARE
ability[CROWD_CONTROL_PURGE] = PURGE
ability[CROWD_CONTROL_HEX] = HEX
ability[CROWD_CONTROL_SLEEP] = SLEEP
ability[CROWD_CONTROL_CYCLONE] = CYCLONE
ability[CROWD_CONTROL_ENTANGLE] = ENTANGLE
ability[CROWD_CONTROL_DISARM] = DISARM
ability[CROWD_CONTROL_FEAR] = FEAR
ability[CROWD_CONTROL_TAUNT] = TAUNT
buff[CROWD_CONTROL_SILENCE] = SILENCE_BUFF
buff[CROWD_CONTROL_STUN] = STUN_BUFF
buff[CROWD_CONTROL_SLOW] = MOVEMENT_SLOW_BUFF
buff[CROWD_CONTROL_SLOW_ATTACK] = ATTACK_SLOW_BUFF
buff[CROWD_CONTROL_BANISH] = BANISH_BUFF
buff[CROWD_CONTROL_ENSNARE] = ENSNARE_BUFF
buff[CROWD_CONTROL_PURGE] = PURGE_BUFF
buff[CROWD_CONTROL_HEX] = HEX_BUFF
buff[CROWD_CONTROL_SLEEP] = SLEEP_BUFF
buff[CROWD_CONTROL_CYCLONE] = CYCLONE_BUFF
buff[CROWD_CONTROL_ENTANGLE] = ENTANGLE_BUFF
buff[CROWD_CONTROL_DISARM] = DISARM_BUFF
buff[CROWD_CONTROL_FEAR] = FEAR_BUFF
buff[CROWD_CONTROL_TAUNT] = TAUNT_BUFF
order[CROWD_CONTROL_SILENCE] = "drunkenhaze"
order[CROWD_CONTROL_STUN] = "thunderbolt"
order[CROWD_CONTROL_SLOW] = "cripple"
order[CROWD_CONTROL_SLOW_ATTACK] = "cripple"
order[CROWD_CONTROL_BANISH] = "banish"
order[CROWD_CONTROL_ENSNARE] = "ensnare"
order[CROWD_CONTROL_PURGE] = "purge"
order[CROWD_CONTROL_HEX] = "hex"
order[CROWD_CONTROL_SLEEP] = "sleep"
order[CROWD_CONTROL_CYCLONE] = "cyclone"
order[CROWD_CONTROL_ENTANGLE] = "entanglingroots"
order[CROWD_CONTROL_DISARM] = "drunkenhaze"
PauseTimer(t)
DestroyTimer(t)
end)
end)
function mt:onEvent(key)
local i = 0
local next = -1
local prev = -2
count = count + 1
if count - CROWD_CONTROL_KNOCKUP < RECURSION_LIMIT then
while CrowdControl.type[key] ~= next or (i - CROWD_CONTROL_KNOCKUP > RECURSION_LIMIT) do
next = CrowdControl.type[key]
if event[next] then
for j = 1, #event[next] do
event[next][j]()
end
end
if CrowdControl.type[key] ~= next then
i = i + 1
else
if next ~= prev then
for j = 1, #trigger do
trigger[j]()
end
if CrowdControl.type[key] ~= next then
i = i + 1
prev = next
end
end
end
end
end
count = count - 1
CrowdControl.key = key
end
function mt:cast(source, target, amount, angle, distance, height, duration, model, point, stack, onCliff, onDestructable, onUnit, type)
if not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) and UnitAlive(target) and duration > 0 then
CrowdControl.key = CrowdControl.key + 1
CrowdControl.unit[CrowdControl.key] = target
CrowdControl.source[CrowdControl.key] = source
CrowdControl.amount[CrowdControl.key] = amount
CrowdControl.angle[CrowdControl.key] = angle
CrowdControl.distance[CrowdControl.key] = distance
CrowdControl.height[CrowdControl.key] = height
CrowdControl.duration[CrowdControl.key] = duration
CrowdControl.model[CrowdControl.key] = model
CrowdControl.point[CrowdControl.key] = point
CrowdControl.stack[CrowdControl.key] = stack
CrowdControl.cliff[CrowdControl.key] = onCliff
CrowdControl.destructable[CrowdControl.key] = onDestructable
CrowdControl.agent[CrowdControl.key] = onUnit
CrowdControl.type[CrowdControl.key] = type
self:onEvent(CrowdControl.key)
if Tenacity then
CrowdControl.duration[CrowdControl.key] = GetTenacityDuration(CrowdControl.unit[CrowdControl.key], CrowdControl.duration[CrowdControl.key])
end
if CrowdControl.duration[CrowdControl.key] > 0 and UnitAlive(CrowdControl.unit[CrowdControl.key]) then
local i = CrowdControl.unit[CrowdControl.key]
local j = CrowdControl.type[CrowdControl.key]
if not timer[i] then timer[i] = {} end
if not timer[i][j] then
timer[i][j] = CreateTimer()
end
if CrowdControl.stack[CrowdControl.key] then
if CrowdControl.type[CrowdControl.key] ~= CROWD_CONTROL_TAUNT then
CrowdControl.duration[CrowdControl.key] = CrowdControl.duration[CrowdControl.key] + TimerGetRemaining(timer[i][j])
else
if Taunt.source[CrowdControl.unit[CrowdControl.key]] == CrowdControl.source[CrowdControl.key] then
CrowdControl.duration[CrowdControl.key] = CrowdControl.duration[CrowdControl.key] + TimerGetRemaining(timer[i][j])
end
end
end
if CrowdControl.type[CrowdControl.key] ~= CROWD_CONTROL_FEAR and CrowdControl.type[CrowdControl.key] ~= CROWD_CONTROL_TAUNT and CrowdControl.type[CrowdControl.key] ~= CROWD_CONTROL_KNOCKBACK and CrowdControl.type[CrowdControl.key] ~= CROWD_CONTROL_KNOCKUP then
local spell = BlzGetUnitAbility(dummy, ability[CrowdControl.type[CrowdControl.key]])
BlzUnitDisableAbility(dummy, ability[CrowdControl.type[CrowdControl.key]], false, false)
BlzSetAbilityRealLevelField(spell, ABILITY_RLF_DURATION_NORMAL, 0, CrowdControl.duration[CrowdControl.key])
BlzSetAbilityRealLevelField(spell, ABILITY_RLF_DURATION_HERO, 0, CrowdControl.duration[CrowdControl.key])
if CrowdControl.type[CrowdControl.key] == CROWD_CONTROL_SLOW then
BlzSetAbilityRealLevelField(spell, ABILITY_RLF_MOVEMENT_SPEED_REDUCTION_PERCENT_CRI1, 0, CrowdControl.amount[CrowdControl.key])
elseif CrowdControl.type[CrowdControl.key] == CROWD_CONTROL_SLOW_ATTACK then
BlzSetAbilityRealLevelField(spell, ABILITY_RLF_ATTACK_SPEED_REDUCTION_PERCENT_CRI2, 0, CrowdControl.amount[CrowdControl.key])
end
IncUnitAbilityLevel(dummy, ability[CrowdControl.type[CrowdControl.key]])
DecUnitAbilityLevel(dummy, ability[CrowdControl.type[CrowdControl.key]])
if IssueTargetOrder(dummy, order[CrowdControl.type[CrowdControl.key]], CrowdControl.unit[CrowdControl.key]) then
UnitRemoveAbility(CrowdControl.unit[CrowdControl.key], buff[CrowdControl.type[CrowdControl.key]])
IssueTargetOrder(dummy, order[CrowdControl.type[CrowdControl.key]], CrowdControl.unit[CrowdControl.key])
TimerStart(timer[i][j], CrowdControl.duration[CrowdControl.key], false, function()
PauseTimer(timer[i][j])
DestroyTimer(timer[i][j])
timer[i][j] = nil
end)
if CrowdControl.model[CrowdControl.key] then
if CrowdControl.point[CrowdControl.key] then
LinkEffectToBuff(CrowdControl.unit[CrowdControl.key], buff[CrowdControl.type[CrowdControl.key]], CrowdControl.model[CrowdControl.key], CrowdControl.point[CrowdControl.key])
else
DestroyEffect(AddSpecialEffect(CrowdControl.model[CrowdControl.key], GetUnitX(CrowdControl.unit[CrowdControl.key]), GetUnitY(CrowdControl.unit[CrowdControl.key])))
end
end
else
PauseTimer(timer[i][j])
DestroyTimer(timer[i][j])
timer[i][j] = nil
end
BlzUnitDisableAbility(dummy, ability[CrowdControl.type[CrowdControl.key]], true, true)
else
if CrowdControl.type[CrowdControl.key] == CROWD_CONTROL_FEAR then
Fear:apply(CrowdControl.unit[CrowdControl.key], CrowdControl.duration[CrowdControl.key], CrowdControl.model[CrowdControl.key], CrowdControl.point[CrowdControl.key])
elseif CrowdControl.type[CrowdControl.key] == CROWD_CONTROL_TAUNT then
Taunt:apply(CrowdControl.source[CrowdControl.key], CrowdControl.unit[CrowdControl.key], CrowdControl.duration[CrowdControl.key], CrowdControl.model[CrowdControl.key], CrowdControl.point[CrowdControl.key])
elseif CrowdControl.type[CrowdControl.key] == CROWD_CONTROL_KNOCKBACK then
Knockback:apply(CrowdControl.unit[CrowdControl.key], CrowdControl.angle[CrowdControl.key], CrowdControl.distance[CrowdControl.key], CrowdControl.duration[CrowdControl.key], CrowdControl.model[CrowdControl.key], CrowdControl.point[CrowdControl.key], CrowdControl.cliff[CrowdControl.key], CrowdControl.destructable[CrowdControl.key], CrowdControl.agent[CrowdControl.key])
elseif CrowdControl.type[CrowdControl.key] == CROWD_CONTROL_KNOCKUP then
Knockup:apply(CrowdControl.unit[CrowdControl.key], CrowdControl.duration[CrowdControl.key], CrowdControl.height[CrowdControl.key], CrowdControl.model[CrowdControl.key], CrowdControl.point[CrowdControl.key])
end
TimerStart(timer[i][j], CrowdControl.duration[CrowdControl.key], false, function()
PauseTimer(timer[i][j])
DestroyTimer(timer[i][j])
timer[i][j] = nil
end)
end
end
if CrowdControl.key > 0 then
CrowdControl.key = CrowdControl.key - 1
end
end
end
function mt:silence(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_SILENCE)
end
function mt:silenced(unit)
return GetUnitAbilityLevel(unit, SILENCE_BUFF) > 0
end
function mt:stun(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_STUN)
end
function mt:stunned(unit)
return GetUnitAbilityLevel(unit, STUN_BUFF) > 0
end
function mt:slow(unit, amount, duration, model, point, stack)
self:cast(nil, unit, amount, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_SLOW)
end
function mt:slowed(unit)
return GetUnitAbilityLevel(unit, MOVEMENT_SLOW_BUFF) > 0
end
function mt:slowAttack(unit, amount, duration, model, point, stack)
self:cast(nil, unit, amount, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_SLOW_ATTACK)
end
function mt:attackSlowed(unit)
return GetUnitAbilityLevel(unit, ATTACK_SLOW_BUFF) > 0
end
function mt:banish(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_BANISH)
end
function mt:banished(unit)
return GetUnitAbilityLevel(unit, BANISH_BUFF) > 0
end
function mt:ensnare(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_ENSNARE)
end
function mt:ensnared(unit)
return GetUnitAbilityLevel(unit, ENSNARE_BUFF) > 0
end
function mt:purge(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_PURGE)
end
function mt:purged(unit)
return GetUnitAbilityLevel(unit, PURGE_BUFF) > 0
end
function mt:hex(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_HEX)
end
function mt:hexed(unit)
return GetUnitAbilityLevel(unit, HEX_BUFF) > 0
end
function mt:sleep(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_SLEEP)
end
function mt:sleeping(unit)
return GetUnitAbilityLevel(unit, SLEEP_BUFF) > 0
end
function mt:cyclone(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_CYCLONE)
end
function mt:cycloned(unit)
return GetUnitAbilityLevel(unit, CYCLONE_BUFF) > 0
end
function mt:entangle(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_ENTANGLE)
end
function mt:entangled(unit)
return GetUnitAbilityLevel(unit, ENTANGLE_BUFF) > 0
end
function mt:knockback(unit, angle, distance, duration, model, point, onCliff, onDestructable, onUnit, stack)
self:cast(nil, unit, 0, angle, distance, 0, duration, model, point, stack, onCliff, onDestructable, onUnit, CROWD_CONTROL_KNOCKBACK)
end
function mt:knockedback(unit)
return Knockback:knocked(unit)
end
function mt:knockup(unit, height, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, height, duration, model, point, stack, false, false, false, CROWD_CONTROL_KNOCKUP)
end
function mt:knockedup(unit)
return Knockup:isUnitKnocked(unit)
end
function mt:fear(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_FEAR)
end
function mt:feared(unit)
return Fear:feared(unit)
end
function mt:disarm(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_DISARM)
end
function mt:disarmed(unit)
return GetUnitAbilityLevel(unit, DISARM_BUFF) > 0
end
function mt:taunt(source, target, duration, model, point, stack)
self:cast(source, target, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_TAUNT)
end
function mt:taunted(unit)
return Taunt:taunted(unit)
end
function mt:dispel(unit, type)
if buff[type] then
UnitRemoveAbility(unit, buff[type])
if timer[unit] then
PauseTimer(timer[unit][type])
DestroyTimer(timer[unit][type])
timer[unit][type] = nil
end
end
end
function mt:dispelAll(unit)
self:dispel(unit, CROWD_CONTROL_SILENCE)
self:dispel(unit, CROWD_CONTROL_STUN)
self:dispel(unit, CROWD_CONTROL_SLOW)
self:dispel(unit, CROWD_CONTROL_SLOW_ATTACK)
self:dispel(unit, CROWD_CONTROL_BANISH)
self:dispel(unit, CROWD_CONTROL_ENSNARE)
self:dispel(unit, CROWD_CONTROL_PURGE)
self:dispel(unit, CROWD_CONTROL_HEX)
self:dispel(unit, CROWD_CONTROL_SLEEP)
self:dispel(unit, CROWD_CONTROL_CYCLONE)
self:dispel(unit, CROWD_CONTROL_ENTANGLE)
self:dispel(unit, CROWD_CONTROL_DISARM)
self:dispel(unit, CROWD_CONTROL_FEAR)
self:dispel(unit, CROWD_CONTROL_TAUNT)
end
function mt:remaining(unit, type)
if not timer[unit] then
return 0
else
return TimerGetRemaining(timer[unit][type])
end
end
function mt:register(id, code)
if type(code) == "function" then
if id >= CROWD_CONTROL_SILENCE and id <= CROWD_CONTROL_KNOCKUP then
if not event[id] then event[id] = {} end
table.insert(event[id], code)
else
table.insert(trigger, code)
end
end
end
end
end
if Debug and Debug.beginFile then Debug.beginFile("CrowdControl") end
--[[ requires Utilities, WorldBounds, Indexer, TimerUtils, RegisterPlayerUnitEvent, optional Tenacity
-- ------------------------------------- Crowd Control v1.0 ------------------------------------- --
-- How to Import:
-- 1 - Copy the Utilities library over to your map and follow its install instructions
-- 2 - Copy the WorldBounds library over to your map and follow its install instructions
-- 3 - Copy the Indexer library over to your map and follow its install instructions
-- 4 - Copy the RegisterPlayerUnitEvent library over to your map and follow its install instructions
-- 5 - Copy the Tenacity library over to your map and follow its install instructions
-- 6 - Copy this library into your map
-- 7 - Copy the 14 buffs and 15 abilities with the CC prefix and match their raw code below.
-- ---------------------------------------- By Chopinski ---------------------------------------- --
]]--
OnInit.main(function()
-- ---------------------------------------------------------------------------------------------- --
-- Configuration --
-- ---------------------------------------------------------------------------------------------- --
-- The raw code of the silence ability
local SILENCE = FourCC('U000')
-- The raw code of the stun ability
local STUN = FourCC('U001')
-- The raw code of the attack slow ability
local ATTACK_SLOW = FourCC('U002')
-- The raw code of the movement slow ability
local MOVEMENT_SLOW = FourCC('U003')
-- The raw code of the banish ability
local BANISH = FourCC('U004')
-- The raw code of the ensnare ability
local ENSNARE = FourCC('U005')
-- The raw code of the purge ability
local PURGE = FourCC('U006')
-- The raw code of the hex ability
local HEX = FourCC('U007')
-- The raw code of the sleep ability
local SLEEP = FourCC('U008')
-- The raw code of the cyclone ability
local CYCLONE = FourCC('U009')
-- The raw code of the entangle ability
local ENTANGLE = FourCC('U010')
-- The raw code of the disarm ability
local DISARM = FourCC('U011')
-- The raw code of the fear ability
local FEAR = FourCC('U012')
-- The raw code of the taunt ability
local TAUNT = FourCC('U013')
-- The raw code of the true sight ability
local TRUE_SIGHT = FourCC('U014')
-- The raw code of the silence buff
local SILENCE_BUFF = FourCC('BU00')
-- The raw code of the stun buff
local STUN_BUFF = FourCC('BU01')
-- The raw code of the attack slow buff
local ATTACK_SLOW_BUFF = FourCC('BU02')
-- The raw code of the movement slow buff
local MOVEMENT_SLOW_BUFF = FourCC('BU03')
-- The raw code of the banish buff
local BANISH_BUFF = FourCC('BU04')
-- The raw code of the ensnare buff
local ENSNARE_BUFF = FourCC('BU05')
-- The raw code of the purge buff
local PURGE_BUFF = FourCC('BU06')
-- The raw code of the hex buff
local HEX_BUFF = FourCC('BU07')
-- The raw code of the sleep buff
local SLEEP_BUFF = FourCC('BU08')
-- The raw code of the cyclone buff
local CYCLONE_BUFF = FourCC('BU09')
-- The raw code of the entangle buff
local ENTANGLE_BUFF = FourCC('BU10')
-- The raw code of the disarm buff
local DISARM_BUFF = FourCC('BU11')
-- The raw code of the fear buff
local FEAR_BUFF = FourCC('BU12')
-- The raw code of the taunt buff
local TAUNT_BUFF = FourCC('BU13')
-- This is the maximum recursion limit allowed by the system.
-- Its value must be greater than or equal to 0. When equal to 0
-- no recursion is allowed. Values too big can cause screen freezes.
local RECURSION_LIMIT = 8
-- The Crowd Control types
CROWD_CONTROL_SILENCE = 0
CROWD_CONTROL_STUN = 1
CROWD_CONTROL_SLOW = 2
CROWD_CONTROL_SLOW_ATTACK = 3
CROWD_CONTROL_BANISH = 4
CROWD_CONTROL_ENSNARE = 5
CROWD_CONTROL_PURGE = 6
CROWD_CONTROL_HEX = 7
CROWD_CONTROL_SLEEP = 8
CROWD_CONTROL_CYCLONE = 9
CROWD_CONTROL_ENTANGLE = 10
CROWD_CONTROL_DISARM = 11
CROWD_CONTROL_FEAR = 12
CROWD_CONTROL_TAUNT = 13
CROWD_CONTROL_KNOCKBACK = 14
CROWD_CONTROL_KNOCKUP = 15
-- ---------------------------------------------------------------------------------------------- --
-- LUA API --
-- ---------------------------------------------------------------------------------------------- --
-- ------------------------------------------- Disarm ------------------------------------------- --
function DisarmUnit(unit, duration, model, point, stack)
CrowdControl:disarm(unit, duration, model, point, stack)
end
function IsUnitDisarmed(unit)
return CrowdControl:disarmed(unit)
end
-- -------------------------------------------- Fear -------------------------------------------- --
function FearUnit(unit, duration, model, point, stack)
CrowdControl:fear(unit, duration, model, point, stack)
end
function IsUnitFeared(unit)
return CrowdControl:feared(unit)
end
-- -------------------------------------------- Taunt ------------------------------------------- --
function TauntUnit(source, target, duration, model, point, stack)
CrowdControl:taunt(source, target, duration, model, point, stack)
end
function IsUnitTaunted(unit)
return CrowdControl:taunted(unit)
end
-- ------------------------------------------ Knockback ----------------------------------------- --
function KnockbackUnit(unit, angle, distance, duration, model, point, onCliff, onDestructable, onUnit, stack)
CrowdControl:knockback(unit, angle, distance, duration, model, point, onCliff, onDestructable, onUnit, stack)
end
function IsUnitKnockedBack(unit)
return CrowdControl:knockedback(unit)
end
-- ------------------------------------------- Knockup ------------------------------------------ --
function KnockupUnit(unit, height, duration, model, point, stack)
CrowdControl:knockup(unit, height, duration, model, point, stack)
end
function IsUnitKnockedUp(unit)
return CrowdControl:knockedup(unit)
end
-- ------------------------------------------- Silence ------------------------------------------ --
function SilenceUnit(unit, duration, model, point, stack)
CrowdControl:silence(unit, duration, model, point, stack)
end
function IsUnitSilenced(unit)
return CrowdControl:silenced(unit)
end
-- -------------------------------------------- Stun -------------------------------------------- --
function StunUnit(unit, duration, model, point, stack)
CrowdControl:stun(unit, duration, model, point, stack)
end
function IsUnitStunned(unit)
return CrowdControl:stunned(unit)
end
-- ---------------------------------------- Movement Slow --------------------------------------- --
function SlowUnit(unit, amount, duration, model, point, stack)
CrowdControl:slow(unit, amount, duration, model, point, stack)
end
function IsUnitSlowed(unit)
return CrowdControl:slowed(unit)
end
-- ----------------------------------------- Attack Slow ---------------------------------------- --
function SlowUnitAttack(unit, amount, duration, model, point, stack)
CrowdControl:slowAttack(unit, amount, duration, model, point, stack)
end
function IsUnitAttackSlowed(unit)
return CrowdControl:attackSlowed(unit)
end
-- ------------------------------------------- Banish ------------------------------------------- --
function BanishUnit(unit, duration, model, point, stack)
CrowdControl:banish(unit, duration, model, point, stack)
end
function IsUnitBanished(unit)
return CrowdControl:banished(unit)
end
-- ------------------------------------------- Ensnare ------------------------------------------ --
function EnsnareUnit(unit, duration, model, point, stack)
CrowdControl:ensnare(unit, duration, model, point, stack)
end
function IsUnitEnsnared(unit)
return CrowdControl:ensnared(unit)
end
-- -------------------------------------------- Purge ------------------------------------------- --
function PurgeUnit(unit, duration, model, point, stack)
CrowdControl:purge(unit, duration, model, point, stack)
end
function IsUnitPurged(unit)
return CrowdControl:purged(unit)
end
-- --------------------------------------------- Hex -------------------------------------------- --
function HexUnit(unit, duration, model, point, stack)
CrowdControl:hex(unit, duration, model, point, stack)
end
function IsUnitHexed(unit)
return CrowdControl:hexed(unit)
end
-- -------------------------------------------- Sleep ------------------------------------------- --
function SleepUnit(unit, duration, model, point, stack)
CrowdControl:sleep(unit, duration, model, point, stack)
end
function IsUnitSleeping(unit)
return CrowdControl:sleeping(unit)
end
-- ------------------------------------------- Cyclone ------------------------------------------ --
function CycloneUnit(unit, duration, model, point, stack)
CrowdControl:cyclone(unit, duration, model, point, stack)
end
function IsUnitCycloned(unit)
return CrowdControl:cycloned(unit)
end
-- ------------------------------------------ Entangle ------------------------------------------ --
function EntangleUnit(unit, duration, model, point, stack)
CrowdControl:entangle(unit, duration, model, point, stack)
end
function IsUnitEntangled(unit)
return CrowdControl:entangled(unit)
end
-- ------------------------------------------- Dispel ------------------------------------------- --
function UnitDispelCrowdControl(unit, type)
CrowdControl:dispel(unit, type)
end
function UnitDispelAllCrowdControl(unit)
CrowdControl:dispelAll(unit)
end
-- ------------------------------------------- Events ------------------------------------------- --
function RegisterCrowdControlEvent(type, code)
CrowdControl:register(type, code)
end
function RegisterAnyCrowdControlEvent(code)
CrowdControl:register(-1, code)
end
function GetCrowdControlUnit()
return CrowdControl.unit[CrowdControl.key]
end
function GetCrowdControlType()
return CrowdControl.type[CrowdControl.key]
end
function GetCrowdControlDuration()
return CrowdControl.duration[CrowdControl.key]
end
function GetCrowdControlAmount()
return CrowdControl.amount[CrowdControl.key]
end
function GetCrowdControlModel()
return CrowdControl.model[CrowdControl.key]
end
function GetCrowdControlBone()
return CrowdControl.point[CrowdControl.key]
end
function GetCrowdControlStack()
return CrowdControl.stack[CrowdControl.key]
end
function GetCrowdControlRemaining(unit, type)
return CrowdControl:remaining(unit, type)
end
function GetTauntSource()
return CrowdControl.source[CrowdControl.key]
end
function GetKnockbackAngle()
return CrowdControl.angle[CrowdControl.key]
end
function GetKnockbackDistance()
return CrowdControl.distance[CrowdControl.key]
end
function GetKnockupHeight()
return CrowdControl.height[CrowdControl.key]
end
function GetKnockbackOnCliff()
return CrowdControl.cliff[CrowdControl.key]
end
function GetKnockbackOnDestructable()
return CrowdControl.destructable[CrowdControl.key]
end
function GetKnockbackOnUnit()
return CrowdControl.agent[CrowdControl.key]
end
function SetCrowdControlUnit(unit)
CrowdControl.unit[CrowdControl.key] = unit
end
function SetCrowdControlType(type)
if type >= CROWD_CONTROL_SILENCE and type <= CROWD_CONTROL_KNOCKUP then
CrowdControl.type[CrowdControl.key] = type
end
end
function SetCrowdControlDuration(duration)
CrowdControl.duration[CrowdControl.key] = duration
end
function SetCrowdControlAmount(amount)
CrowdControl.amount[CrowdControl.key] = amount
end
function SetCrowdControlModel(model)
CrowdControl.model[CrowdControl.key] = model
end
function SetCrowdControlBone(point)
CrowdControl.point[CrowdControl.key] = point
end
function SetCrowdControlStack(stack)
CrowdControl.stack[CrowdControl.key] = stack
end
function SetTauntSource(unit)
CrowdControl.source[CrowdControl.key] = unit
end
function SetKnockbackAngle(angle)
CrowdControl.angle[CrowdControl.key] = angle
end
function SetKnockbackDistance(distance)
CrowdControl.distance[CrowdControl.key] = distance
end
function SetKnockupHeight(height)
CrowdControl.height[CrowdControl.key] = height
end
function SetKnockbackOnCliff(onCliff)
CrowdControl.cliff[CrowdControl.key] = onCliff
end
function SetKnockbackOnDestructable(onDestructable)
CrowdControl.destructable[CrowdControl.key] = onDestructable
end
function SetKnockbackOnUnit(onUnit)
CrowdControl.agent[CrowdControl.key] = onUnit
end
-- ---------------------------------------------------------------------------------------------- --
-- Systems --
-- ---------------------------------------------------------------------------------------------- --
-- ------------------------------------------ Knockback ----------------------------------------- --
do
Knockback = setmetatable({}, {})
local mt = getmetatable(Knockback)
mt.__index = mt
local timer = CreateTimer()
local rect = Rect(0., 0., 0., 0.)
local period = 0.03125
local array = {}
local struct = {}
local key = 0
function mt:destroy(i)
DestroyGroup(self.group)
DestroyEffect(self.effect)
BlzPauseUnitEx(self.unit, false)
struct[self.unit] = nil
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:knocked(unit)
return struct[unit] ~= nil
end
function mt:apply(target, angle, distance, duration, model, point, cliff, dest, hit)
local this
if duration > 0 and UnitAlive(target) then
if struct[target] then
this = struct[target]
else
this = {}
setmetatable(this, mt)
this.unit = target
this.collision = 2*BlzGetUnitCollisionSize(target)
this.group = CreateGroup()
key = key + 1
array[key] = this
struct[target] = this
BlzPauseUnitEx(target, true)
if model and point then
this.effect = AddSpecialEffectTarget(model, target, point)
end
if key == 1 then
TimerStart(timer, period, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.duration > 0 and UnitAlive(this.unit) then
local x = GetUnitX(this.unit) + this.offset*Cos(this.angle)
local y = GetUnitY(this.unit) + this.offset*Sin(this.angle)
this.duration = this.duration - period
if this.onUnit and this.collision > 0 then
GroupEnumUnitsInRange(this.group, x, y, this.collision, nil)
GroupRemoveUnit(this.group, this.unit)
for j = 0, BlzGroupGetSize(this.group) - 1 do
if UnitAlive(BlzGroupUnitAt(this.group, j)) then
this.duration = 0
break
end
end
end
if this.onDestructable and this.duration > 0 and this.collision > 0 then
SetRect(rect, x - this.collision, y - this.collision, x + this.collision, y + this.collision)
EnumDestructablesInRect(rect, nil, function()
if GetDestructableLife(GetEnumDestructable()) > 0 then
this.duration = 0
return
end
end)
end
if this.onCliff and this.duration > 0 then
if GetTerrainCliffLevel(GetUnitX(this.unit), GetUnitY(this.unit)) < GetTerrainCliffLevel(x, y) and GetUnitZ(this.unit) < (GetTerrainCliffLevel(x, y) - GetTerrainCliffLevel(WorldBounds.maxX, WorldBounds.maxY))*bj_CLIFFHEIGHT then
this.duration = 0
end
end
if this.duration > 0 then
SetUnitX(this.unit, x)
SetUnitY(this.unit, y)
end
else
i = this:destroy(i)
end
i = i + 1
end
end)
end
end
this.angle = angle
this.distance = distance
this.duration = duration
this.onCliff = cliff
this.onDestructable = dest
this.onUnit = hit
this.offset = RMaxBJ(0.00000001, distance*period/RMaxBJ(0.00000001, duration))
end
end
end
-- ------------------------------------------- Knockup ------------------------------------------ --
do
Knockup = setmetatable({}, {})
local mt = getmetatable(Knockup)
mt.__index = mt
local knocked = {}
function mt:isUnitKnocked(unit)
return (knocked[unit] or 0) > 0
end
function mt:apply(unit, duration, height, model, point)
if duration > 0 then
local timer = CreateTimer()
local rate = height/duration
local effect
knocked[unit] = (knocked[unit] or 0) + 1
if model and point then
effect = AddSpecialEffect(model, unit, point)
end
if knocked[unit] == 1 then
BlzPauseUnitEx(unit, true)
end
UnitAddAbility(unit, FourCC('Amrf'))
UnitRemoveAbility(unit, FourCC('Amrf'))
SetUnitFlyHeight(unit, (GetUnitDefaultFlyHeight(unit) + height), rate)
TimerStart(timer, duration/2, false, function()
SetUnitFlyHeight(unit, GetUnitDefaultFlyHeight(unit), rate)
TimerStart(timer, duration/2, false, function()
DestroyEffect(effect)
PauseTimer(timer)
DestroyTimer(timer)
knocked[unit] = knocked[unit] - 1
if knocked[unit] == 0 then
BlzPauseUnitEx(unit, false)
end
end)
end)
end
end
end
-- -------------------------------------------- Fear -------------------------------------------- --
do
Fear = setmetatable({}, {})
local mt = getmetatable(Fear)
mt.__index = mt
local timer = CreateTimer()
local array = {}
local struct = {}
local flag = {}
local x = {}
local y = {}
local key = 0
local UPDATE = 0.2
local DIRECTION_CHANGE = 5
local MAX_CHANGE = 200.
local dummy
local ability
function mt:feared(unit)
return GetUnitAbilityLevel(unit, FEAR_BUFF) > 0
end
function mt:destroy(i)
flag[self.target] = true
IssueImmediateOrder(self.target, "stop")
DestroyEffect(self.effect)
array[i] = array[key]
key = key - 1
struct[self.target] = nil
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:apply(target, duration, effect, attach)
local this
if duration > 0 then
BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_NORMAL, 0, duration)
BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_HERO, 0, duration)
IncUnitAbilityLevel(dummy, FEAR)
DecUnitAbilityLevel(dummy, FEAR)
if IssueTargetOrder(dummy, "drunkenhaze", target) then
if struct[target] then
this = struct[target]
else
this = {}
setmetatable(this, mt)
key = key + 1
array[key] = this
struct[target] = this
this.target = target
this.change = 0
if effect and attach then
this.effect = AddSpecialEffectTarget(effect, target, attach)
end
if key == 1 then
TimerStart(timer, UPDATE, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if GetUnitAbilityLevel(this.target, FEAR_BUFF) > 0 then
this.change = this.change + 1
if this.change == DIRECTION_CHANGE then
this.change = 0
flag[this.target] = true
x[this.target] = GetRandomReal(GetUnitX(this.target) - MAX_CHANGE, GetUnitX(this.target) + MAX_CHANGE)
y[this.target] = GetRandomReal(GetUnitY(this.target) - MAX_CHANGE, GetUnitY(this.target) + MAX_CHANGE)
IssuePointOrder(this.target, "move", x[this.target], y[this.target])
end
else
i = this:destroy(i)
end
i = i + 1
end
end)
end
end
flag[target] = true
x[target] = GetRandomReal(GetUnitX(target) - MAX_CHANGE, GetUnitX(target) + MAX_CHANGE)
y[target] = GetRandomReal(GetUnitY(target) - MAX_CHANGE, GetUnitY(target) + MAX_CHANGE)
IssuePointOrder(target, "move", x[target], y[target])
end
end
end
function mt:onOrder()
local unit = GetOrderedUnit()
if self:feared(unit) and GetIssuedOrderId() ~= 851973 then
if not flag[unit] then
flag[unit] = true
IssuePointOrder(unit, "move", x[unit], y[unit])
else
flag[unit] = false
end
end
end
OnInit.global(function()
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function()
Fear:onOrder()
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function()
Fear:onOrder()
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function()
Fear:onOrder()
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_UNIT_ORDER, function()
Fear:onOrder()
end)
TimerStart(CreateTimer(), 0, false, function()
dummy = DummyRetrieve(Player(PLAYER_NEUTRAL_PASSIVE), GetRectCenterX(GetWorldBounds()), GetRectCenterY(GetWorldBounds()), 0, 0)
UnitAddAbility(dummy, TRUE_SIGHT)
UnitAddAbility(dummy, FEAR)
PauseTimer(GetExpiredTimer())
DestroyTimer(GetExpiredTimer())
ability = BlzGetUnitAbility(dummy, FEAR)
end)
end)
end
-- -------------------------------------------- Taunt ------------------------------------------- --
do
Taunt = setmetatable({}, {})
local mt = getmetatable(Taunt)
mt.__index = mt
Taunt.source = {}
local timer = CreateTimer()
local PERIOD = 0.2
local key = 0
local array = {}
local struct = {}
local dummy
local ability
function mt:taunted(unit)
return GetUnitAbilityLevel(unit, TAUNT_BUFF) > 0
end
function mt:destroy(i)
UnitDispelCrowdControl(self.target, CROWD_CONTROL_TAUNT)
IssueImmediateOrder(self.target, "stop")
DestroyEffect(self.effect)
if self.selected then
SelectUnitAddForPlayer(self.target, GetOwningPlayer(self.target))
end
array[i] = array[key]
key = key - 1
struct[self.target] = nil
Taunt.source[self.target] = nil
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:apply(source, target, duration, model, point)
local this
if duration > 0 and UnitAlive(source) and UnitAlive(target) then
BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_NORMAL, 0, duration)
BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_HERO, 0, duration)
IncUnitAbilityLevel(dummy, TAUNT)
DecUnitAbilityLevel(dummy, TAUNT)
if IssueTargetOrder(dummy, "drunkenhaze", target) then
if struct[target] then
this = struct[target]
else
this = {}
setmetatable(this, mt)
key = key + 1
array[key] = this
struct[target] = this
this.target = target
this.selected = IsUnitSelected(target, GetOwningPlayer(target))
if this.selected then
SelectUnit(target, false)
end
if model and point then
this.effect = AddSpecialEffectTarget(model, target, point)
end
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if GetUnitAbilityLevel(this.target, TAUNT_BUFF) > 0 and UnitAlive(Taunt.source[this.target]) and UnitAlive(this.target) then
if IsUnitVisible(Taunt.source[this.target], GetOwningPlayer(this.target)) then
IssueTargetOrderById(this.target, 851983, Taunt.source[this.target])
else
IssuePointOrderById(this.target, 851986, GetUnitX(Taunt.source[this.target]), GetUnitY(Taunt.source[this.target]))
end
else
i = this:destroy(i)
end
i = i + 1
end
end)
end
end
Taunt.source[target] = source
if IsUnitVisible(source, GetOwningPlayer(target)) then
IssueTargetOrderById(target, 851983, source)
else
IssuePointOrderById(target, 851986, GetUnitX(source), GetUnitY(source))
end
end
end
end
function mt:onOrder()
local unit = GetOrderedUnit()
local order = GetIssuedOrderId()
if self:taunted(unit) and order ~= 851973 then
if order ~= 851983 and order ~= 851986 then
if IsUnitVisible(Taunt.source[unit], GetOwningPlayer(unit)) then
IssueTargetOrderById(unit, 851983, Taunt.source[unit])
else
IssuePointOrderById(unit, 851986, GetUnitX(Taunt.source[unit]), GetUnitY(Taunt.source[unit]))
end
else
if GetOrderTargetUnit() ~= Taunt.source[unit] and GetOrderTargetUnit() ~= nil then
if IsUnitVisible(source[id], GetOwningPlayer(target)) then
IssueTargetOrderById(unit, 851983, Taunt.source[unit])
else
IssuePointOrderById(unit, 851986, GetUnitX(Taunt.source[unit]), GetUnitY(Taunt.source[unit]))
end
end
end
end
end
function mt:onSelect()
local unit = GetTriggerUnit()
if self:taunted(unit) then
if IsUnitSelected(unit, GetOwningPlayer(unit)) then
SelectUnit(unit, false)
end
end
end
OnInit.global(function()
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function()
Taunt:onOrder()
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function()
Taunt:onOrder()
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function()
Taunt:onOrder()
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_UNIT_ORDER, function()
Taunt:onOrder()
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SELECTED, function()
Taunt:onSelect()
end)
TimerStart(CreateTimer(), 0, false, function()
dummy = DummyRetrieve(Player(PLAYER_NEUTRAL_PASSIVE), GetRectCenterX(GetWorldBounds()), GetRectCenterY(GetWorldBounds()), 0, 0)
UnitAddAbility(dummy, TRUE_SIGHT)
UnitAddAbility(dummy, TAUNT)
PauseTimer(GetExpiredTimer())
DestroyTimer(GetExpiredTimer())
ability = BlzGetUnitAbility(dummy, TAUNT)
end)
end)
end
-- ---------------------------------------- Crowd Control --------------------------------------- --
do
CrowdControl = setmetatable({}, {})
local mt = getmetatable(CrowdControl)
mt.__index = mt
CrowdControl.key = 0
CrowdControl.unit = {}
CrowdControl.source = {}
CrowdControl.amount = {}
CrowdControl.duration = {}
CrowdControl.angle = {}
CrowdControl.distance = {}
CrowdControl.height = {}
CrowdControl.model = {}
CrowdControl.point = {}
CrowdControl.stack = {}
CrowdControl.cliff = {}
CrowdControl.destructable = {}
CrowdControl.agent = {}
CrowdControl.type = {}
local trigger = {}
local timer = {}
local event = {}
local ability = {}
local buff = {}
local order = {}
local dummy
local count = 0
OnInit.global(function()
local t = CreateTimer()
TimerStart(t, 0, false, function()
dummy = DummyRetrieve(Player(PLAYER_NEUTRAL_PASSIVE), GetRectCenterX(GetWorldBounds()), GetRectCenterY(GetWorldBounds()), 0, 0)
UnitAddAbility(dummy, SILENCE)
UnitAddAbility(dummy, STUN)
UnitAddAbility(dummy, ATTACK_SLOW)
UnitAddAbility(dummy, MOVEMENT_SLOW)
UnitAddAbility(dummy, BANISH)
UnitAddAbility(dummy, ENSNARE)
UnitAddAbility(dummy, PURGE)
UnitAddAbility(dummy, HEX)
UnitAddAbility(dummy, SLEEP)
UnitAddAbility(dummy, CYCLONE)
UnitAddAbility(dummy, ENTANGLE)
UnitAddAbility(dummy, DISARM)
UnitAddAbility(dummy, TRUE_SIGHT)
BlzUnitDisableAbility(dummy, SILENCE, true, true)
BlzUnitDisableAbility(dummy, STUN, true, true)
BlzUnitDisableAbility(dummy, ATTACK_SLOW, true, true)
BlzUnitDisableAbility(dummy, MOVEMENT_SLOW, true, true)
BlzUnitDisableAbility(dummy, BANISH, true, true)
BlzUnitDisableAbility(dummy, ENSNARE, true, true)
BlzUnitDisableAbility(dummy, PURGE, true, true)
BlzUnitDisableAbility(dummy, HEX, true, true)
BlzUnitDisableAbility(dummy, SLEEP, true, true)
BlzUnitDisableAbility(dummy, CYCLONE, true, true)
BlzUnitDisableAbility(dummy, ENTANGLE, true, true)
BlzUnitDisableAbility(dummy, DISARM, true, true)
ability[CROWD_CONTROL_SILENCE] = SILENCE
ability[CROWD_CONTROL_STUN] = STUN
ability[CROWD_CONTROL_SLOW] = MOVEMENT_SLOW
ability[CROWD_CONTROL_SLOW_ATTACK] = ATTACK_SLOW
ability[CROWD_CONTROL_BANISH] = BANISH
ability[CROWD_CONTROL_ENSNARE] = ENSNARE
ability[CROWD_CONTROL_PURGE] = PURGE
ability[CROWD_CONTROL_HEX] = HEX
ability[CROWD_CONTROL_SLEEP] = SLEEP
ability[CROWD_CONTROL_CYCLONE] = CYCLONE
ability[CROWD_CONTROL_ENTANGLE] = ENTANGLE
ability[CROWD_CONTROL_DISARM] = DISARM
ability[CROWD_CONTROL_FEAR] = FEAR
ability[CROWD_CONTROL_TAUNT] = TAUNT
buff[CROWD_CONTROL_SILENCE] = SILENCE_BUFF
buff[CROWD_CONTROL_STUN] = STUN_BUFF
buff[CROWD_CONTROL_SLOW] = MOVEMENT_SLOW_BUFF
buff[CROWD_CONTROL_SLOW_ATTACK] = ATTACK_SLOW_BUFF
buff[CROWD_CONTROL_BANISH] = BANISH_BUFF
buff[CROWD_CONTROL_ENSNARE] = ENSNARE_BUFF
buff[CROWD_CONTROL_PURGE] = PURGE_BUFF
buff[CROWD_CONTROL_HEX] = HEX_BUFF
buff[CROWD_CONTROL_SLEEP] = SLEEP_BUFF
buff[CROWD_CONTROL_CYCLONE] = CYCLONE_BUFF
buff[CROWD_CONTROL_ENTANGLE] = ENTANGLE_BUFF
buff[CROWD_CONTROL_DISARM] = DISARM_BUFF
buff[CROWD_CONTROL_FEAR] = FEAR_BUFF
buff[CROWD_CONTROL_TAUNT] = TAUNT_BUFF
order[CROWD_CONTROL_SILENCE] = "drunkenhaze"
order[CROWD_CONTROL_STUN] = "thunderbolt"
order[CROWD_CONTROL_SLOW] = "cripple"
order[CROWD_CONTROL_SLOW_ATTACK] = "cripple"
order[CROWD_CONTROL_BANISH] = "banish"
order[CROWD_CONTROL_ENSNARE] = "ensnare"
order[CROWD_CONTROL_PURGE] = "purge"
order[CROWD_CONTROL_HEX] = "hex"
order[CROWD_CONTROL_SLEEP] = "sleep"
order[CROWD_CONTROL_CYCLONE] = "cyclone"
order[CROWD_CONTROL_ENTANGLE] = "entanglingroots"
order[CROWD_CONTROL_DISARM] = "drunkenhaze"
PauseTimer(t)
DestroyTimer(t)
end)
end)
function mt:onEvent(key)
local i = 0
local next = -1
local prev = -2
count = count + 1
if count - CROWD_CONTROL_KNOCKUP < RECURSION_LIMIT then
while CrowdControl.type[key] ~= next or (i - CROWD_CONTROL_KNOCKUP > RECURSION_LIMIT) do
next = CrowdControl.type[key]
if event[next] then
for j = 1, #event[next] do
event[next][j]()
end
end
if CrowdControl.type[key] ~= next then
i = i + 1
else
if next ~= prev then
for j = 1, #trigger do
trigger[j]()
end
if CrowdControl.type[key] ~= next then
i = i + 1
prev = next
end
end
end
end
end
count = count - 1
CrowdControl.key = key
end
function mt:cast(source, target, amount, angle, distance, height, duration, model, point, stack, onCliff, onDestructable, onUnit, type)
if not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) and UnitAlive(target) and duration > 0 then
CrowdControl.key = CrowdControl.key + 1
CrowdControl.unit[CrowdControl.key] = target
CrowdControl.source[CrowdControl.key] = source
CrowdControl.amount[CrowdControl.key] = amount
CrowdControl.angle[CrowdControl.key] = angle
CrowdControl.distance[CrowdControl.key] = distance
CrowdControl.height[CrowdControl.key] = height
CrowdControl.duration[CrowdControl.key] = duration
CrowdControl.model[CrowdControl.key] = model
CrowdControl.point[CrowdControl.key] = point
CrowdControl.stack[CrowdControl.key] = stack
CrowdControl.cliff[CrowdControl.key] = onCliff
CrowdControl.destructable[CrowdControl.key] = onDestructable
CrowdControl.agent[CrowdControl.key] = onUnit
CrowdControl.type[CrowdControl.key] = type
self:onEvent(CrowdControl.key)
if Tenacity then
CrowdControl.duration[CrowdControl.key] = GetTenacityDuration(CrowdControl.unit[CrowdControl.key], CrowdControl.duration[CrowdControl.key])
end
if CrowdControl.duration[CrowdControl.key] > 0 and UnitAlive(CrowdControl.unit[CrowdControl.key]) then
local i = CrowdControl.unit[CrowdControl.key]
local j = CrowdControl.type[CrowdControl.key]
if not timer[i] then timer[i] = {} end
if not timer[i][j] then
timer[i][j] = CreateTimer()
end
if CrowdControl.stack[CrowdControl.key] then
if CrowdControl.type[CrowdControl.key] ~= CROWD_CONTROL_TAUNT then
CrowdControl.duration[CrowdControl.key] = CrowdControl.duration[CrowdControl.key] + TimerGetRemaining(timer[i][j])
else
if Taunt.source[CrowdControl.unit[CrowdControl.key]] == CrowdControl.source[CrowdControl.key] then
CrowdControl.duration[CrowdControl.key] = CrowdControl.duration[CrowdControl.key] + TimerGetRemaining(timer[i][j])
end
end
end
if CrowdControl.type[CrowdControl.key] ~= CROWD_CONTROL_FEAR and CrowdControl.type[CrowdControl.key] ~= CROWD_CONTROL_TAUNT and CrowdControl.type[CrowdControl.key] ~= CROWD_CONTROL_KNOCKBACK and CrowdControl.type[CrowdControl.key] ~= CROWD_CONTROL_KNOCKUP then
local spell = BlzGetUnitAbility(dummy, ability[CrowdControl.type[CrowdControl.key]])
BlzUnitDisableAbility(dummy, ability[CrowdControl.type[CrowdControl.key]], false, false)
BlzSetAbilityRealLevelField(spell, ABILITY_RLF_DURATION_NORMAL, 0, CrowdControl.duration[CrowdControl.key])
BlzSetAbilityRealLevelField(spell, ABILITY_RLF_DURATION_HERO, 0, CrowdControl.duration[CrowdControl.key])
if CrowdControl.type[CrowdControl.key] == CROWD_CONTROL_SLOW then
BlzSetAbilityRealLevelField(spell, ABILITY_RLF_MOVEMENT_SPEED_REDUCTION_PERCENT_CRI1, 0, CrowdControl.amount[CrowdControl.key])
elseif CrowdControl.type[CrowdControl.key] == CROWD_CONTROL_SLOW_ATTACK then
BlzSetAbilityRealLevelField(spell, ABILITY_RLF_ATTACK_SPEED_REDUCTION_PERCENT_CRI2, 0, CrowdControl.amount[CrowdControl.key])
end
IncUnitAbilityLevel(dummy, ability[CrowdControl.type[CrowdControl.key]])
DecUnitAbilityLevel(dummy, ability[CrowdControl.type[CrowdControl.key]])
if IssueTargetOrder(dummy, order[CrowdControl.type[CrowdControl.key]], CrowdControl.unit[CrowdControl.key]) then
UnitRemoveAbility(CrowdControl.unit[CrowdControl.key], buff[CrowdControl.type[CrowdControl.key]])
IssueTargetOrder(dummy, order[CrowdControl.type[CrowdControl.key]], CrowdControl.unit[CrowdControl.key])
TimerStart(timer[i][j], CrowdControl.duration[CrowdControl.key], false, function()
PauseTimer(timer[i][j])
DestroyTimer(timer[i][j])
timer[i][j] = nil
end)
if CrowdControl.model[CrowdControl.key] then
if CrowdControl.point[CrowdControl.key] then
LinkEffectToBuff(CrowdControl.unit[CrowdControl.key], buff[CrowdControl.type[CrowdControl.key]], CrowdControl.model[CrowdControl.key], CrowdControl.point[CrowdControl.key])
else
DestroyEffect(AddSpecialEffect(CrowdControl.model[CrowdControl.key], GetUnitX(CrowdControl.unit[CrowdControl.key]), GetUnitY(CrowdControl.unit[CrowdControl.key])))
end
end
else
PauseTimer(timer[i][j])
DestroyTimer(timer[i][j])
timer[i][j] = nil
end
BlzUnitDisableAbility(dummy, ability[CrowdControl.type[CrowdControl.key]], true, true)
else
if CrowdControl.type[CrowdControl.key] == CROWD_CONTROL_FEAR then
Fear:apply(CrowdControl.unit[CrowdControl.key], CrowdControl.duration[CrowdControl.key], CrowdControl.model[CrowdControl.key], CrowdControl.point[CrowdControl.key])
elseif CrowdControl.type[CrowdControl.key] == CROWD_CONTROL_TAUNT then
Taunt:apply(CrowdControl.source[CrowdControl.key], CrowdControl.unit[CrowdControl.key], CrowdControl.duration[CrowdControl.key], CrowdControl.model[CrowdControl.key], CrowdControl.point[CrowdControl.key])
elseif CrowdControl.type[CrowdControl.key] == CROWD_CONTROL_KNOCKBACK then
Knockback:apply(CrowdControl.unit[CrowdControl.key], CrowdControl.angle[CrowdControl.key], CrowdControl.distance[CrowdControl.key], CrowdControl.duration[CrowdControl.key], CrowdControl.model[CrowdControl.key], CrowdControl.point[CrowdControl.key], CrowdControl.cliff[CrowdControl.key], CrowdControl.destructable[CrowdControl.key], CrowdControl.agent[CrowdControl.key])
elseif CrowdControl.type[CrowdControl.key] == CROWD_CONTROL_KNOCKUP then
Knockup:apply(CrowdControl.unit[CrowdControl.key], CrowdControl.duration[CrowdControl.key], CrowdControl.height[CrowdControl.key], CrowdControl.model[CrowdControl.key], CrowdControl.point[CrowdControl.key])
end
TimerStart(timer[i][j], CrowdControl.duration[CrowdControl.key], false, function()
PauseTimer(timer[i][j])
DestroyTimer(timer[i][j])
timer[i][j] = nil
end)
end
end
if CrowdControl.key > 0 then
CrowdControl.key = CrowdControl.key - 1
end
end
end
function mt:silence(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_SILENCE)
end
function mt:silenced(unit)
return GetUnitAbilityLevel(unit, SILENCE_BUFF) > 0
end
function mt:stun(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_STUN)
end
function mt:stunned(unit)
return GetUnitAbilityLevel(unit, STUN_BUFF) > 0
end
function mt:slow(unit, amount, duration, model, point, stack)
self:cast(nil, unit, amount, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_SLOW)
end
function mt:slowed(unit)
return GetUnitAbilityLevel(unit, MOVEMENT_SLOW_BUFF) > 0
end
function mt:slowAttack(unit, amount, duration, model, point, stack)
self:cast(nil, unit, amount, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_SLOW_ATTACK)
end
function mt:attackSlowed(unit)
return GetUnitAbilityLevel(unit, ATTACK_SLOW_BUFF) > 0
end
function mt:banish(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_BANISH)
end
function mt:banished(unit)
return GetUnitAbilityLevel(unit, BANISH_BUFF) > 0
end
function mt:ensnare(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_ENSNARE)
end
function mt:ensnared(unit)
return GetUnitAbilityLevel(unit, ENSNARE_BUFF) > 0
end
function mt:purge(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_PURGE)
end
function mt:purged(unit)
return GetUnitAbilityLevel(unit, PURGE_BUFF) > 0
end
function mt:hex(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_HEX)
end
function mt:hexed(unit)
return GetUnitAbilityLevel(unit, HEX_BUFF) > 0
end
function mt:sleep(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_SLEEP)
end
function mt:sleeping(unit)
return GetUnitAbilityLevel(unit, SLEEP_BUFF) > 0
end
function mt:cyclone(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_CYCLONE)
end
function mt:cycloned(unit)
return GetUnitAbilityLevel(unit, CYCLONE_BUFF) > 0
end
function mt:entangle(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_ENTANGLE)
end
function mt:entangled(unit)
return GetUnitAbilityLevel(unit, ENTANGLE_BUFF) > 0
end
function mt:knockback(unit, angle, distance, duration, model, point, onCliff, onDestructable, onUnit, stack)
self:cast(nil, unit, 0, angle, distance, 0, duration, model, point, stack, onCliff, onDestructable, onUnit, CROWD_CONTROL_KNOCKBACK)
end
function mt:knockedback(unit)
return Knockback:knocked(unit)
end
function mt:knockup(unit, height, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, height, duration, model, point, stack, false, false, false, CROWD_CONTROL_KNOCKUP)
end
function mt:knockedup(unit)
return Knockup:isUnitKnocked(unit)
end
function mt:fear(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_FEAR)
end
function mt:feared(unit)
return Fear:feared(unit)
end
function mt:disarm(unit, duration, model, point, stack)
self:cast(nil, unit, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_DISARM)
end
function mt:disarmed(unit)
return GetUnitAbilityLevel(unit, DISARM_BUFF) > 0
end
function mt:taunt(source, target, duration, model, point, stack)
self:cast(source, target, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_TAUNT)
end
function mt:taunted(unit)
return Taunt:taunted(unit)
end
function mt:dispel(unit, type)
if buff[type] then
UnitRemoveAbility(unit, buff[type])
if timer[unit] then
PauseTimer(timer[unit][type])
DestroyTimer(timer[unit][type])
timer[unit][type] = nil
end
end
end
function mt:dispelAll(unit)
self:dispel(unit, CROWD_CONTROL_SILENCE)
self:dispel(unit, CROWD_CONTROL_STUN)
self:dispel(unit, CROWD_CONTROL_SLOW)
self:dispel(unit, CROWD_CONTROL_SLOW_ATTACK)
self:dispel(unit, CROWD_CONTROL_BANISH)
self:dispel(unit, CROWD_CONTROL_ENSNARE)
self:dispel(unit, CROWD_CONTROL_PURGE)
self:dispel(unit, CROWD_CONTROL_HEX)
self:dispel(unit, CROWD_CONTROL_SLEEP)
self:dispel(unit, CROWD_CONTROL_CYCLONE)
self:dispel(unit, CROWD_CONTROL_ENTANGLE)
self:dispel(unit, CROWD_CONTROL_DISARM)
self:dispel(unit, CROWD_CONTROL_FEAR)
self:dispel(unit, CROWD_CONTROL_TAUNT)
end
function mt:remaining(unit, type)
if not timer[unit] then
return 0
else
return TimerGetRemaining(timer[unit][type])
end
end
function mt:register(id, code)
if type(code) == "function" then
if id >= CROWD_CONTROL_SILENCE and id <= CROWD_CONTROL_KNOCKUP then
if not event[id] then event[id] = {} end
table.insert(event[id], code)
else
table.insert(trigger, code)
end
end
end
end
end)
if Debug and Debug.endFile then Debug.endFile() end
--[[
/* --------------------- DamageInterface v2.4 by Chopinski --------------------- */
Allows for easy registration of specific damage type events like on attack
damage or on spell damage, etc...
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
-- This constant is used to define if the system will cache
-- extra information from a Damage Event, like the unit
-- Custom value (UnitUserData), a unit Handle Id, and more
-- Additionaly you can see the Cache function below
-- to have an idea and comment the members you want cached or not
local CACHE_EXTRA = true
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
Damage = {
source = {
unit,
player,
handle,
isHero,
isMelee,
isRanged,
isStructure,
isMagicImmune,
id,
x,
y,
z
},
target = {
unit,
player,
handle,
isHero,
isMelee,
isRanged,
isStructure,
isMagicImmune,
id,
x,
y,
z
},
damagetype,
attacktype,
isSpell,
isAttack,
isEnemy,
isAlly
}
local after = {}
local before = {}
local damage = {}
local damaging = {}
local trigger = CreateTrigger()
local location = Location(0, 0)
local function GetUnitZ(unit)
MoveLocation(location, GetUnitX(unit), GetUnitY(unit))
return GetUnitFlyHeight(unit) + GetLocationZ(location)
end
local function Cache(source, target, damagetype, attacktype)
Damage.damagetype = damagetype
Damage.attacktype = attacktype
Damage.source.unit = source
Damage.target.unit = target
Damage.isAttack = damagetype == DAMAGE_TYPE_NORMAL
Damage.isSpell = attacktype == ATTACK_TYPE_NORMAL
-- You can comment the members you dont want to be cached
-- or set CACHE_EXTRA = false to not save them at all
if CACHE_EXTRA then
Damage.source.player = GetOwningPlayer(source)
Damage.target.player = GetOwningPlayer(target)
Damage.isEnemy = IsUnitEnemy(target, Damage.source.player)
Damage.isAlly = IsUnitAlly(target, Damage.source.player)
Damage.source.isMelee = IsUnitType(source, UNIT_TYPE_MELEE_ATTACKER)
Damage.source.isRanged = IsUnitType(source, UNIT_TYPE_RANGED_ATTACKER)
Damage.target.isMelee = IsUnitType(target, UNIT_TYPE_MELEE_ATTACKER)
Damage.target.isRanged = IsUnitType(target, UNIT_TYPE_RANGED_ATTACKER)
Damage.source.isHero = IsUnitType(source, UNIT_TYPE_HERO)
Damage.target.isHero = IsUnitType(target, UNIT_TYPE_HERO)
Damage.source.isStructure = IsUnitType(source, UNIT_TYPE_STRUCTURE)
Damage.target.isStructure = IsUnitType(target, UNIT_TYPE_STRUCTURE)
Damage.source.isMagicImmune = IsUnitType(source, UNIT_TYPE_MAGIC_IMMUNE)
Damage.target.isMagicImmune = IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE)
Damage.source.x = GetUnitX(source)
Damage.source.y = GetUnitY(source)
Damage.source.z = GetUnitZ(source)
Damage.target.x = GetUnitX(target)
Damage.target.y = GetUnitY(target)
Damage.target.z = GetUnitZ(target)
Damage.source.id = GetUnitUserData(source)
Damage.target.id = GetUnitUserData(target)
Damage.source.handle = GetHandleId(source)
Damage.target.handle = GetHandleId(target)
end
end
onInit(function()
for i = 1, 7 do
after[i] = {}
before[i] = {}
end
TriggerRegisterAnyUnitEventBJ(trigger, EVENT_PLAYER_UNIT_DAMAGING)
TriggerAddCondition(trigger, Filter(function()
if GetTriggerEventId() == EVENT_PLAYER_UNIT_DAMAGING then
Cache(GetEventDamageSource(), BlzGetEventDamageTarget(), BlzGetEventDamageType(), BlzGetEventAttackType())
if Damage.damagetype ~= DAMAGE_TYPE_UNKNOWN then
local i = GetHandleId(Damage.attacktype) + 1
local j = GetHandleId(Damage.damagetype) + 1
if before[i][1] then
for k = 1, #before[i][1] do
before[i][1][k]()
end
end
if before[1][j] then
for k = 1, #before[1][j] do
before[1][j][k]()
end
end
if before[i][j] then
for k = 1, #before[i][j] do
before[i][j][k]()
end
end
for k = 1, #damaging do
damaging[k]()
end
end
end
end))
TriggerRegisterAnyUnitEventBJ(trigger, EVENT_PLAYER_UNIT_DAMAGED)
TriggerAddCondition(trigger, Filter(function()
if GetTriggerEventId() == EVENT_PLAYER_UNIT_DAMAGED then
Cache(GetEventDamageSource(), BlzGetEventDamageTarget(), BlzGetEventDamageType(), BlzGetEventAttackType())
if Damage.damagetype ~= DAMAGE_TYPE_UNKNOWN then
local i = GetHandleId(Damage.attacktype) + 1
local j = GetHandleId(Damage.damagetype) + 1
if after[i][1] then
for k = 1, #after[i][1] do
after[i][1][k]()
end
end
if after[1][j] then
if Damage.isAttack and Evasion then
if not Evasion.evade then
for k = 1, #after[1][j] do
after[1][j][k]()
end
end
else
for k = 1, #after[1][j] do
after[1][j][k]()
end
end
end
if after[i][j] then
for k = 1, #after[i][j] do
after[i][j][k]()
end
end
for k = 1, #damage do
damage[k]()
end
end
end
end))
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function RegisterDamageEvent(attacktype, damagetype, code)
if type(code) == "function" then
local i = GetHandleId(attacktype) + 1
local j = GetHandleId(damagetype) + 1
if not after[i][j] then after[i][j] = {} end
table.insert(after[i][j], code)
end
end
function RegisterAttackDamageEvent(code)
RegisterDamageEvent(nil, DAMAGE_TYPE_NORMAL, code)
end
function RegisterSpellDamageEvent(code)
RegisterDamageEvent(ATTACK_TYPE_NORMAL, nil, code)
end
function RegisterAnyDamageEvent(code)
if type(code) == "function" then
table.insert(damage, code)
end
end
function RegisterDamagingEvent(attacktype, damagetype, code)
if type(code) == "function" then
local i = GetHandleId(attacktype) + 1
local j = GetHandleId(damagetype) + 1
if not before[i][j] then before[i][j] = {} end
table.insert(before[i][j], code)
end
end
function RegisterAttackDamagingEvent(code)
RegisterDamagingEvent(nil, DAMAGE_TYPE_NORMAL, code)
end
function RegisterSpellDamagingEvent(code)
RegisterDamagingEvent(ATTACK_TYPE_NORMAL, nil, code)
end
function RegisterAnyDamagingEvent(code)
if type(code) == "function" then
table.insert(damaging, code)
end
end
end
if Debug and Debug.beginFile then Debug.beginFile("DamageInterface") end
--[[
/* --------------------- DamageInterface v2.4 by Chopinski --------------------- */
Allows for easy registration of specific damage type events like on attack
damage or on spell damage, etc...
]]--
OnInit.main(function()
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
-- This constant is used to define if the system will cache
-- extra information from a Damage Event, like the unit
-- Custom value (UnitUserData), a unit Handle Id, and more
-- Additionaly you can see the Cache function below
-- to have an idea and comment the members you want cached or not
local CACHE_EXTRA = true
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
Damage = {
source = {
unit,
player,
handle,
isHero,
isMelee,
isRanged,
isStructure,
isMagicImmune,
id,
x,
y,
z
},
target = {
unit,
player,
handle,
isHero,
isMelee,
isRanged,
isStructure,
isMagicImmune,
id,
x,
y,
z
},
damagetype,
attacktype,
isSpell,
isAttack,
isEnemy,
isAlly
}
local after = {}
local before = {}
local damage = {}
local damaging = {}
local trigger = CreateTrigger()
local location = Location(0, 0)
local function GetUnitZ(unit)
MoveLocation(location, GetUnitX(unit), GetUnitY(unit))
return GetUnitFlyHeight(unit) + GetLocationZ(location)
end
local function Cache(source, target, damagetype, attacktype)
Damage.damagetype = damagetype
Damage.attacktype = attacktype
Damage.source.unit = source
Damage.target.unit = target
Damage.isAttack = damagetype == DAMAGE_TYPE_NORMAL
Damage.isSpell = attacktype == ATTACK_TYPE_NORMAL
-- You can comment the members you dont want to be cached
-- or set CACHE_EXTRA = false to not save them at all
if CACHE_EXTRA then
Damage.source.player = GetOwningPlayer(source)
Damage.target.player = GetOwningPlayer(target)
Damage.isEnemy = IsUnitEnemy(target, Damage.source.player)
Damage.isAlly = IsUnitAlly(target, Damage.source.player)
Damage.source.isMelee = IsUnitType(source, UNIT_TYPE_MELEE_ATTACKER)
Damage.source.isRanged = IsUnitType(source, UNIT_TYPE_RANGED_ATTACKER)
Damage.target.isMelee = IsUnitType(target, UNIT_TYPE_MELEE_ATTACKER)
Damage.target.isRanged = IsUnitType(target, UNIT_TYPE_RANGED_ATTACKER)
Damage.source.isHero = IsUnitType(source, UNIT_TYPE_HERO)
Damage.target.isHero = IsUnitType(target, UNIT_TYPE_HERO)
Damage.source.isStructure = IsUnitType(source, UNIT_TYPE_STRUCTURE)
Damage.target.isStructure = IsUnitType(target, UNIT_TYPE_STRUCTURE)
Damage.source.isMagicImmune = IsUnitType(source, UNIT_TYPE_MAGIC_IMMUNE)
Damage.target.isMagicImmune = IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE)
Damage.source.x = GetUnitX(source)
Damage.source.y = GetUnitY(source)
Damage.source.z = GetUnitZ(source)
Damage.target.x = GetUnitX(target)
Damage.target.y = GetUnitY(target)
Damage.target.z = GetUnitZ(target)
Damage.source.id = GetUnitUserData(source)
Damage.target.id = GetUnitUserData(target)
Damage.source.handle = GetHandleId(source)
Damage.target.handle = GetHandleId(target)
end
end
for i = 1, 7 do
after[i] = {}
before[i] = {}
end
TriggerRegisterAnyUnitEventBJ(trigger, EVENT_PLAYER_UNIT_DAMAGING)
TriggerAddCondition(trigger, Filter(function()
if GetTriggerEventId() == EVENT_PLAYER_UNIT_DAMAGING then
Cache(GetEventDamageSource(), BlzGetEventDamageTarget(), BlzGetEventDamageType(), BlzGetEventAttackType())
if Damage.damagetype ~= DAMAGE_TYPE_UNKNOWN then
local i = GetHandleId(Damage.attacktype) + 1
local j = GetHandleId(Damage.damagetype) + 1
if before[i][1] then
for k = 1, #before[i][1] do
before[i][1][k]()
end
end
if before[1][j] then
for k = 1, #before[1][j] do
before[1][j][k]()
end
end
if before[i][j] then
for k = 1, #before[i][j] do
before[i][j][k]()
end
end
for k = 1, #damaging do
damaging[k]()
end
end
end
end))
TriggerRegisterAnyUnitEventBJ(trigger, EVENT_PLAYER_UNIT_DAMAGED)
TriggerAddCondition(trigger, Filter(function()
if GetTriggerEventId() == EVENT_PLAYER_UNIT_DAMAGED then
Cache(GetEventDamageSource(), BlzGetEventDamageTarget(), BlzGetEventDamageType(), BlzGetEventAttackType())
if Damage.damagetype ~= DAMAGE_TYPE_UNKNOWN then
local i = GetHandleId(Damage.attacktype) + 1
local j = GetHandleId(Damage.damagetype) + 1
if after[i][1] then
for k = 1, #after[i][1] do
after[i][1][k]()
end
end
if after[1][j] then
if Damage.isAttack and Evasion then
if not Evasion.evade then
for k = 1, #after[1][j] do
after[1][j][k]()
end
end
else
for k = 1, #after[1][j] do
after[1][j][k]()
end
end
end
if after[i][j] then
for k = 1, #after[i][j] do
after[i][j][k]()
end
end
for k = 1, #damage do
damage[k]()
end
end
end
end))
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function RegisterDamageEvent(attacktype, damagetype, code)
if type(code) == "function" then
local i = GetHandleId(attacktype) + 1
local j = GetHandleId(damagetype) + 1
if not after[i][j] then after[i][j] = {} end
table.insert(after[i][j], code)
end
end
function RegisterAttackDamageEvent(code)
RegisterDamageEvent(nil, DAMAGE_TYPE_NORMAL, code)
end
function RegisterSpellDamageEvent(code)
RegisterDamageEvent(ATTACK_TYPE_NORMAL, nil, code)
end
function RegisterAnyDamageEvent(code)
if type(code) == "function" then
table.insert(damage, code)
end
end
function RegisterDamagingEvent(attacktype, damagetype, code)
if type(code) == "function" then
local i = GetHandleId(attacktype) + 1
local j = GetHandleId(damagetype) + 1
if not before[i][j] then before[i][j] = {} end
table.insert(before[i][j], code)
end
end
function RegisterAttackDamagingEvent(code)
RegisterDamagingEvent(nil, DAMAGE_TYPE_NORMAL, code)
end
function RegisterSpellDamagingEvent(code)
RegisterDamagingEvent(ATTACK_TYPE_NORMAL, nil, code)
end
function RegisterAnyDamagingEvent(code)
if type(code) == "function" then
table.insert(damaging, code)
end
end
end)
if Debug and Debug.endFile then Debug.endFile() end
--[[
/* ------- Utility Library for all the Damage Interface Custom Events ------- */
/* ---------------------------- v2.4 by Chopinski --------------------------- */
The API:
Evasion System:
function UnitAddEvasionChanceTimed takes unit u, real amount, real duration returns nothing
-> Add to a unit Evasion chance the specified amount for a given period
function UnitAddMissChanceTimed takes unit u, real amount, real duration returns nothing
-> Add to a unit Miss chance the specified amount for a given period
Critical Strike System:
function UnitAddCriticalStrikeTimed takes unit u, real chance, real multiplier, real duration returns nothing
-> Adds the specified values of chance and multiplier to a unit for a given period
function UnitAddCriticalChanceTimed takes unit u, real chance, real duration returns nothing
-> Adds the specified values of critical chance to a unit for a given period
function UnitAddCriticalMultiplierTimed takes unit u, real multiplier, real duration returns nothing
-> Adds the specified values of critical multiplier to a unit for a given period
Spell Power System:
function UnitAddSpellPowerFlatTimed takes unit u, real amount, real duration returns nothing
-> Add to the Flat amount of Spell Power of a unit for a given period
function UnitAddSpellPowerPercentTimed takes unit u, real amount, real duration returns nothing
-> Add to the Percent amount of Spell Power of a unit for a given period
function AbilitySpellDamage takes unit u, integer abilityId, abilityreallevelfield field returns string
-> Given an ability field, will return a string that represents the damage that would be dealt
taking into consideration the spell power bonusses of a unit.
function AbilitySpellDamageEx takes real amount, unit u returns string
-> Similar to GetSpellDamage will return the damage that would be dealt but as a string
Life Steal System:
function UnitAddLifeStealTimed takes unit u, real amount, real duration returns nothing
-> Add to the Life Steal amount of a unit the given amount for a given period
Spell Vamp System:
function UnitAddSpellVampTimed takes unit u, real amount, real duration returns nothing
-> Add to the Spell Vamp amount of a unit the given amount for a given period
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
local PERIOD = 0.03125000
-- -------------------------------------------------------------------------- --
-- Evasion Utils --
-- -------------------------------------------------------------------------- --
do
EvasionUtils = setmetatable({}, {})
local mt = getmetatable(EvasionUtils)
mt.__index = mt
local array = {}
local key = 0
local timer = CreateTimer()
function mt:remove(i)
if self.type then
UnitAddEvasionChance(self.unit, -self.amount)
else
UnitAddMissChance(self.unit, -self.amount)
end
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:addTimed(unit, amount, duration, type)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.amount = amount
this.ticks = duration/PERIOD
this.type = type
key = key + 1
array[key] = this
if type then
UnitAddEvasionChance(unit, amount)
else
UnitAddMissChance(unit, amount)
end
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.ticks <= 0 then
i = this:remove(i)
end
this.ticks = this.ticks - 1
i = i + 1
end
end)
end
end
end
-- -------------------------------------------------------------------------- --
-- Critical Strike Utils --
-- -------------------------------------------------------------------------- --
do
CriticalUtils = setmetatable({}, {})
local mt = getmetatable(CriticalUtils)
mt.__index = mt
local array = {}
local key = 0
local timer = CreateTimer()
function mt:remove(i)
UnitAddCriticalStrike(self.unit, -self.chance, -self.multiplier)
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:addTimed(unit, chance, multiplier, duration, type)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.chance = chance
this.multiplier = multiplier
this.ticks = duration/PERIOD
key = key + 1
array[key] = this
UnitAddCriticalStrike(unit, chance, multiplier)
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.ticks <= 0 then
i = this:remove(i)
end
this.ticks = this.ticks - 1
i = i + 1
end
end)
end
end
end
-- -------------------------------------------------------------------------- --
-- Spell Power Utils --
-- -------------------------------------------------------------------------- --
do
SpellPowerUtils = setmetatable({}, {})
local mt = getmetatable(SpellPowerUtils)
mt.__index = mt
local array = {}
local key = 0
local timer = CreateTimer()
function mt:remove(i)
if self.type then
UnitAddSpellPowerFlat(self.unit, -self.amount)
else
UnitAddSpellPowerPercent(self.unit, -self.amount)
end
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:addTimed(unit, amount, duration, type)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.amount = amount
this.ticks = duration/PERIOD
this.type = type
key = key + 1
array[key] = this
if type then
UnitAddSpellPowerFlat(unit, amount)
else
UnitAddSpellPowerPercent(unit, amount)
end
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.ticks <= 0 then
i = this:remove(i)
end
this.ticks = this.ticks - 1
i = i + 1
end
end)
end
end
end
-- -------------------------------------------------------------------------- --
-- Life Steal Utils --
-- -------------------------------------------------------------------------- --
do
LifeStealUtils = setmetatable({}, {})
local mt = getmetatable(LifeStealUtils)
mt.__index = mt
local array = {}
local key = 0
local timer = CreateTimer()
function mt:remove(i)
UnitAddLifeSteal(self.unit, -self.amount)
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:addTimed(unit, amount, duration)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.amount = amount
this.ticks = duration/PERIOD
key = key + 1
array[key] = this
UnitAddLifeSteal(unit, amount)
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.ticks <= 0 then
i = this:remove(i)
end
this.ticks = this.ticks - 1
i = i + 1
end
end)
end
end
end
-- -------------------------------------------------------------------------- --
-- Spell Vamp Utils --
-- -------------------------------------------------------------------------- --
SpellVampUtils = setmetatable({}, {})
local mt = getmetatable(SpellVampUtils)
mt.__index = mt
local array = {}
local key = 0
local timer = CreateTimer()
function mt:remove(i)
UnitAddSpellVamp(self.unit, -self.amount)
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:addTimed(unit, amount, duration)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.amount = amount
this.ticks = duration/PERIOD
key = key + 1
array[key] = this
UnitAddSpellVamp(unit, amount)
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.ticks <= 0 then
i = this:remove(i)
end
this.ticks = this.ticks - 1
i = i + 1
end
end)
end
end
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function UnitAddEvasionChanceTimed(unit, amount, duration)
EvasionUtils:addTimed(unit, amount, duration, true)
end
function UnitAddMissChanceTimed(unit, amount, duration)
EvasionUtils:addTimed(unit, amount, duration, false)
end
function UnitAddCriticalStrikeTimed(unit, chance, multiplier, duration)
CriticalUtils:addTimed(unit, chance, multiplier, duration, 0)
end
function UnitAddCriticalChanceTimed(unit, chance, duration)
CriticalUtils:addTimed(unit, chance, 0, duration, 1)
end
function UnitAddCriticalMultiplierTimed(unit, multiplier, duration)
CriticalUtils:addTimed(unit, 0, multiplier, duration, 2)
end
function UnitAddSpellPowerFlatTimed(unit, amount, duration)
SpellPowerUtils:addTimed(unit, amount, duration, true)
end
function UnitAddSpellPowerPercentTimed(unit, amount, duration)
SpellPowerUtils:addTimed(unit, amount, duration, false)
end
function AbilitySpellDamage(unit, ability, field)
return I2S(R2I((BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ability), field, GetUnitAbilityLevel(unit, ability) - 1) + (SpellPower.flat[unit] or 0)) * (1 + (SpellPower.percent[unit] or 0))))
end
function AbilitySpellDamageEx(real, unit)
return I2S(R2I((real + (SpellPower.flat[unit] or 0)) * (1 + (SpellPower.percent[unit] or 0))))
end
function UnitAddLifeStealTimed(unit, amount, duration)
LifeStealUtils:addTimed(unit, amount, duration)
end
function UnitAddSpellVampTimed(unit, amount, duration)
SpellVampUtils:addTimed(unit, amount, duration)
end
end
if Debug and Debug.beginFile then Debug.beginFile("DamageInterfaceUtils") end
--[[
/* ------- Utility Library for all the Damage Interface Custom Events ------- */
/* ---------------------------- v2.4 by Chopinski --------------------------- */
The API:
Evasion System:
function UnitAddEvasionChanceTimed takes unit u, real amount, real duration returns nothing
-> Add to a unit Evasion chance the specified amount for a given period
function UnitAddMissChanceTimed takes unit u, real amount, real duration returns nothing
-> Add to a unit Miss chance the specified amount for a given period
Critical Strike System:
function UnitAddCriticalStrikeTimed takes unit u, real chance, real multiplier, real duration returns nothing
-> Adds the specified values of chance and multiplier to a unit for a given period
function UnitAddCriticalChanceTimed takes unit u, real chance, real duration returns nothing
-> Adds the specified values of critical chance to a unit for a given period
function UnitAddCriticalMultiplierTimed takes unit u, real multiplier, real duration returns nothing
-> Adds the specified values of critical multiplier to a unit for a given period
Spell Power System:
function UnitAddSpellPowerFlatTimed takes unit u, real amount, real duration returns nothing
-> Add to the Flat amount of Spell Power of a unit for a given period
function UnitAddSpellPowerPercentTimed takes unit u, real amount, real duration returns nothing
-> Add to the Percent amount of Spell Power of a unit for a given period
function AbilitySpellDamage takes unit u, integer abilityId, abilityreallevelfield field returns string
-> Given an ability field, will return a string that represents the damage that would be dealt
taking into consideration the spell power bonusses of a unit.
function AbilitySpellDamageEx takes real amount, unit u returns string
-> Similar to GetSpellDamage will return the damage that would be dealt but as a string
Life Steal System:
function UnitAddLifeStealTimed takes unit u, real amount, real duration returns nothing
-> Add to the Life Steal amount of a unit the given amount for a given period
Spell Vamp System:
function UnitAddSpellVampTimed takes unit u, real amount, real duration returns nothing
-> Add to the Spell Vamp amount of a unit the given amount for a given period
]]--
OnInit.main(function()
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
local PERIOD = 0.03125000
-- -------------------------------------------------------------------------- --
-- Evasion Utils --
-- -------------------------------------------------------------------------- --
do
EvasionUtils = setmetatable({}, {})
local mt = getmetatable(EvasionUtils)
mt.__index = mt
local array = {}
local key = 0
local timer = CreateTimer()
function mt:remove(i)
if self.type then
UnitAddEvasionChance(self.unit, -self.amount)
else
UnitAddMissChance(self.unit, -self.amount)
end
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:addTimed(unit, amount, duration, type)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.amount = amount
this.ticks = duration/PERIOD
this.type = type
key = key + 1
array[key] = this
if type then
UnitAddEvasionChance(unit, amount)
else
UnitAddMissChance(unit, amount)
end
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.ticks <= 0 then
i = this:remove(i)
end
this.ticks = this.ticks - 1
i = i + 1
end
end)
end
end
end
-- -------------------------------------------------------------------------- --
-- Critical Strike Utils --
-- -------------------------------------------------------------------------- --
do
CriticalUtils = setmetatable({}, {})
local mt = getmetatable(CriticalUtils)
mt.__index = mt
local array = {}
local key = 0
local timer = CreateTimer()
function mt:remove(i)
UnitAddCriticalStrike(self.unit, -self.chance, -self.multiplier)
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:addTimed(unit, chance, multiplier, duration, type)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.chance = chance
this.multiplier = multiplier
this.ticks = duration/PERIOD
key = key + 1
array[key] = this
UnitAddCriticalStrike(unit, chance, multiplier)
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.ticks <= 0 then
i = this:remove(i)
end
this.ticks = this.ticks - 1
i = i + 1
end
end)
end
end
end
-- -------------------------------------------------------------------------- --
-- Spell Power Utils --
-- -------------------------------------------------------------------------- --
do
SpellPowerUtils = setmetatable({}, {})
local mt = getmetatable(SpellPowerUtils)
mt.__index = mt
local array = {}
local key = 0
local timer = CreateTimer()
function mt:remove(i)
if self.type then
UnitAddSpellPowerFlat(self.unit, -self.amount)
else
UnitAddSpellPowerPercent(self.unit, -self.amount)
end
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:addTimed(unit, amount, duration, type)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.amount = amount
this.ticks = duration/PERIOD
this.type = type
key = key + 1
array[key] = this
if type then
UnitAddSpellPowerFlat(unit, amount)
else
UnitAddSpellPowerPercent(unit, amount)
end
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.ticks <= 0 then
i = this:remove(i)
end
this.ticks = this.ticks - 1
i = i + 1
end
end)
end
end
end
-- -------------------------------------------------------------------------- --
-- Life Steal Utils --
-- -------------------------------------------------------------------------- --
do
LifeStealUtils = setmetatable({}, {})
local mt = getmetatable(LifeStealUtils)
mt.__index = mt
local array = {}
local key = 0
local timer = CreateTimer()
function mt:remove(i)
UnitAddLifeSteal(self.unit, -self.amount)
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:addTimed(unit, amount, duration)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.amount = amount
this.ticks = duration/PERIOD
key = key + 1
array[key] = this
UnitAddLifeSteal(unit, amount)
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.ticks <= 0 then
i = this:remove(i)
end
this.ticks = this.ticks - 1
i = i + 1
end
end)
end
end
end
-- -------------------------------------------------------------------------- --
-- Spell Vamp Utils --
-- -------------------------------------------------------------------------- --
SpellVampUtils = setmetatable({}, {})
local mt = getmetatable(SpellVampUtils)
mt.__index = mt
local array = {}
local key = 0
local timer = CreateTimer()
function mt:remove(i)
UnitAddSpellVamp(self.unit, -self.amount)
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:addTimed(unit, amount, duration)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.amount = amount
this.ticks = duration/PERIOD
key = key + 1
array[key] = this
UnitAddSpellVamp(unit, amount)
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.ticks <= 0 then
i = this:remove(i)
end
this.ticks = this.ticks - 1
i = i + 1
end
end)
end
end
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function UnitAddEvasionChanceTimed(unit, amount, duration)
EvasionUtils:addTimed(unit, amount, duration, true)
end
function UnitAddMissChanceTimed(unit, amount, duration)
EvasionUtils:addTimed(unit, amount, duration, false)
end
function UnitAddCriticalStrikeTimed(unit, chance, multiplier, duration)
CriticalUtils:addTimed(unit, chance, multiplier, duration, 0)
end
function UnitAddCriticalChanceTimed(unit, chance, duration)
CriticalUtils:addTimed(unit, chance, 0, duration, 1)
end
function UnitAddCriticalMultiplierTimed(unit, multiplier, duration)
CriticalUtils:addTimed(unit, 0, multiplier, duration, 2)
end
function UnitAddSpellPowerFlatTimed(unit, amount, duration)
SpellPowerUtils:addTimed(unit, amount, duration, true)
end
function UnitAddSpellPowerPercentTimed(unit, amount, duration)
SpellPowerUtils:addTimed(unit, amount, duration, false)
end
function AbilitySpellDamage(unit, ability, field)
return I2S(R2I((BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ability), field, GetUnitAbilityLevel(unit, ability) - 1) + (SpellPower.flat[unit] or 0)) * (1 + (SpellPower.percent[unit] or 0))))
end
function AbilitySpellDamageEx(real, unit)
return I2S(R2I((real + (SpellPower.flat[unit] or 0)) * (1 + (SpellPower.percent[unit] or 0))))
end
function UnitAddLifeStealTimed(unit, amount, duration)
LifeStealUtils:addTimed(unit, amount, duration)
end
function UnitAddSpellVampTimed(unit, amount, duration)
SpellVampUtils:addTimed(unit, amount, duration)
end
end)
if Debug and Debug.endFile then Debug.endFile() end
--[[
/* ------------------------ Evasion v2.4 by Chopinski ----------------------- */
Evasion implements an easy way to register and detect a custom evasion event.
It works by monitoring custom evasion and missing values given to units,
and nulling damage when the odds given occurs.
It will only detect custom evasion, so all evasion or miss values given to a
unit must be done so using the public API provided by this system.
*Evasion requires DamageInterface. Do not use TriggerSleepAction() with Evasion.
The API:
function RegisterEvasionEvent(function YourFunction)
-> YourFunction will run when a unit evades an attack.
function GetMissingUnit takes nothing returns unit
-> Returns the unit missing the attack
function GetEvadingUnit takes nothing returns unit
-> Returns the unit evading the attack
function GetEvadedDamage takes nothing returns real
-> Returns the amount of evaded damage
function GetUnitEvasionChance takes unit u returns real
-> Returns this system amount of evasion chance given to a unit
function GetUnitMissChance takes unit u returns real
-> Returns this system amount of miss chance given to a unit
function SetUnitEvasionChance takes unit u, real chance returns nothing
-> Sets unit evasion chance to specified amount
function SetUnitMissChance takes unit u, real chance returns nothing
-> Sets unit miss chance to specified amount
function UnitAddEvasionChance takes unit u, real chance returns nothing
-> Add to a unit Evasion chance the specified amount
function UnitAddMissChance takes unit u, real chance returns nothing
-> Add to a unit Miss chance the specified amount
function MakeUnitNeverMiss takes unit u, boolean flag returns nothing
-> Will make a unit never miss attacks no matter the evasion chance of the attacked unit
function DoUnitNeverMiss takes unit u returns boolean
-> Returns true if the unit will never miss an attack
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
local TEXT_SIZE = 0.016
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
Evasion = {
evasion = {},
miss = {},
neverMiss = {},
source,
target,
damage,
evade
}
local event = {}
local function Text(unit, text, duration, red, green, blue, alpha)
local texttag = CreateTextTag()
SetTextTagText(texttag, text, TEXT_SIZE)
SetTextTagPosUnit(texttag, unit, 0)
SetTextTagColor(texttag, red, green, blue, alpha)
SetTextTagLifespan(texttag, duration)
SetTextTagVelocity(texttag, 0.0, 0.0355)
SetTextTagPermanent(texttag, false)
end
onInit(function()
RegisterAttackDamagingEvent(function()
local damage = GetEventDamage()
Evasion.evade = false
if damage > 0 and not ((Evasion.neverMiss[Damage.source.unit] or 0) > 0) then
Evasion.evade = GetRandomReal(0, 100) <= (Evasion.evasion[Damage.target.unit] or 0) or GetRandomReal(0, 100) <= (Evasion.miss[Damage.source.unit] or 0)
if Evasion.evade then
Evasion.source = Damage.source
Evasion.target = Damage.target
Evasion.damage = damage
for i = 1, #event do
event[i]()
end
BlzSetEventDamage(0)
BlzSetEventWeaponType(WEAPON_TYPE_WHOKNOWS)
Text(Evasion.source.unit, "miss", 1.5, 255, 0, 0, 255)
Evasion.damage = 0
Evasion.source = nil
Evasion.target = nil
end
end
end)
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function RegisterEvasionEvent(code)
if type(code) == "function" then
table.insert(event, code)
end
end
function GetMissingUnit()
return Evasion.source.unit
end
function GetEvadingUnit()
return Evasion.target.unit
end
function GetEvadedDamage()
return Evasion.damage or 0
end
function GetUnitEvasionChance(unit)
return Evasion.evasion[unit] or 0
end
function GetUnitMissChance(unit)
return Evasion.miss[unit] or 0
end
function SetUnitEvasionChance(unit, real)
Evasion.evasion[unit] = real
end
function SetUnitMissChance(unit, real)
Evasion.miss[unit] = real
end
function UnitAddEvasionChance(unit, real)
if not Evasion.evasion[unit] then Evasion.evasion[unit] = 0 end
Evasion.evasion[unit] = Evasion.evasion[unit] + real
end
function UnitAddMissChance(unit, real)
if not Evasion.miss[unit] then Evasion.miss[unit] = 0 end
Evasion.miss[unit] = Evasion.miss[unit] + real
end
function MakeUnitNeverMiss(unit, flag)
if not Evasion.neverMiss[unit] then Evasion.neverMiss[unit] = 0 end
if flag then
Evasion.neverMiss[unit] = Evasion.neverMiss[unit] + 1
else
Evasion.neverMiss[unit] = Evasion.neverMiss[unit] - 1
end
end
function DoUnitNeverMiss(unit)
return Evasion.neverMiss[unit] > 0
end
end
if Debug and Debug.beginFile then Debug.beginFile("Evasion") end
--[[
/* ------------------------ Evasion v2.4 by Chopinski ----------------------- */
Evasion implements an easy way to register and detect a custom evasion event.
It works by monitoring custom evasion and missing values given to units,
and nulling damage when the odds given occurs.
It will only detect custom evasion, so all evasion or miss values given to a
unit must be done so using the public API provided by this system.
*Evasion requires DamageInterface. Do not use TriggerSleepAction() with Evasion.
The API:
function RegisterEvasionEvent(function YourFunction)
-> YourFunction will run when a unit evades an attack.
function GetMissingUnit takes nothing returns unit
-> Returns the unit missing the attack
function GetEvadingUnit takes nothing returns unit
-> Returns the unit evading the attack
function GetEvadedDamage takes nothing returns real
-> Returns the amount of evaded damage
function GetUnitEvasionChance takes unit u returns real
-> Returns this system amount of evasion chance given to a unit
function GetUnitMissChance takes unit u returns real
-> Returns this system amount of miss chance given to a unit
function SetUnitEvasionChance takes unit u, real chance returns nothing
-> Sets unit evasion chance to specified amount
function SetUnitMissChance takes unit u, real chance returns nothing
-> Sets unit miss chance to specified amount
function UnitAddEvasionChance takes unit u, real chance returns nothing
-> Add to a unit Evasion chance the specified amount
function UnitAddMissChance takes unit u, real chance returns nothing
-> Add to a unit Miss chance the specified amount
function MakeUnitNeverMiss takes unit u, boolean flag returns nothing
-> Will make a unit never miss attacks no matter the evasion chance of the attacked unit
function DoUnitNeverMiss takes unit u returns boolean
-> Returns true if the unit will never miss an attack
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
local TEXT_SIZE = 0.016
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
Evasion = {
evasion = {},
miss = {},
neverMiss = {},
source,
target,
damage,
evade
}
local event = {}
local function Text(unit, text, duration, red, green, blue, alpha)
local texttag = CreateTextTag()
SetTextTagText(texttag, text, TEXT_SIZE)
SetTextTagPosUnit(texttag, unit, 0)
SetTextTagColor(texttag, red, green, blue, alpha)
SetTextTagLifespan(texttag, duration)
SetTextTagVelocity(texttag, 0.0, 0.0355)
SetTextTagPermanent(texttag, false)
end
OnInit.global(function()
RegisterAttackDamagingEvent(function()
local damage = GetEventDamage()
Evasion.evade = false
if damage > 0 and not ((Evasion.neverMiss[Damage.source.unit] or 0) > 0) then
Evasion.evade = GetRandomReal(0, 100) <= (Evasion.evasion[Damage.target.unit] or 0) or GetRandomReal(0, 100) <= (Evasion.miss[Damage.source.unit] or 0)
if Evasion.evade then
Evasion.source = Damage.source
Evasion.target = Damage.target
Evasion.damage = damage
for i = 1, #event do
event[i]()
end
BlzSetEventDamage(0)
BlzSetEventWeaponType(WEAPON_TYPE_WHOKNOWS)
Text(Evasion.source.unit, "miss", 1.5, 255, 0, 0, 255)
Evasion.damage = 0
Evasion.source = nil
Evasion.target = nil
end
end
end)
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function RegisterEvasionEvent(code)
if type(code) == "function" then
table.insert(event, code)
end
end
function GetMissingUnit()
return Evasion.source.unit
end
function GetEvadingUnit()
return Evasion.target.unit
end
function GetEvadedDamage()
return Evasion.damage or 0
end
function GetUnitEvasionChance(unit)
return Evasion.evasion[unit] or 0
end
function GetUnitMissChance(unit)
return Evasion.miss[unit] or 0
end
function SetUnitEvasionChance(unit, real)
Evasion.evasion[unit] = real
end
function SetUnitMissChance(unit, real)
Evasion.miss[unit] = real
end
function UnitAddEvasionChance(unit, real)
if not Evasion.evasion[unit] then Evasion.evasion[unit] = 0 end
Evasion.evasion[unit] = Evasion.evasion[unit] + real
end
function UnitAddMissChance(unit, real)
if not Evasion.miss[unit] then Evasion.miss[unit] = 0 end
Evasion.miss[unit] = Evasion.miss[unit] + real
end
function MakeUnitNeverMiss(unit, flag)
if not Evasion.neverMiss[unit] then Evasion.neverMiss[unit] = 0 end
if flag then
Evasion.neverMiss[unit] = Evasion.neverMiss[unit] + 1
else
Evasion.neverMiss[unit] = Evasion.neverMiss[unit] - 1
end
end
function DoUnitNeverMiss(unit)
return Evasion.neverMiss[unit] > 0
end
end
if Debug and Debug.endFile then Debug.endFile() end
--[[
/* ------------------------ CriticalStrike v2.4 by Chopinski ----------------------- */
CriticalStrike implements an easy way to register and detect a custom critical event.
allows the manipulation of a unit critical strike chance and multiplier, as well as
manipulating the critical damage dealt.
It works by monitoring custom critical strike chance and multiplier values given to units.
It will only detect custom critical strikes, so all critical chance given to a
unit must be done so using the public API provided by this system.
*CriticalStrike requires DamageInterface. Do not use TriggerSleepAction() with Evasion.
It also requires optional Evasion so that this library is written after the Evasion
library, so both custom events will not fire at the same time.
The API:
function RegisterCriticalStrikeEvent(function YourFunction)
-> YourFunction will run when a unit hits a critical strike.
function GetCriticalSource takes nothing returns unit
-> Returns the unit hitting a critical strike.
function GetCriticalTarget takes nothing returns unit
-> Returns the unit being hit by a critical strike.
function GetCriticalDamage takes nothing returns real
-> Returns the critical strike damage amount.
function GetUnitCriticalChance takes unit u returns real
-> Returns the chance to hit a critical strike to specified unit.
function GetUnitCriticalMultiplier takes unit u returns real
-> Returns the chance to hit a critical strike to specified unit.
function SetUnitCriticalChance takes unit u, real value returns nothing
-> Set's the unit chance to hit a critical strike to specified value.
-> 15.0 = 15%
function SetUnitCriticalMultiplier takes unit u, real value returns nothing
-> Set's the unit multiplier of damage when hitting a critical to value
-> 1.0 = increases the multiplier by 1. all units have a multiplier of 1.0
by default, so by adding 1.0, for example, the critical damage will be
2x the normal damage
function SetCriticalEventDamage takes real newValue returns nothing
-> Modify the critical damage dealt to the specified value.
function UnitAddCriticalStrike takes unit u, real chance, real multiplier returns nothing
-> Adds the specified values of chance and multiplier to a unit
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
local TEXT_SIZE = 0.016
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
Critical = {
source,
target,
damage,
chance = {},
multiplier = {}
}
local event = {}
local function Text(unit, text, duration, red, green, blue, alpha)
local texttag = CreateTextTag()
SetTextTagText(texttag, text, TEXT_SIZE)
SetTextTagPosUnit(texttag, unit, 0)
SetTextTagColor(texttag, red, green, blue, alpha)
SetTextTagLifespan(texttag, duration)
SetTextTagVelocity(texttag, 0.0, 0.0355)
SetTextTagPermanent(texttag, false)
end
onInit(function()
RegisterAttackDamageEvent(function()
local damage = GetEventDamage()
if damage > 0 and GetRandomReal(0, 100) <= (Critical.chance[Damage.source.unit] or 0) and Damage.isEnemy and not Damage.target.isStructure and (Critical.multiplier[Damage.source.unit] or 0) > 0 then
Critical.source = Damage.source
Critical.target = Damage.target
Critical.damage = damage*(1 + (Critical.multiplier[Damage.source.unit] or 0))
for i = 1, #event do
event[i]()
end
BlzSetEventDamage(Critical.damage)
if Critical.damage > 0 then
Text(Critical.target.unit, (I2S(R2I(Critical.damage)) .. "!"), 1.5, 255, 0, 0, 255)
end
Critical.source = nil
Critical.target = nil
Critical.damage = 0
end
end)
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function RegisterCriticalStrikeEvent(code)
if type(code) == "function" then
table.insert(event, code)
end
end
function GetCriticalSource()
return Critical.source.unit
end
function GetCriticalTarget()
return Critical.target.unit
end
function GetCriticalDamage()
return Critical.damage or 0
end
function GetUnitCriticalChance(unit)
return Critical.chance[unit] or 0
end
function GetUnitCriticalMultiplier(unit)
return Critical.multiplier[unit] or 0
end
function SetUnitCriticalChance(unit, real)
Critical.chance[unit] = real
end
function SetUnitCriticalMultiplier(unit, real)
Critical.multiplier[unit] = real
end
function SetCriticalEventDamage(real)
Critical.damage = real
end
function UnitAddCriticalStrike(unit, chance, multiplier)
if not Critical.chance[unit] then Critical.chance[unit] = 0 end
if not Critical.multiplier[unit] then Critical.multiplier[unit] = 0 end
Critical.chance[unit] = Critical.chance[unit] + chance
Critical.multiplier[unit] = Critical.multiplier[unit] + multiplier
end
end
if Debug and Debug.beginFile then Debug.beginFile("CriticalStrike") end
--[[
/* ------------------------ CriticalStrike v2.4 by Chopinski ----------------------- */
CriticalStrike implements an easy way to register and detect a custom critical event.
allows the manipulation of a unit critical strike chance and multiplier, as well as
manipulating the critical damage dealt.
It works by monitoring custom critical strike chance and multiplier values given to units.
It will only detect custom critical strikes, so all critical chance given to a
unit must be done so using the public API provided by this system.
*CriticalStrike requires DamageInterface. Do not use TriggerSleepAction() with Evasion.
It also requires optional Evasion so that this library is written after the Evasion
library, so both custom events will not fire at the same time.
The API:
function RegisterCriticalStrikeEvent(function YourFunction)
-> YourFunction will run when a unit hits a critical strike.
function GetCriticalSource takes nothing returns unit
-> Returns the unit hitting a critical strike.
function GetCriticalTarget takes nothing returns unit
-> Returns the unit being hit by a critical strike.
function GetCriticalDamage takes nothing returns real
-> Returns the critical strike damage amount.
function GetUnitCriticalChance takes unit u returns real
-> Returns the chance to hit a critical strike to specified unit.
function GetUnitCriticalMultiplier takes unit u returns real
-> Returns the chance to hit a critical strike to specified unit.
function SetUnitCriticalChance takes unit u, real value returns nothing
-> Set's the unit chance to hit a critical strike to specified value.
-> 15.0 = 15%
function SetUnitCriticalMultiplier takes unit u, real value returns nothing
-> Set's the unit multiplier of damage when hitting a critical to value
-> 1.0 = increases the multiplier by 1. all units have a multiplier of 1.0
by default, so by adding 1.0, for example, the critical damage will be
2x the normal damage
function SetCriticalEventDamage takes real newValue returns nothing
-> Modify the critical damage dealt to the specified value.
function UnitAddCriticalStrike takes unit u, real chance, real multiplier returns nothing
-> Adds the specified values of chance and multiplier to a unit
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
local TEXT_SIZE = 0.016
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
Critical = {
source,
target,
damage,
chance = {},
multiplier = {}
}
local event = {}
local function Text(unit, text, duration, red, green, blue, alpha)
local texttag = CreateTextTag()
SetTextTagText(texttag, text, TEXT_SIZE)
SetTextTagPosUnit(texttag, unit, 0)
SetTextTagColor(texttag, red, green, blue, alpha)
SetTextTagLifespan(texttag, duration)
SetTextTagVelocity(texttag, 0.0, 0.0355)
SetTextTagPermanent(texttag, false)
end
OnInit.global(function()
RegisterAttackDamageEvent(function()
local damage = GetEventDamage()
if damage > 0 and GetRandomReal(0, 100) <= (Critical.chance[Damage.source.unit] or 0) and Damage.isEnemy and not Damage.target.isStructure and (Critical.multiplier[Damage.source.unit] or 0) > 0 then
Critical.source = Damage.source
Critical.target = Damage.target
Critical.damage = damage*(1 + (Critical.multiplier[Damage.source.unit] or 0))
for i = 1, #event do
event[i]()
end
BlzSetEventDamage(Critical.damage)
if Critical.damage > 0 then
Text(Critical.target.unit, (I2S(R2I(Critical.damage)) .. "!"), 1.5, 255, 0, 0, 255)
end
Critical.source = nil
Critical.target = nil
Critical.damage = 0
end
end)
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function RegisterCriticalStrikeEvent(code)
if type(code) == "function" then
table.insert(event, code)
end
end
function GetCriticalSource()
return Critical.source.unit
end
function GetCriticalTarget()
return Critical.target.unit
end
function GetCriticalDamage()
return Critical.damage or 0
end
function GetUnitCriticalChance(unit)
return Critical.chance[unit] or 0
end
function GetUnitCriticalMultiplier(unit)
return Critical.multiplier[unit] or 0
end
function SetUnitCriticalChance(unit, real)
Critical.chance[unit] = real
end
function SetUnitCriticalMultiplier(unit, real)
Critical.multiplier[unit] = real
end
function SetCriticalEventDamage(real)
Critical.damage = real
end
function UnitAddCriticalStrike(unit, chance, multiplier)
if not Critical.chance[unit] then Critical.chance[unit] = 0 end
if not Critical.multiplier[unit] then Critical.multiplier[unit] = 0 end
Critical.chance[unit] = Critical.chance[unit] + chance
Critical.multiplier[unit] = Critical.multiplier[unit] + multiplier
end
end
if Debug and Debug.endFile then Debug.endFile() end
--[[
/* ------------------------ SpellPower 2.4 by Chopinski ----------------------- */
SpellPower intends to simulate a system similiar to Ability Power from LoL or
Spell Amplification from Dota 2.
Whenever a units deals Spell damage, that dealt damage will be amplified by a flat
and percentile amount that represents a unit spell power bonus.
The formula for amplification is:
final damage = (initial damage + flat bonus) * (1 + percent bonus)
for percent bonus: 0.1 = 10% bonus
*SpellPower requires DamageInterface. Do not use TriggerSleepAction() within triggers.
The API:
function GetUnitSpellPowerFlat takes unit u returns real
-> Returns the Flat bonus of spell power of a unit
function GetUnitSpellPowerPercent takes unit u returns real
-> Returns the Percent bonus of spell power of a unit
function SetUnitSpellPowerFlat takes unit u, real value returns nothing
-> Set the Flat amount of Spell Power of a unit
function SetUnitSpellPowerPercent takes unit u, real value returns nothing
-> Set the Flat amount of Spell Power of a unit
function UnitAddSpellPowerFlat takes unit u, real amount returns nothing
-> Add to the Flat amount of Spell Power of a unit
function UnitAddSpellPowerPercent takes unit u, real amount returns nothing
-> Add to the Percent amount of Spell Power of a unit
function GetSpellDamage takes real amount, unit u returns real
-> Returns the amount of spell damage that would be dealt given an initial damage
]]--
do
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
SpellPower = {
flat = {},
percent = {}
}
onInit(function()
RegisterSpellDamageEvent(function()
local damage = GetEventDamage()
if damage > 0 then
BlzSetEventDamage((damage + (SpellPower.flat[Damage.source.unit] or 0))*(1 + (SpellPower.percent[Damage.source.unit] or 0)))
end
end)
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function GetUnitSpellPowerFlat(unit)
return SpellPower.flat[unit] or 0
end
function GetUnitSpellPowerPercent(unit)
return SpellPower.percent[unit] or 0
end
function SetUnitSpellPowerFlat(unit, real)
SpellPower.flat[unit] = real
end
function SetUnitSpellPowerPercent(unit, real)
SpellPower.percent[unit] = real
end
function UnitAddSpellPowerFlat(unit, real)
if not SpellPower.flat[unit] then SpellPower.flat[unit] = 0 end
SpellPower.flat[unit] = SpellPower.flat[unit] + real
end
function UnitAddSpellPowerPercent(unit, real)
if not SpellPower.percent[unit] then SpellPower.percent[unit] = 0 end
SpellPower.percent[unit] = SpellPower.percent[unit] + real
end
function GetSpellDamage(unit, real)
return (real + (SpellPower.flat[unit] or 0))*(1 + (SpellPower.percent[unit] or 0))
end
end
if Debug and Debug.beginFile then Debug.beginFile("SpellPower") end
--[[
/* ------------------------ SpellPower 2.4 by Chopinski ----------------------- */
SpellPower intends to simulate a system similiar to Ability Power from LoL or
Spell Amplification from Dota 2.
Whenever a units deals Spell damage, that dealt damage will be amplified by a flat
and percentile amount that represents a unit spell power bonus.
The formula for amplification is:
final damage = (initial damage + flat bonus) * (1 + percent bonus)
for percent bonus: 0.1 = 10% bonus
*SpellPower requires DamageInterface. Do not use TriggerSleepAction() within triggers.
The API:
function GetUnitSpellPowerFlat takes unit u returns real
-> Returns the Flat bonus of spell power of a unit
function GetUnitSpellPowerPercent takes unit u returns real
-> Returns the Percent bonus of spell power of a unit
function SetUnitSpellPowerFlat takes unit u, real value returns nothing
-> Set the Flat amount of Spell Power of a unit
function SetUnitSpellPowerPercent takes unit u, real value returns nothing
-> Set the Flat amount of Spell Power of a unit
function UnitAddSpellPowerFlat takes unit u, real amount returns nothing
-> Add to the Flat amount of Spell Power of a unit
function UnitAddSpellPowerPercent takes unit u, real amount returns nothing
-> Add to the Percent amount of Spell Power of a unit
function GetSpellDamage takes real amount, unit u returns real
-> Returns the amount of spell damage that would be dealt given an initial damage
]]--
do
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
SpellPower = {
flat = {},
percent = {}
}
OnInit.global(function()
RegisterSpellDamageEvent(function()
local damage = GetEventDamage()
if damage > 0 then
BlzSetEventDamage((damage + (SpellPower.flat[Damage.source.unit] or 0))*(1 + (SpellPower.percent[Damage.source.unit] or 0)))
end
end)
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function GetUnitSpellPowerFlat(unit)
return SpellPower.flat[unit] or 0
end
function GetUnitSpellPowerPercent(unit)
return SpellPower.percent[unit] or 0
end
function SetUnitSpellPowerFlat(unit, real)
SpellPower.flat[unit] = real
end
function SetUnitSpellPowerPercent(unit, real)
SpellPower.percent[unit] = real
end
function UnitAddSpellPowerFlat(unit, real)
if not SpellPower.flat[unit] then SpellPower.flat[unit] = 0 end
SpellPower.flat[unit] = SpellPower.flat[unit] + real
end
function UnitAddSpellPowerPercent(unit, real)
if not SpellPower.percent[unit] then SpellPower.percent[unit] = 0 end
SpellPower.percent[unit] = SpellPower.percent[unit] + real
end
function GetSpellDamage(unit, real)
return (real + (SpellPower.flat[unit] or 0))*(1 + (SpellPower.percent[unit] or 0))
end
end
if Debug and Debug.endFile then Debug.endFile() end
--[[
/* ------------------------ LifeSteal v2.4 by Chopinski ----------------------- */
LifeSteal intends to simulate the Life Steal system in warcraft, and allow you
to easily change the life steal amount of any unit.
Whenever a unit deals Physical damage, and it has a value of life steal given by
this system, it will heal based of this value and the damage amount.
The formula for life steal is:
heal = damage * life steal
fror life steal: 0.1 = 10%
*LifeSteal requires DamageInterface. Do not use TriggerSleepAction() within triggers.
The API:
function SetUnitLifeSteal takes unit u, real amount returns nothing
-> Set the Life Steal amount for a unit
function GetUnitLifeSteal takes unit u returns real
-> Returns the Life Steal amount of a unit
function UnitAddLifeSteal takes unit u, real amount returns nothing
-> Add to the Life Steal amount of a unit the given amount
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
local effect = "Abilities\\Spells\\Undead\\VampiricAura\\VampiricAuraTarget.mdl"
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
LifeSteal = {}
onInit(function()
RegisterAttackDamageEvent(function()
local damage = GetEventDamage()
if damage > 0 and (LifeSteal[Damage.source.unit] or 0) > 0 and not Damage.target.isStructure then
SetWidgetLife(Damage.source.unit, (GetWidgetLife(Damage.source.unit) + (damage * (LifeSteal[Damage.source.unit] or 0))))
DestroyEffect(AddSpecialEffectTarget(effect, Damage.source.unit, "origin"))
end
end)
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function SetUnitLifeSteal(unit, real)
LifeSteal[unit] = real
end
function GetUnitLifeSteal(unit)
return LifeSteal[unit] or 0
end
function UnitAddLifeSteal(unit, real)
if not LifeSteal[unit] then LifeSteal[unit] = 0 end
LifeSteal[unit] = LifeSteal[unit] + real
end
end
if Debug and Debug.beginFile then Debug.beginFile("LifeSteal") end
--[[
/* ------------------------ LifeSteal v2.4 by Chopinski ----------------------- */
LifeSteal intends to simulate the Life Steal system in warcraft, and allow you
to easily change the life steal amount of any unit.
Whenever a unit deals Physical damage, and it has a value of life steal given by
this system, it will heal based of this value and the damage amount.
The formula for life steal is:
heal = damage * life steal
fror life steal: 0.1 = 10%
*LifeSteal requires DamageInterface. Do not use TriggerSleepAction() within triggers.
The API:
function SetUnitLifeSteal takes unit u, real amount returns nothing
-> Set the Life Steal amount for a unit
function GetUnitLifeSteal takes unit u returns real
-> Returns the Life Steal amount of a unit
function UnitAddLifeSteal takes unit u, real amount returns nothing
-> Add to the Life Steal amount of a unit the given amount
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
local effect = "Abilities\\Spells\\Undead\\VampiricAura\\VampiricAuraTarget.mdl"
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
LifeSteal = {}
OnInit.global(function()
RegisterAttackDamageEvent(function()
local damage = GetEventDamage()
if damage > 0 and (LifeSteal[Damage.source.unit] or 0) > 0 and not Damage.target.isStructure then
SetWidgetLife(Damage.source.unit, (GetWidgetLife(Damage.source.unit) + (damage * (LifeSteal[Damage.source.unit] or 0))))
DestroyEffect(AddSpecialEffectTarget(effect, Damage.source.unit, "origin"))
end
end)
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function SetUnitLifeSteal(unit, real)
LifeSteal[unit] = real
end
function GetUnitLifeSteal(unit)
return LifeSteal[unit] or 0
end
function UnitAddLifeSteal(unit, real)
if not LifeSteal[unit] then LifeSteal[unit] = 0 end
LifeSteal[unit] = LifeSteal[unit] + real
end
end
if Debug and Debug.endFile then Debug.endFile() end
--[[
/* ------------------------ SpellVamp v2.4 by Chopinski ----------------------- */
SpellVamp intends to introduce to warcraft a healing based on Spell damage, like
in LoL or Dota 2.
Whenever a unit deals Spell damage, and it has a value of spell vamp given by
this system, it will heal based of this value and the damage amount.
The formula for spell vamp is:
heal = damage * lspell vamp
fror spell vamp: 0.1 = 10%
*SpellVamp requires DamageInterface. Do not use TriggerSleepAction() within triggers.
The API:
function SetUnitSpellVamp takes unit u, real amount returns nothing
-> Set the Spell Vamp amount for a unit
function GetUnitSpellVamp takes unit u returns real
-> Returns the Spell Vamp amount of a unit
function UnitAddSpellVamp takes unit u, real amount returns nothing
-> Add to the Spell Vamp amount of a unit the given amount
]]--
do
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
SpellVamp = {}
onInit(function()
RegisterSpellDamageEvent(function()
local damage = GetEventDamage()
if damage > 0 and (SpellVamp[Damage.source.unit] or 0) > 0 and not Damage.target.isStructure then
SetWidgetLife(Damage.source.unit, (GetWidgetLife(Damage.source.unit) + (damage * (SpellVamp[Damage.source.unit] or 0))))
end
end)
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function SetUnitSpellVamp(unit, real)
SpellVamp[unit] = real
end
function GetUnitSpellVamp(unit)
return SpellVamp[unit] or 0
end
function UnitAddSpellVamp(unit, real)
if not SpellVamp[unit] then SpellVamp[unit] = 0 end
SpellVamp[unit] = SpellVamp[unit] + real
end
end
if Debug and Debug.beginFile then Debug.beginFile("SpellVamp") end
--[[
/* ------------------------ SpellVamp v2.4 by Chopinski ----------------------- */
SpellVamp intends to introduce to warcraft a healing based on Spell damage, like
in LoL or Dota 2.
Whenever a unit deals Spell damage, and it has a value of spell vamp given by
this system, it will heal based of this value and the damage amount.
The formula for spell vamp is:
heal = damage * lspell vamp
fror spell vamp: 0.1 = 10%
*SpellVamp requires DamageInterface. Do not use TriggerSleepAction() within triggers.
The API:
function SetUnitSpellVamp takes unit u, real amount returns nothing
-> Set the Spell Vamp amount for a unit
function GetUnitSpellVamp takes unit u returns real
-> Returns the Spell Vamp amount of a unit
function UnitAddSpellVamp takes unit u, real amount returns nothing
-> Add to the Spell Vamp amount of a unit the given amount
]]--
do
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
SpellVamp = {}
OnInit.global(function()
RegisterSpellDamageEvent(function()
local damage = GetEventDamage()
if damage > 0 and (SpellVamp[Damage.source.unit] or 0) > 0 and not Damage.target.isStructure then
SetWidgetLife(Damage.source.unit, (GetWidgetLife(Damage.source.unit) + (damage * (SpellVamp[Damage.source.unit] or 0))))
end
end)
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function SetUnitSpellVamp(unit, real)
SpellVamp[unit] = real
end
function GetUnitSpellVamp(unit)
return SpellVamp[unit] or 0
end
function UnitAddSpellVamp(unit, real)
if not SpellVamp[unit] then SpellVamp[unit] = 0 end
SpellVamp[unit] = SpellVamp[unit] + real
end
end
if Debug and Debug.endFile then Debug.endFile() end
--[[ requires RegisterPlayerUnitEvent, Indexer
/* ------------------ Cooldown Reduction v1.9 by Chopinski ------------------ */
Intro
This library intension in to introduce to warcraft an easy way to
manipulate abilities cooldowns based on a cooldown reduction value that
is unique for each unit.
How it Works?
When casting an ability, its "new" cooldown is calculated based on the
amount of cooldown reduction of the casting unit. the formula for
calculation is:
Cooldown = (Default Cooldown - Cooldown Offset) * [(1 - source1)*(1 - source2)*...] * (1 - Cooldown Reduction Flat)
The system also allow negative values for CDR, resulting in increased
ability cooldown.
It does not acumulate because the abilities are registered automatically
on the first cast, saving its base cooldown (Object Editor values) and
always using this base value for calculation, so you can still edit
the ability via the editor and the system takes care of the rest.
How to Import
simply copy the CooldownReduction folder over to your map, and start
use the API functions
Requirements
CooldownReduction requires RegisterPlayerUnitEvent.
Credits to Magtheridon96 for RegisterPlayerUnitEvent.
It also requires patch 1.31+.
RegisterPlayerUnitEvent: www.hiveworkshop.com/threads/snippet-registerplayerunitevent.203338/
]]--
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
-- Use this function to filter out units you dont want to have abilities registered.
-- By default dummy units do not trigger the system.
local function UnitFilter(unit)
return GetUnitAbilityLevel(unit, FourCC('Aloc')) == 0
end
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
do
local units = {}
local abilities = {}
local defaults = {}
local set = {}
CDR = setmetatable({}, {})
local mt = getmetatable(CDR)
mt.__index = mt
function mt:update(unit)
local real
for i = 1, #abilities[unit] do
local k = abilities[unit][i]
local ability = BlzGetUnitAbility(unit, k)
local level = BlzGetAbilityIntegerField(ability, ABILITY_IF_LEVELS)
for j = 1, level do
if (units[unit].count or 0) > 0 then
real = ((defaults[k][j] - units[unit].offset) * units[unit].cooldown * (1 - units[unit].flat))
else
real = ((defaults[k][j] - units[unit].offset) * (1 - units[unit].flat))
end
BlzSetAbilityRealLevelField(ability, ABILITY_RLF_COOLDOWN, j-1, real)
IncUnitAbilityLevel(unit, k)
DecUnitAbilityLevel(unit, k)
end
end
end
function mt:set(unit, real, type)
if not units[unit] then self:create(unit) end
if type == 0 then
units[unit].cooldown = real
elseif type == 1 then
units[unit].flat = real
else
units[unit].offset = real
end
self:update(unit)
end
function mt:get(unit, type)
if not units[unit] then self:create(unit) end
if type == 0 then
return units[unit].cooldown or 0
elseif type == 1 then
return units[unit].flat or 0
else
return units[unit].offset or 0
end
end
function mt:calculate(unit)
local real = 0
if (#units[unit].cdr or 0) > 0 then
for i = 1, #units[unit].cdr do
if i > 1 then
real = real * (1 - units[unit].cdr[i])
else
real = 1 - units[unit].cdr[i]
end
end
end
return real
end
function mt:calculateCooldown(unit, id, level, cooldown)
if not units[unit] then
self:create(unit)
else
local ability = BlzGetUnitAbility(unit, id)
local real
if (#units[unit].cdr or 0) > 0 then
real = ((cooldown - units[unit].offset) * units[unit].cooldown * (1 - units[unit].flat))
else
real = ((cooldown - units[unit].offset) * (1 - units[unit].flat))
end
BlzSetAbilityRealLevelField(ability, ABILITY_RLF_COOLDOWN, level-1, real)
IncUnitAbilityLevel(unit, id)
DecUnitAbilityLevel(unit, id)
end
end
function mt:simulateCooldown(unit, cooldown)
if not units[unit] then
self:create(unit)
return cooldown
else
local real
if (#units[unit].cdr or 0) > 0 then
real = ((cooldown - units[unit].offset) * units[unit].cooldown * (1 - units[unit].flat))
else
real = ((cooldown - units[unit].offset) * (1 - units[unit].flat))
end
return real
end
end
function mt:add(unit, real, type)
if real ~= 0 then
if not units[unit] then self:create(unit) end
if type == 0 then
table.insert(units[unit].cdr, real)
units[unit].count = units[unit].count + 1
units[unit].cooldown = self:calculate(unit)
elseif type == 1 then
units[unit].flat = units[unit].flat + real
else
units[unit].offset = units[unit].offset + real
end
self:update(unit)
end
end
function mt:remove(unit, real)
if real ~= 0 then
if not units[unit] then
self:create(unit)
return
end
for j = 1, #units[unit].cdr do
if units[unit].cdr[j] == real then
table.remove(units[unit].cdr, j)
units[unit].count = units[unit].count - 1
units[unit].cooldown = self:calculate(unit)
break
end
end
self:update(unit)
end
end
function mt:create(unit)
abilities[unit] = {}
set[unit] = {}
units[unit] = {}
units[unit].cdr = {}
units[unit].cooldown = 0
units[unit].offset = 0
units[unit].flat = 0
units[unit].count = 0
end
function mt:destroy(unit)
if abilities[unit] then
for i = 1, #abilities[unit] do
defaults[abilities[unit][i]] = nil
end
end
abilities[unit] = nil
set[unit] = nil
units[unit] = nil
units[unit].cdr = nil
units[unit].cooldown = nil
units[unit].offset = nil
units[unit].flat = nil
units[unit].count = nil
end
function mt:register(unit, id)
if UnitFilter(unit) then
if not units[unit] then self:create(unit) end
if not set[unit][id] then
set[unit][id] = true
table.insert(abilities[unit], id)
if not defaults[id] then defaults[id] = {} end
local ability = BlzGetUnitAbility(unit, id)
local levels = BlzGetAbilityIntegerField(ability, ABILITY_IF_LEVELS)
for i = 1, levels do
defaults[id][i] = BlzGetAbilityRealLevelField(ability, ABILITY_RLF_COOLDOWN, i - 1)
end
if (units[unit].count or 0) > 0 or units[unit].cooldown or units[unit].offset or units[unit].flat then
self:update(unit)
end
end
end
end
onInit(function()
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function()
CDR:register(GetTriggerUnit(), GetSpellAbilityId())
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_HERO_SKILL, function()
CDR:register(GetTriggerUnit(), GetLearnedSkill())
end)
RegisterUnitDeindexEvent(function()
CDR:destroy(GetIndexUnit())
end)
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function GetUnitCooldownReduction(unit)
return 1 - CDR:get(unit, 0)
end
function GetUnitCooldownReductionFlat(unit)
return CDR:get(unit, 1)
end
function GetUnitCooldownOffset(unit)
return CDR:get(unit, 2)
end
function SetUnitCooldownReduction(unit, real)
CDR:set(unit, real, 0)
end
function SetUnitCooldownReductionFlat(unit, real)
CDR:set(unit, real, 1)
end
function SetUnitCooldownOffset(unit, real)
CDR:set(unit, real, 2)
end
function UnitAddCooldownReduction(unit, real)
CDR:add(unit, real, 0)
end
function UnitAddCooldownReductionFlat(unit, real)
CDR:add(unit, real, 1)
end
function UnitAddCooldownOffset(unit, real)
CDR:add(unit, real, 2)
end
function UnitRemoveCooldownReduction(unit, real)
CDR:remove(unit, real)
end
function CalculateAbilityCooldown(unit, ability, level, cooldown)
CDR:calculateCooldown(unit, ability, level, cooldown)
end
function SimulateAbilityCooldown(unit, cooldown)
return CDR:simulateCooldown(unit, cooldown)
end
function RegisterAbility(unit, ability)
CDR:register(unit, ability)
end
function GetAbilityTable()
return units
end
end
if Debug and Debug.beginFile then Debug.beginFile("CooldownReduction") end
--[[ requires RegisterPlayerUnitEvent, Indexer
/* ------------------ Cooldown Reduction v1.9 by Chopinski ------------------ */
Intro
This library intension in to introduce to warcraft an easy way to
manipulate abilities cooldowns based on a cooldown reduction value that
is unique for each unit.
How it Works?
When casting an ability, its "new" cooldown is calculated based on the
amount of cooldown reduction of the casting unit. the formula for
calculation is:
Cooldown = (Default Cooldown - Cooldown Offset) * [(1 - source1)*(1 - source2)*...] * (1 - Cooldown Reduction Flat)
The system also allow negative values for CDR, resulting in increased
ability cooldown.
It does not acumulate because the abilities are registered automatically
on the first cast, saving its base cooldown (Object Editor values) and
always using this base value for calculation, so you can still edit
the ability via the editor and the system takes care of the rest.
How to Import
simply copy the CooldownReduction folder over to your map, and start
use the API functions
Requirements
CooldownReduction requires RegisterPlayerUnitEvent.
Credits to Magtheridon96 for RegisterPlayerUnitEvent.
It also requires patch 1.31+.
RegisterPlayerUnitEvent: www.hiveworkshop.com/threads/snippet-registerplayerunitevent.203338/
]]--
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
-- Use this function to filter out units you dont want to have abilities registered.
-- By default dummy units do not trigger the system.
do
local function UnitFilter(unit)
return GetUnitAbilityLevel(unit, FourCC('Aloc')) == 0
end
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
local units = {}
local abilities = {}
local defaults = {}
local set = {}
CDR = setmetatable({}, {})
local mt = getmetatable(CDR)
mt.__index = mt
function mt:update(unit)
local real
for i = 1, #abilities[unit] do
local k = abilities[unit][i]
local ability = BlzGetUnitAbility(unit, k)
local level = BlzGetAbilityIntegerField(ability, ABILITY_IF_LEVELS)
for j = 1, level do
if (units[unit].count or 0) > 0 then
real = ((defaults[k][j] - units[unit].offset) * units[unit].cooldown * (1 - units[unit].flat))
else
real = ((defaults[k][j] - units[unit].offset) * (1 - units[unit].flat))
end
BlzSetAbilityRealLevelField(ability, ABILITY_RLF_COOLDOWN, j-1, real)
IncUnitAbilityLevel(unit, k)
DecUnitAbilityLevel(unit, k)
end
end
end
function mt:set(unit, real, type)
if not units[unit] then self:create(unit) end
if type == 0 then
units[unit].cooldown = real
elseif type == 1 then
units[unit].flat = real
else
units[unit].offset = real
end
self:update(unit)
end
function mt:get(unit, type)
if not units[unit] then self:create(unit) end
if type == 0 then
return units[unit].cooldown or 0
elseif type == 1 then
return units[unit].flat or 0
else
return units[unit].offset or 0
end
end
function mt:calculate(unit)
local real = 0
if (#units[unit].cdr or 0) > 0 then
for i = 1, #units[unit].cdr do
if i > 1 then
real = real * (1 - units[unit].cdr[i])
else
real = 1 - units[unit].cdr[i]
end
end
end
return real
end
function mt:calculateCooldown(unit, id, level, cooldown)
if not units[unit] then
self:create(unit)
else
local ability = BlzGetUnitAbility(unit, id)
local real
if (#units[unit].cdr or 0) > 0 then
real = ((cooldown - units[unit].offset) * units[unit].cooldown * (1 - units[unit].flat))
else
real = ((cooldown - units[unit].offset) * (1 - units[unit].flat))
end
BlzSetAbilityRealLevelField(ability, ABILITY_RLF_COOLDOWN, level-1, real)
IncUnitAbilityLevel(unit, id)
DecUnitAbilityLevel(unit, id)
end
end
function mt:simulateCooldown(unit, cooldown)
if not units[unit] then
self:create(unit)
return cooldown
else
local real
if (#units[unit].cdr or 0) > 0 then
real = ((cooldown - units[unit].offset) * units[unit].cooldown * (1 - units[unit].flat))
else
real = ((cooldown - units[unit].offset) * (1 - units[unit].flat))
end
return real
end
end
function mt:add(unit, real, type)
if real ~= 0 then
if not units[unit] then self:create(unit) end
if type == 0 then
table.insert(units[unit].cdr, real)
units[unit].count = units[unit].count + 1
units[unit].cooldown = self:calculate(unit)
elseif type == 1 then
units[unit].flat = units[unit].flat + real
else
units[unit].offset = units[unit].offset + real
end
self:update(unit)
end
end
function mt:remove(unit, real)
if real ~= 0 then
if not units[unit] then
self:create(unit)
return
end
for j = 1, #units[unit].cdr do
if units[unit].cdr[j] == real then
table.remove(units[unit].cdr, j)
units[unit].count = units[unit].count - 1
units[unit].cooldown = self:calculate(unit)
break
end
end
self:update(unit)
end
end
function mt:create(unit)
abilities[unit] = {}
set[unit] = {}
units[unit] = {}
units[unit].cdr = {}
units[unit].cooldown = 0
units[unit].offset = 0
units[unit].flat = 0
units[unit].count = 0
end
function mt:destroy(unit)
if abilities[unit] then
for i = 1, #abilities[unit] do
defaults[abilities[unit][i]] = nil
end
end
abilities[unit] = nil
set[unit] = nil
units[unit].cdr = nil
units[unit].cooldown = nil
units[unit].offset = nil
units[unit].flat = nil
units[unit].count = nil
units[unit] = nil
end
function mt:register(unit, id)
if UnitFilter(unit) then
if not units[unit] then self:create(unit) end
if not set[unit][id] then
set[unit][id] = true
table.insert(abilities[unit], id)
if not defaults[id] then defaults[id] = {} end
local ability = BlzGetUnitAbility(unit, id)
local levels = BlzGetAbilityIntegerField(ability, ABILITY_IF_LEVELS)
for i = 1, levels do
defaults[id][i] = BlzGetAbilityRealLevelField(ability, ABILITY_RLF_COOLDOWN, i - 1)
end
if (units[unit].count or 0) > 0 or units[unit].cooldown or units[unit].offset or units[unit].flat then
self:update(unit)
end
end
end
end
OnInit.global(function()
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function()
CDR:register(GetTriggerUnit(), GetSpellAbilityId())
end)
RegisterPlayerUnitEvent(EVENT_PLAYER_HERO_SKILL, function()
CDR:register(GetTriggerUnit(), GetLearnedSkill())
end)
RegisterUnitDeindexEvent(function()
if units[GetIndexUnit()] then
CDR:destroy(GetIndexUnit())
end
end)
end)
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function GetUnitCooldownReduction(unit)
return 1 - CDR:get(unit, 0)
end
function GetUnitCooldownReductionFlat(unit)
return CDR:get(unit, 1)
end
function GetUnitCooldownOffset(unit)
return CDR:get(unit, 2)
end
function SetUnitCooldownReduction(unit, real)
CDR:set(unit, real, 0)
end
function SetUnitCooldownReductionFlat(unit, real)
CDR:set(unit, real, 1)
end
function SetUnitCooldownOffset(unit, real)
CDR:set(unit, real, 2)
end
function UnitAddCooldownReduction(unit, real)
CDR:add(unit, real, 0)
end
function UnitAddCooldownReductionFlat(unit, real)
CDR:add(unit, real, 1)
end
function UnitAddCooldownOffset(unit, real)
CDR:add(unit, real, 2)
end
function UnitRemoveCooldownReduction(unit, real)
CDR:remove(unit, real)
end
function CalculateAbilityCooldown(unit, ability, level, cooldown)
CDR:calculateCooldown(unit, ability, level, cooldown)
end
function SimulateAbilityCooldown(unit, cooldown)
return CDR:simulateCooldown(unit, cooldown)
end
function RegisterAbility(unit, ability)
CDR:register(unit, ability)
end
function GetAbilityTable()
return units
end
end
if Debug and Debug.endFile then Debug.endFile() end
--[[
/* --------------- Cooldown Reduction Utils v1.9 by Chopinski --------------- */
Intro
Utility Library that include a few extra functions to deal with
Cooldown Reduction
The API
function UnitAddCooldownReductionTimed takes unit u, real value, real duration returns nothing
-> Add to the amount of cdr of a unit for a given duration. Accepts positive and negative values.
-> It handles removing the bonus automatically
function UnitAddCooldownReductionFlatTimed takes unit u, real value, real duration returns nothing
-> Add to the amount of cdr flat of a unit for a given period. Accepts positive and negative values.
-> It handles removing the bonus automatically
function UnitAddCooldownOffsetTimed takes unit u, real value, real duration returns nothing
-> Add to the amount of cdr offset of a unit for a given period. Accepts positive and negative values.
-> It handles removing the bonus automatically
function GetUnitCooldownReductionEx takes unit u returns string
-> Returns the amount of cdr a unit has as a string factored by 100
-> example of return: 10.50 -> 0.105 internally.
function GetUnitCooldownReductionFlatEx takes unit u returns string
-> Returns the amount of cdr flat a unit has as a string factored by 100
-> example of return: 10.50 -> 0.105 internally.
function GetUnitCooldownOffsetEx takes unit u returns string
-> Returns the amount of cdr offset a unit has as a string
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
local PERIOD = 0.03125000
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
CDRUtils = setmetatable({}, {})
local mt = getmetatable(CDRUtils)
mt.__index = mt
local array = {}
local key = 0
local timer = CreateTimer()
function mt:remove(i)
if self.type == 0 then
UnitRemoveCooldownReduction(self.unit, self.value)
elseif self.type == 1 then
UnitAddCooldownReductionFlat(self.unit, -self.value)
else
UnitAddCooldownOffset(self.unit, -self.value)
end
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:addTimed(unit, value, duration, type)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.value = value
this.type = type
this.ticks = duration/PERIOD
key = key + 1
array[key] = this
if type == 0 then
UnitAddCooldownReduction(unit, value)
elseif type == 1 then
UnitAddCooldownReductionFlat(unit, value)
else
UnitAddCooldownOffset(unit, value)
end
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.ticks <= 0 then
i = this:remove(i)
end
this.ticks = this.ticks - 1
i = i + 1
end
end)
end
end
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function UnitAddCooldownReductionTimed(unit, value, duration)
CDRUtils:addTimed(unit, value, duration, 0)
end
function UnitAddCooldownReductionFlatTimed(unit, value, duration)
CDRUtils:addTimed(unit, value, duration, 1)
end
function UnitAddCooldownOffsetTimed(unit, value, duration)
CDRUtils:addTimed(unit, value, duration, 2)
end
function GetUnitCooldownReductionEx(unit)
return R2SW(CDR:get(unit, 0)*100, 1, 2)
end
function GetUnitCooldownReductionFlatEx(unit)
return R2SW(CDR:get(unit, 1)*100, 1, 2)
end
function GetUnitCooldownOffsetEx(unit)
return R2SW(CDR:get(u, 2), 1, 2)
end
end
if Debug and Debug.beginFile then Debug.beginFile("CooldownReductionUtils") end
--[[
/* --------------- Cooldown Reduction Utils v1.9 by Chopinski --------------- */
Intro
Utility Library that include a few extra functions to deal with
Cooldown Reduction
The API
function UnitAddCooldownReductionTimed takes unit u, real value, real duration returns nothing
-> Add to the amount of cdr of a unit for a given duration. Accepts positive and negative values.
-> It handles removing the bonus automatically
function UnitAddCooldownReductionFlatTimed takes unit u, real value, real duration returns nothing
-> Add to the amount of cdr flat of a unit for a given period. Accepts positive and negative values.
-> It handles removing the bonus automatically
function UnitAddCooldownOffsetTimed takes unit u, real value, real duration returns nothing
-> Add to the amount of cdr offset of a unit for a given period. Accepts positive and negative values.
-> It handles removing the bonus automatically
function GetUnitCooldownReductionEx takes unit u returns string
-> Returns the amount of cdr a unit has as a string factored by 100
-> example of return: 10.50 -> 0.105 internally.
function GetUnitCooldownReductionFlatEx takes unit u returns string
-> Returns the amount of cdr flat a unit has as a string factored by 100
-> example of return: 10.50 -> 0.105 internally.
function GetUnitCooldownOffsetEx takes unit u returns string
-> Returns the amount of cdr offset a unit has as a string
]]--
OnInit.main(function()
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
local PERIOD = 0.03125000
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
CDRUtils = setmetatable({}, {})
local mt = getmetatable(CDRUtils)
mt.__index = mt
local array = {}
local key = 0
local timer = CreateTimer()
function mt:remove(i)
if self.type == 0 then
UnitRemoveCooldownReduction(self.unit, self.value)
elseif self.type == 1 then
UnitAddCooldownReductionFlat(self.unit, -self.value)
else
UnitAddCooldownOffset(self.unit, -self.value)
end
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:addTimed(unit, value, duration, type)
local this = {}
setmetatable(this, mt)
this.unit = unit
this.value = value
this.type = type
this.ticks = duration/PERIOD
key = key + 1
array[key] = this
if type == 0 then
UnitAddCooldownReduction(unit, value)
elseif type == 1 then
UnitAddCooldownReductionFlat(unit, value)
else
UnitAddCooldownOffset(unit, value)
end
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
local this
while i <= key do
this = array[i]
if this.ticks <= 0 then
i = this:remove(i)
end
this.ticks = this.ticks - 1
i = i + 1
end
end)
end
end
-- -------------------------------------------------------------------------- --
-- LUA API --
-- -------------------------------------------------------------------------- --
function UnitAddCooldownReductionTimed(unit, value, duration)
CDRUtils:addTimed(unit, value, duration, 0)
end
function UnitAddCooldownReductionFlatTimed(unit, value, duration)
CDRUtils:addTimed(unit, value, duration, 1)
end
function UnitAddCooldownOffsetTimed(unit, value, duration)
CDRUtils:addTimed(unit, value, duration, 2)
end
function GetUnitCooldownReductionEx(unit)
return R2SW(CDR:get(unit, 0)*100, 1, 2)
end
function GetUnitCooldownReductionFlatEx(unit)
return R2SW(CDR:get(unit, 1)*100, 1, 2)
end
function GetUnitCooldownOffsetEx(unit)
return R2SW(CDR:get(u, 2), 1, 2)
end
end)
if Debug and Debug.endFile then Debug.endFile() end
--[[ requires RegisterPlayerUnitEvent, NewBonusUtils
/* ---------------------- Immolation v1.2 by Chopinski ---------------------- */
// Credits:
// Blizzard - Icon
// Magtheridon96 - RegisterPlayerUnitEvent
// Vexorian - TimerUtils
// Mythic - Immolation Effect (Edited by me)
/* ----------------------------------- END ---------------------------------- */
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
-- The raw code of the Immolation ability
local ABILITY = FourCC('A032')
-- The raw code of the Immolation buff
local BUFF = FourCC('B000')
-- The immolation damage period
local PERIOD = 1.
-- The immolation Damage model
local MODEL = "Abilities\\Spells\\NightElf\\Immolation\\ImmolationDamage.mdl"
-- The immolation Damage model
local ATTACH_POINT = "head"
-- The immolation AoE
local function GetAoE(unit, level)
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ABILITY), ABILITY_RLF_AREA_OF_EFFECT, level - 1)
end
-- The Immolation damage
local function GetDamage(level)
--return 10.*level
if level == 4 then
return 30
elseif level == 3 then
return 20
elseif level == 2 then
return 15
elseif level == 1 then
return 10
end
end
-- The Immolation armor debuff duration
local function GetDuration(level)
return 5. + 0.*level
end
local function DamageFilter(player, unit)
return UnitAlive(unit) and IsUnitEnemy(unit, player) and not IsUnitType(unit, UNIT_TYPE_MAGIC_IMMUNE)
end
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
local n = {}
onInit(function()
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function()
local unit = GetOrderedUnit()
if not n[unit] and GetIssuedOrderId() == 852177 then
local timer = CreateTimer()
local group = CreateGroup()
local player = GetOwningPlayer(unit)
n[unit] = true
LinkEffectToBuff(unit, BUFF, "Ember Green.mdl", "chest")
TimerStart(timer, PERIOD, true, function()
if GetUnitAbilityLevel(unit, BUFF) > 0 then
local level = GetUnitAbilityLevel(unit, ABILITY)
GroupEnumUnitsInRange(group, GetUnitX(unit), GetUnitY(unit), GetAoE(unit, level), nil)
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
if DamageFilter(player, u) then
if UnitDamageTarget(unit, u, GetDamage(level), false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, nil) then
DestroyEffect(AddSpecialEffectTarget(MODEL, u, ATTACH_POINT))
AddUnitBonusTimed(u, BONUS_ARMOR, -1, GetDuration(level))
end
end
end
else
n[unit] = nil
DestroyGroup(group)
PauseTimer(timer)
DestroyTimer(timer)
end
end)
end
end)
end)
end
if Debug and Debug.beginFile then Debug.beginFile("Immolation") end
--[[ requires RegisterPlayerUnitEvent, NewBonusUtils
/* ---------------------- Immolation v1.2 by Chopinski ---------------------- */
// Credits:
// Blizzard - Icon
// Magtheridon96 - RegisterPlayerUnitEvent
// Vexorian - TimerUtils
// Mythic - Immolation Effect (Edited by me)
/* ----------------------------------- END ---------------------------------- */
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
-- The raw code of the Immolation ability
local ABILITY = FourCC('A032')
-- The raw code of the Immolation buff
local BUFF = FourCC('B000')
-- The immolation damage period
local PERIOD = 1.
-- The immolation Damage model
local MODEL = "Abilities\\Spells\\NightElf\\Immolation\\ImmolationDamage.mdl"
-- The immolation Damage model
local ATTACH_POINT = "head"
-- The immolation AoE
local function GetAoE(unit, level)
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ABILITY), ABILITY_RLF_AREA_OF_EFFECT, level - 1)
end
-- The Immolation damage
local function GetDamage(level)
--return 10.*level
if level == 4 then
return 30
elseif level == 3 then
return 20
elseif level == 2 then
return 15
elseif level == 1 then
return 10
end
end
-- The Immolation armor debuff duration
local function GetDuration(level)
return 5. + 0.*level
end
local function DamageFilter(player, unit)
return UnitAlive(unit) and IsUnitEnemy(unit, player) and not IsUnitType(unit, UNIT_TYPE_MAGIC_IMMUNE)
end
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
local n = {}
OnInit.global(function()
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function()
local unit = GetOrderedUnit()
if not n[unit] and GetIssuedOrderId() == 852177 then
local timer = CreateTimer()
local group = CreateGroup()
local player = GetOwningPlayer(unit)
n[unit] = true
LinkEffectToBuff(unit, BUFF, "Ember Green.mdl", "chest")
TimerStart(timer, PERIOD, true, function()
if GetUnitAbilityLevel(unit, BUFF) > 0 then
local level = GetUnitAbilityLevel(unit, ABILITY)
GroupEnumUnitsInRange(group, GetUnitX(unit), GetUnitY(unit), GetAoE(unit, level), nil)
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
if DamageFilter(player, u) then
if UnitDamageTarget(unit, u, GetDamage(level), false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, nil) then
DestroyEffect(AddSpecialEffectTarget(MODEL, u, ATTACH_POINT))
AddUnitBonusTimed(u, BONUS_ARMOR, -1, GetDuration(level))
end
end
end
else
n[unit] = nil
DestroyGroup(group)
PauseTimer(timer)
DestroyTimer(timer)
end
end)
end
end)
end)
end
if Debug and Debug.endFile then Debug.endFile() end
--[[ requires RegisterPlayerUnitEvent, NewBonus
/* -------------------- Adaptive Strike v1.2 by Chopinski ------------------- */
// Credits:
// Blizzard - Icon
// Magtheridon96 - RegisterPlayerUnitEvent
// Mythic - Culling Slash and Cleave Effects
/* ----------------------------------- END ---------------------------------- */
]]--
do
-- -------------------------------------------------------------------------- --
-- 25, 33, 50, 65 Configuration --
-- -------------------------------------------------------------------------- --
-- The raw code of the Adaptive Strike ability
local ABILITY = FourCC('A033')
-- The Adaptive Strike Slash model
local SLASH = "Culling Slash.mdl"
-- The Adaptive Strike Cleave model
local CLEAVE = "Culling Cleave.mdl"
-- The Adaptive Strike proc chance
local function GetChance(level)
if level == 4 then
return GetRandomInt(1, 5) >= 1 -- 20%
elseif level == 3 then
return GetRandomInt(1, 6) <= 1 -- 17%
elseif level == 2 then
return GetRandomInt(1, 7) <= 1 -- 14%
elseif level == 1 then
return GetRandomInt(1, 8) <= 1 -- 12%
end
end
-- The Adaptive Strike damage
local function GetDamage(unit, level)
if level == 4 then
return BlzGetUnitBaseDamage(unit, 0 ) + 50 + GetUnitBonus(unit, BONUS_DAMAGE)*0.5
elseif level == 3 then
return BlzGetUnitBaseDamage(unit, 0 ) + 40 + GetUnitBonus(unit, BONUS_DAMAGE)*0.5
elseif level == 2 then
return BlzGetUnitBaseDamage(unit, 0 ) + 35 + GetUnitBonus(unit, BONUS_DAMAGE)*0.5
elseif level == 1 then
return BlzGetUnitBaseDamage(unit, 0 ) + 30 + GetUnitBonus(unit, BONUS_DAMAGE)*0.5
end
-- return 130 + GetUnitBonus(unit, BONUS_DAMAGE)*0.5
end
-- The Adaptive Strike AoE
local function GetAoE(slash, level)
if slash then
return 250. + 0.*level
else
return 250. + 0.*level
end
end
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
local state = {}
onInit(function()
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED, function()
local unit = GetAttacker()
local level = GetUnitAbilityLevel(unit, ABILITY)
if level > 0 and IsUnitType(unit, UNIT_TYPE_MELEE_ATTACKER) and GetChance(level) then
state[unit] = GetRandomInt(1, 2)
if state[unit] == 1 then
SetUnitAnimationByIndex(unit, 4)
QueueUnitAnimation(unit, "Stand Ready")
DestroyEffect(AddSpecialEffectTarget(CLEAVE, unit, "origin"))
else
SetUnitAnimationByIndex(unit, 5)
QueueUnitAnimation(unit, "Stand Ready")
DestroyEffect(AddSpecialEffectTarget(SLASH, unit, "origin"))
end
end
end)
RegisterAttackDamageEvent(function()
if state[Damage.source.unit] then
local level = GetUnitAbilityLevel(Damage.source.unit, ABILITY)
if state[Damage.source.unit] == 1 then
UnitDamageCone(Damage.source.unit, Damage.source.x, Damage.source.y, GetUnitFacing(Damage.source.unit), 150, GetAoE(false, level), GetDamage(Damage.source.unit, level), ATTACK_TYPE_HERO, DAMAGE_TYPE_UNIVERSAL, false, true, false)
else
UnitDamageArea(Damage.source.unit, Damage.source.x, Damage.source.y, GetAoE(true, level), GetDamage(Damage.source.unit, level), ATTACK_TYPE_HERO, DAMAGE_TYPE_UNIVERSAL, false, true, false)
end
state[Damage.source.unit] = nil
end
end)
end)
end
if Debug and Debug.beginFile then Debug.beginFile("AdaptiveStrike") end
--[[ requires RegisterPlayerUnitEvent, NewBonus
/* -------------------- Adaptive Strike v1.2 by Chopinski ------------------- */
// Credits:
// Blizzard - Icon
// Magtheridon96 - RegisterPlayerUnitEvent
// Mythic - Culling Slash and Cleave Effects
/* ----------------------------------- END ---------------------------------- */
]]--
do
-- -------------------------------------------------------------------------- --
-- 25, 33, 50, 65 Configuration --
-- -------------------------------------------------------------------------- --
-- The raw code of the Adaptive Strike ability
local ABILITY = FourCC('A033')
-- The Adaptive Strike Slash model
local SLASH = "Culling Slash.mdl"
-- The Adaptive Strike Cleave model
local CLEAVE = "Culling Cleave.mdl"
-- The Adaptive Strike proc chance
local function GetChance(level)
if level == 4 then
return GetRandomInt(1, 5) >= 1 -- 20%
elseif level == 3 then
return GetRandomInt(1, 6) <= 1 -- 17%
elseif level == 2 then
return GetRandomInt(1, 7) <= 1 -- 14%
elseif level == 1 then
return GetRandomInt(1, 8) <= 1 -- 12%
end
end
-- The Adaptive Strike damage
local function GetDamage(unit, level)
if level == 4 then
return BlzGetUnitBaseDamage(unit, 0 ) + 35 + GetUnitBonus(unit, BONUS_DAMAGE)*0.5
elseif level == 3 then
return BlzGetUnitBaseDamage(unit, 0 ) + 30 + GetUnitBonus(unit, BONUS_DAMAGE)*0.5
elseif level == 2 then
return BlzGetUnitBaseDamage(unit, 0 ) + 25 + GetUnitBonus(unit, BONUS_DAMAGE)*0.5
elseif level == 1 then
return BlzGetUnitBaseDamage(unit, 0 ) + 20 + GetUnitBonus(unit, BONUS_DAMAGE)*0.5
end
-- return 130 + GetUnitBonus(unit, BONUS_DAMAGE)*0.5
end
-- The Adaptive Strike AoE
local function GetAoE(slash, level)
if slash then
return 250. + 0.*level
else
return 250. + 0.*level
end
end
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
local state = {}
OnInit.global(function()
RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED, function()
local unit = GetAttacker()
local level = GetUnitAbilityLevel(unit, ABILITY)
if level > 0 and IsUnitType(unit, UNIT_TYPE_MELEE_ATTACKER) and GetChance(level) then
state[unit] = GetRandomInt(1, 2)
if state[unit] == 1 then
SetUnitAnimationByIndex(unit, 4)
QueueUnitAnimation(unit, "Stand Ready")
DestroyEffect(AddSpecialEffectTarget(CLEAVE, unit, "origin"))
else
SetUnitAnimationByIndex(unit, 5)
QueueUnitAnimation(unit, "Stand Ready")
DestroyEffect(AddSpecialEffectTarget(SLASH, unit, "origin"))
end
end
end)
RegisterAttackDamageEvent(function()
if state[Damage.source.unit] then
local level = GetUnitAbilityLevel(Damage.source.unit, ABILITY)
if state[Damage.source.unit] == 1 then
UnitDamageCone(Damage.source.unit, Damage.source.x, Damage.source.y, GetUnitFacing(Damage.source.unit), 150, GetAoE(false, level), GetDamage(Damage.source.unit, level), ATTACK_TYPE_HERO, DAMAGE_TYPE_UNIVERSAL, false, true, false)
else
UnitDamageArea(Damage.source.unit, Damage.source.x, Damage.source.y, GetAoE(true, level), GetDamage(Damage.source.unit, level), ATTACK_TYPE_HERO, DAMAGE_TYPE_UNIVERSAL, false, true, false)
end
state[Damage.source.unit] = nil
end
end)
end)
end
if Debug and Debug.endFile then Debug.endFile() end
--[[ requires DamageInterface, SpellEffectEvent, Utilities, NewBonusUtils, CrowdControl, optional Metamorphosis
/* --------------------- Spell Shield v1.3 by Chopinski --------------------- */
// Credits:
// Darkfang - Icon
// Bribe - SpellEffectEvent
/* ----------------------------------- END ---------------------------------- */
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
-- The raw code of the Spell Shield ability
local ABILITY = FourCC('A034')
-- The raw code of the Spell Shield buff
local BUFF = FourCC('B033')
-- The Spell Shield model
local MODEL = "SpellShield.mdl"
-- Spell Shield block effect period
local PERIOD = 0.03125
-- The Adaptive Strike damage
local function GetConversion(level)
return 0.2 + 0.1*level
end
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
SpellShield = setmetatable({}, {})
local mt = getmetatable(SpellShield)
mt.__index = mt
local timer = CreateTimer()
local array = {}
local key = 0
function mt:destroy(i)
DestroyEffect(self.effect)
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:onDamage()
local damage = GetEventDamage()
if damage > 0 and GetUnitAbilityLevel(Damage.target.unit, BUFF) > 0 then
local this = {}
setmetatable(this, mt)
this.source = Damage.source.unit
this.target = Damage.target.unit
this.angle = AngleBetweenCoordinates(Damage.target.x, Damage.target.y, Damage.source.x, Damage.source.y)
this.x = Damage.target.x + 60*Cos(this.angle)
this.y = Damage.target.y + 60*Sin(this.angle)
this.count = 16
this.effect = AddSpecialEffect(MODEL, this.x, this.y)
key = key + 1
array[key] = this
damage = damage*(1 - GetConversion(GetUnitAbilityLevel(Damage.target.unit, ABILITY)))
if Metamorphosis then
if GetUnitAbilityLevel(Damage.target.unit, Metamorphosis_BUFF) > 0 then
this.height = GetUnitZ(Damage.target.unit) + 400
else
this.height = GetUnitZ(Damage.target.unit) + 100
end
else
this.height = GetUnitZ(Damage.target.unit) + 100
end
BlzSetSpecialEffectZ(this.effect, this.height)
BlzSetSpecialEffectScale(this.effect, 1.5)
BlzSetSpecialEffectYaw(this.effect, this.angle)
BlzSetSpecialEffectColor(this.effect, 0, 0, 255)
BlzSetEventDamage(damage)
LinkBonusToBuff(Damage.target.unit, BONUS_DAMAGE, damage, BUFF)
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
while i <= key do
local this = array[i]
if this.count <= 0 then
i = this:destroy(i)
else
this.x = GetUnitX(this.target)
this.y = GetUnitY(this.target)
this.angle = AngleBetweenCoordinates(this.x, this.y, GetUnitX(this.source), GetUnitY(this.source))
if Metamorphosis then
if GetUnitAbilityLevel(this.target, Metamorphosis_BUFF) > 0 then
this.height = GetUnitZ(this.target) + 400
else
this.height = GetUnitZ(this.target) + 100
end
else
this.height = GetUnitZ(this.target) + 100
end
BlzSetSpecialEffectPosition(this.effect, this.x + 60*Cos(this.angle), this.y + 60*Sin(this.angle), this.height)
BlzSetSpecialEffectYaw(this.effect, this.angle)
end
this.count = this.count - 1
i = i + 1
end
end)
end
end
end
onInit(function()
RegisterSpellEffectEvent(ABILITY, function()
UnitDispelAllCrowdControl(Spell.source.unit)
end)
RegisterSpellDamageEvent(function()
SpellShield:onDamage()
end)
RegisterAnyCrowdControlEvent(function()
if GetUnitAbilityLevel(GetCrowdControlUnit(), BUFF) > 0 then
SetCrowdControlDuration(0)
end
end)
end)
end
if Debug and Debug.beginFile then Debug.beginFile("SpellShield") end
--[[ requires DamageInterface, SpellEffectEvent, Utilities, NewBonusUtils, CrowdControl, optional Metamorphosis
/* --------------------- Spell Shield v1.3 by Chopinski --------------------- */
// Credits:
// Darkfang - Icon
// Bribe - SpellEffectEvent
/* ----------------------------------- END ---------------------------------- */
]]--
OnInit.root(function()
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
-- The raw code of the Spell Shield ability
local ABILITY = FourCC('A034')
-- The raw code of the Spell Shield buff
local BUFF = FourCC('B033')
-- The Spell Shield model
local MODEL = "SpellShield.mdl"
-- Spell Shield block effect period
local PERIOD = 0.03125
-- The Adaptive Strike damage
local function GetConversion(level)
return 0.2 + 0.1*level
end
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
SpellShield = setmetatable({}, {})
local mt = getmetatable(SpellShield)
mt.__index = mt
local timer = CreateTimer()
local array = {}
local key = 0
function mt:destroy(i)
DestroyEffect(self.effect)
array[i] = array[key]
key = key - 1
self = nil
if key == 0 then
PauseTimer(timer)
end
return i - 1
end
function mt:onDamage()
local damage = GetEventDamage()
if damage > 0 and GetUnitAbilityLevel(Damage.target.unit, BUFF) > 0 then
local this = {}
setmetatable(this, mt)
this.source = Damage.source.unit
this.target = Damage.target.unit
this.angle = AngleBetweenCoordinates(Damage.target.x, Damage.target.y, Damage.source.x, Damage.source.y)
this.x = Damage.target.x + 60*Cos(this.angle)
this.y = Damage.target.y + 60*Sin(this.angle)
this.count = 16
this.effect = AddSpecialEffect(MODEL, this.x, this.y)
key = key + 1
array[key] = this
damage = damage*(1 - GetConversion(GetUnitAbilityLevel(Damage.target.unit, ABILITY)))
if Metamorphosis then
if GetUnitAbilityLevel(Damage.target.unit, Metamorphosis_BUFF) > 0 then
this.height = GetUnitZ(Damage.target.unit) + 400
else
this.height = GetUnitZ(Damage.target.unit) + 100
end
else
this.height = GetUnitZ(Damage.target.unit) + 100
end
BlzSetSpecialEffectZ(this.effect, this.height)
BlzSetSpecialEffectScale(this.effect, 1.5)
BlzSetSpecialEffectYaw(this.effect, this.angle)
BlzSetSpecialEffectColor(this.effect, 0, 0, 255)
BlzSetEventDamage(damage)
LinkBonusToBuff(Damage.target.unit, BONUS_DAMAGE, damage, BUFF)
if key == 1 then
TimerStart(timer, PERIOD, true, function()
local i = 1
while i <= key do
local this = array[i]
if this.count <= 0 then
i = this:destroy(i)
else
this.x = GetUnitX(this.target)
this.y = GetUnitY(this.target)
this.angle = AngleBetweenCoordinates(this.x, this.y, GetUnitX(this.source), GetUnitY(this.source))
if Metamorphosis then
if GetUnitAbilityLevel(this.target, Metamorphosis_BUFF) > 0 then
this.height = GetUnitZ(this.target) + 400
else
this.height = GetUnitZ(this.target) + 100
end
else
this.height = GetUnitZ(this.target) + 100
end
BlzSetSpecialEffectPosition(this.effect, this.x + 60*Cos(this.angle), this.y + 60*Sin(this.angle), this.height)
BlzSetSpecialEffectYaw(this.effect, this.angle)
end
this.count = this.count - 1
i = i + 1
end
end)
end
end
end
OnInit.global(function()
RegisterSpellEffectEvent(ABILITY, function()
UnitDispelAllCrowdControl(Spell.source.unit)
end)
RegisterSpellDamageEvent(function()
SpellShield:onDamage()
end)
RegisterAnyCrowdControlEvent(function()
if GetUnitAbilityLevel(GetCrowdControlUnit(), BUFF) > 0 then
SetCrowdControlDuration(0)
end
end)
end)
end)
if Debug and Debug.endFile then Debug.endFile() end
--[[ requires SpellEffectEvent, Utilities, optional LightInfusion
/* --------------------- Divine Smite v1.2 by Chopinski --------------------- */
// Credits:
// CRAZYRUSSIAN - Icon
// Bribe - SpellEffectEvent
// Mythic - Divine Edict Effect
// AZ - Light Stomp effect
/* ----------------------------------- END ---------------------------------- */
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
-- The raw code of the Divine Smite ability
local ABILITY = FourCC('A021')
-- The Divine Smite model
local MODEL = "Divine Edict.mdl"
-- The Divine Smite heal model
local HEAL_MODEL = "HolyLight2.mdl"
-- The Divine Smite model
local ATTACH_POINT = "origin"
-- The Divine Smite normal scale
local SCALE = 0.6
-- The Divine Smite infused scale
local INFUSED_SCALE = 1.
-- The Divine Smite stomp model
local STOMP = "LightStomp.mdl"
-- The Divine Smite stomp scale
local STOMP_SCALE = 0.8
-- The Divine Smite damage/heal
local function GetDamage(unit, level, infused)
if infused then
return 50.*level
else
return 100.*level
end
end
-- The Divine Smite AoE
local function GetAoE(unit, level, infused)
if infused then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ABILITY), ABILITY_RLF_AREA_OF_EFFECT, level - 1) + 50*level
else
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ABILITY), ABILITY_RLF_AREA_OF_EFFECT, level - 1)
end
end
-- The Divine Smite Infused duration
local function GetDuration(level)
return 3. + 0.*level
end
-- The Divine Smite infused damage/heal interval
local function GetInterval(level)
return 0.5 + 0.*level
end
-- Filter for damage/heal
local function GroupFilter(player, unit)
return UnitAlive(unit) and not IsUnitType(unit, UNIT_TYPE_STRUCTURE)
end
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
local function Smite(unit, player, x, y, aoe, damage)
local group = CreateGroup()
GroupEnumUnitsInRange(group, x, y, aoe, nil)
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
if GroupFilter(player, u) then
if IsUnitEnemy(u, player) then
UnitDamageTarget(unit, u, damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, nil)
else
SetWidgetLife(u, GetWidgetLife(u) + damage)
DestroyEffect(AddSpecialEffectTarget(HEAL_MODEL, u, ATTACH_POINT))
end
end
end
DestroyGroup(group)
end
local function Cast(unit, player, level, x, y, aoe, damage, infused)
if infused then
local timer = CreateTimer()
local i = R2I(GetDuration(level)/GetInterval(level))
Smite(unit, player, x, y, aoe, damage)
SpamEffect(MODEL, x, y, 0, INFUSED_SCALE, 0.15, 20)
TimerStart(timer, GetInterval(level), true, function()
if i > 0 then
Smite(unit, player, x, y, aoe, damage)
else
PauseTimer(timer)
DestroyTimer(timer)
end
i = i - 1
end)
else
Smite(unit, player, x, y, aoe, damage)
DestroyEffect(AddSpecialEffectEx(MODEL, x, y, 0, SCALE))
end
end
onInit(function()
RegisterSpellEffectEvent(ABILITY, function()
if LightInfusion then
local infused = (LightInfusion.charges[Spell.source.unit] or 0) > 0
LightInfusion:consume(Spell.source.unit)
Cast(Spell.source.unit, Spell.source.player, Spell.level, Spell.x, Spell.y, GetAoE(Spell.source.unit, Spell.level, infused), GetDamage(Spell.source.unit, Spell.level, infused), infused)
else
Cast(Spell.source.unit, Spell.source.player, Spell.level, Spell.x, Spell.y, GetAoE(Spell.source.unit, Spell.level, false), GetDamage(Spell.source.unit, Spell.level, false), false)
end
end)
end)
end
if Debug and Debug.beginFile then Debug.beginFile("DivineSmite") end
--[[ requires SpellEffectEvent, Utilities, optional LightInfusion
/* --------------------- Divine Smite v1.2 by Chopinski --------------------- */
// Credits:
// CRAZYRUSSIAN - Icon
// Bribe - SpellEffectEvent
// Mythic - Divine Edict Effect
// AZ - Light Stomp effect
/* ----------------------------------- END ---------------------------------- */
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
-- The raw code of the Divine Smite ability
local ABILITY = FourCC('A021')
-- The Divine Smite model
local MODEL = "Divine Edict.mdl"
-- The Divine Smite heal model
local HEAL_MODEL = "HolyLight2.mdl"
-- The Divine Smite model
local ATTACH_POINT = "origin"
-- The Divine Smite normal scale
local SCALE = 0.6
-- The Divine Smite infused scale
local INFUSED_SCALE = 1.
-- The Divine Smite stomp model
local STOMP = "LightStomp.mdl"
-- The Divine Smite stomp scale
local STOMP_SCALE = 0.8
-- The Divine Smite damage/heal
local function GetDamage(unit, level, infused)
if infused then
return 50.*level
else
if level == 4 then
return 180
elseif level == 3 then
return 140
elseif level == 2 then
return 100
elseif level == 1 then
return 60
end
end
end
-- The Divine Smite AoE
local function GetAoE(unit, level, infused)
if infused then
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ABILITY), ABILITY_RLF_AREA_OF_EFFECT, level - 1) + 50*level
else
return BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ABILITY), ABILITY_RLF_AREA_OF_EFFECT, level - 1)
end
end
-- The Divine Smite Infused duration
local function GetDuration(level)
return 3. + 0.*level
end
-- The Divine Smite infused damage/heal interval
local function GetInterval(level)
return 0.5 + 0.*level
end
-- Filter for damage/heal
local function GroupFilter(player, unit)
return UnitAlive(unit) and not IsUnitType(unit, UNIT_TYPE_STRUCTURE)
end
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
local function Smite(unit, player, x, y, aoe, damage)
local group = CreateGroup()
GroupEnumUnitsInRange(group, x, y, aoe, nil)
for i = 0, BlzGroupGetSize(group) - 1 do
local u = BlzGroupUnitAt(group, i)
if GroupFilter(player, u) then
if IsUnitEnemy(u, player) then
UnitDamageTarget(unit, u, damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, nil)
else
SetWidgetLife(u, GetWidgetLife(u) + damage)
DestroyEffect(AddSpecialEffectTarget(HEAL_MODEL, u, ATTACH_POINT))
end
end
end
DestroyGroup(group)
end
local function Cast(unit, player, level, x, y, aoe, damage, infused)
if infused then
local timer = CreateTimer()
local i = R2I(GetDuration(level)/GetInterval(level))
Smite(unit, player, x, y, aoe, damage)
SpamEffect(MODEL, x, y, 0, INFUSED_SCALE, 0.15, 20)
TimerStart(timer, GetInterval(level), true, function()
if i > 0 then
Smite(unit, player, x, y, aoe, damage)
else
PauseTimer(timer)
DestroyTimer(timer)
end
i = i - 1
end)
else
Smite(unit, player, x, y, aoe, damage)
DestroyEffect(AddSpecialEffectEx(MODEL, x, y, 0, SCALE))
end
end
OnInit.global(function()
RegisterSpellEffectEvent(ABILITY, function()
if LightInfusion then
local infused = (LightInfusion.charges[Spell.source.unit] or 0) > 0
LightInfusion:consume(Spell.source.unit)
Cast(Spell.source.unit, Spell.source.player, Spell.level, Spell.x, Spell.y, GetAoE(Spell.source.unit, Spell.level, infused), GetDamage(Spell.source.unit, Spell.level, infused), infused)
else
Cast(Spell.source.unit, Spell.source.player, Spell.level, Spell.x, Spell.y, GetAoE(Spell.source.unit, Spell.level, false), GetDamage(Spell.source.unit, Spell.level, false), false)
end
end)
end)
end
if Debug and Debug.endFile then Debug.endFile() end
--[[ requires DamageInterface
/* ---------------------- Holy Strike v1.2 by Chopinski --------------------- */
// Credits:
// AbstractCreativity - Icon
// Bribe - SpellEffectEvent
// Blizzard - Healing Effect
/* ----------------------------------- END ---------------------------------- */
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
-- The Holy Strike level 1 buff
local BUFF_1 = FourCC('B021')
-- The Holy Strike level 2 buff
local BUFF_2 = FourCC('B022')
-- The Holy Strike level 3 buff
local BUFF_3 = FourCC('B023')
-- The Holy Strike level 4 buff
local BUFF_4 = FourCC('B024')
-- The Holy Strike heal model
local MODEL = "HolyStrike.mdl"
-- The Holy Strike heal attchment point
local ATTACH_POINT = "origin"
-- The Holy Strike Heal
local function GetHeal(level, isRanged)
if level == 4 then
local real heal = 25
elseif level == 3 then
local real heal = 18
elseif level == 2 then
local real heal = 14
elseif level == 1 then
local real heal = 10
end
if isRanged then
heal = heal/2
end
return heal
end
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
onInit(function()
RegisterAttackDamageEvent(function()
if Damage.isEnemy then
if GetUnitAbilityLevel(Damage.source.unit, BUFF_4) > 0 then
SetWidgetLife(Damage.source.unit, GetWidgetLife(Damage.source.unit) + GetHeal(4, Damage.source.isRanged))
DestroyEffect(AddSpecialEffectTarget(MODEL, Damage.source.unit, ATTACH_POINT))
elseif GetUnitAbilityLevel(Damage.source.unit, BUFF_3) > 0 then
SetWidgetLife(Damage.source.unit, GetWidgetLife(Damage.source.unit) + GetHeal(3, Damage.source.isRanged))
DestroyEffect(AddSpecialEffectTarget(MODEL, Damage.source.unit, ATTACH_POINT))
elseif GetUnitAbilityLevel(Damage.source.unit, BUFF_2) > 0 then
SetWidgetLife(Damage.source.unit, GetWidgetLife(Damage.source.unit) + GetHeal(2, Damage.source.isRanged))
DestroyEffect(AddSpecialEffectTarget(MODEL, Damage.source.unit, ATTACH_POINT))
elseif GetUnitAbilityLevel(Damage.source.unit, BUFF_1) > 0 then
SetWidgetLife(Damage.source.unit, GetWidgetLife(Damage.source.unit) + GetHeal(1, Damage.source.isRanged))
DestroyEffect(AddSpecialEffectTarget(MODEL, Damage.source.unit, ATTACH_POINT))
end
end
end)
end)
end
if Debug and Debug.beginFile then Debug.beginFile("HolyStrike") end
--[[ requires DamageInterface
/* ---------------------- Holy Strike v1.2 by Chopinski --------------------- */
// Credits:
// AbstractCreativity - Icon
// Bribe - SpellEffectEvent
// Blizzard - Healing Effect
/* ----------------------------------- END ---------------------------------- */
]]--
do
-- -------------------------------------------------------------------------- --
-- Configuration --
-- -------------------------------------------------------------------------- --
-- The Holy Strike level 1 buff
local BUFF_1 = FourCC('B021')
-- The Holy Strike level 2 buff
local BUFF_2 = FourCC('B022')
-- The Holy Strike level 3 buff
local BUFF_3 = FourCC('B023')
-- The Holy Strike level 4 buff
local BUFF_4 = FourCC('B024')
-- The Holy Strike heal model
local MODEL = "HolyStrike.mdl"
-- The Holy Strike heal attchment point
local ATTACH_POINT = "origin"
-- The Holy Strike Heal
local function GetHeal(level, isRanged)
if level == 4 then
local real heal = 16
elseif level == 3 then
local real heal = 12
elseif level == 2 then
local real heal = 10
elseif level == 1 then
local real heal = 8
end
if isRanged then
heal = heal/2
end
return heal
end
-- -------------------------------------------------------------------------- --
-- System --
-- -------------------------------------------------------------------------- --
OnInit.global(function()
RegisterAttackDamageEvent(function()
if Damage.isEnemy then
if GetUnitAbilityLevel(Damage.source.unit, BUFF_4) > 0 then
SetWidgetLife(Damage.source.unit, GetWidgetLife(Damage.source.unit) + GetHeal(4, Damage.source.isRanged))
DestroyEffect(AddSpecialEffectTarget(MODEL, Damage.source.unit, ATTACH_POINT))
elseif GetUnitAbilityLevel(Damage.source.unit, BUFF_3) > 0 then
SetWidgetLife(Damage.source.unit, GetWidgetLife(Damage.source.unit) + GetHeal(3, Damage.source.isRanged))
DestroyEffect(AddSpecialEffectTarget(MODEL, Damage.source.unit, ATTACH_POINT))
elseif GetUnitAbilityLevel(Damage.source.unit, BUFF_2) > 0 then
SetWidgetLife(Damage.source.unit, GetWidgetLife(Damage.source.unit) + GetHeal(2, Damage.source.isRanged))
DestroyEffect(AddSpecialEffectTarget(MODEL, Damage.source.unit, ATTACH_POINT))
elseif GetUnitAbilityLevel(Damage.source.unit, BUFF_1) > 0 then
SetWidgetLife(Damage.source.unit, GetWidgetLife(Damage.source.unit) + GetHeal(1, Damage.source.isRanged))
DestroyEffect(AddSpecialEffectTarget(MODEL, Damage.source.unit, ATTACH_POINT))
end
end
end)
end)
end
if Debug and Debug.endFile then Debug.endFile() end