├── .gitattributes ├── .gitignore ├── API-JSONdumper.lua ├── AttachManager.lua ├── MotionControllers.lua ├── OrderFilter.lua ├── PseudoRNG.lua └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must ends with two \r. 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | -------------------------------------------------------------------------------- /API-JSONdumper.lua: -------------------------------------------------------------------------------- 1 | --Initial call 2 | --dumpObjectFunc( _G, '_G', 0 ) 3 | 4 | --Only outputs tables and functions! 5 | function dumpObjectFunc( obj, name, indent ) 6 | --build our indentation offset 7 | local indentStr = ''; 8 | for i=1,indent do 9 | indentStr = indentStr..' ' 10 | end 11 | 12 | --keep track of what we did to prevent infinite cycles 13 | doneObjs[obj] = true 14 | 15 | --open the JSON object 16 | print(indentStr..'"'..name..'" : ') 17 | print(indentStr.."{") 18 | 19 | --loop over all fields 20 | for k,v in pairsByKeys(obj) do 21 | 22 | --if the field is a table recurse 23 | if type(v) == 'table' and k ~= 'FDesc' and doneObjs[v] == nil and type(k) ~= 'table' and k ~= 'doneObjs' then 24 | 25 | dumpObjectFunc( v, k, indent + 1) 26 | 27 | elseif type(v) == 'function' then 28 | 29 | --if the field is a function try to find a description and print 30 | if obj.FDesc and obj.FDesc[k] then 31 | print(indentStr..' "'..k..'" : '..'"'..string.gsub(tostring(obj.FDesc[k]), '\n', '\\n ')..'",') 32 | else 33 | print(indentStr..' "'..k..'" : '..'"No description",') 34 | end 35 | 36 | elseif type(v) == 'number' or type(v) == 'string' then 37 | --print constants 38 | print(indentStr..' "'..k..'" : "'..v..'",') 39 | end 40 | 41 | end 42 | print('"##FIX##"') 43 | --close JSON object 44 | print(indentStr.."},") 45 | end 46 | 47 | function pairsByKeys (t, f) 48 | local a = {} 49 | for n in pairs(t) do table.insert(a, n) end 50 | table.sort(a, f) 51 | local i = 0 -- iterator variable 52 | local iter = function () -- iterator function 53 | i = i + 1 54 | if a[i] == nil then return nil 55 | else return a[i], t[a[i]] 56 | end 57 | end 58 | return iter 59 | end -------------------------------------------------------------------------------- /AttachManager.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | AttachManager library. 3 | This class manages the attachment of models to a model's attach points, allowing devs to attach 4 | any model to any model attach points. 5 | 6 | By: Perry 7 | Date: September, 2015 8 | 9 | Interface: 10 | --------------------------------------------------------------------------------------------------------------------------- 11 | AttachManager:DrawAxes( unit, attachment, duration, scale ) 12 | Draw an axis system at a unit's attachment with a scale. Automatically disappears after some duration. 13 | 14 | Params: 15 | * unit - The unit to attach the axes to. 16 | * attachment - The attachment to follow. 17 | * duration - The duration after which the axis system will disappear again. 18 | * scale - The scale of the axes 19 | --------------------------------------------------------------------------------------------------------------------------- 20 | AttachManager:AddModel( modelName[, initialOffset, initialDirection ]) 21 | Add a model to the AttachManager's model library. 22 | This step is REQUIRED if you want to use this model for attachments. 23 | 24 | Params: 25 | * modelName - The path of the model. 26 | * initialOffset - (Optional) The offset a model has to its own origin (can be estimated with model editor). 27 | Default: Vector( 0, 0, 0 ) 28 | * initialOffset - (Optional) The direction a model has to its own origin (can be estimated with model editor). 29 | Default: Vector( 1, 0 ,0 ) 30 | --------------------------------------------------------------------------------------------------------------------------- 31 | AttachManager:AttachModel( unit, modelName, parent[, offset, direction, scale ]) 32 | Create an attachment for some unit. 33 | 34 | Params: 35 | * unit - The unit to attach to. 36 | * modelname - The name of the model. Has to be added usnig AttachManager:AddModel(..) 37 | * parent - The name of the attachment to attach to, e.g. 'attach_hitloc' or 'attach_attack1' 38 | * offset - (Optional) The offset of the attachment relative to its parent. 39 | Default: Vector( 0, 0, 0 ) 40 | * direction - (Optional)The direction of the attachment relative to its parent. 41 | Default: Vector( 1, 0, 0 ) 42 | * scale - (Optional) The scale of the attachment model. 43 | Default: 1 44 | * animation - (Optional) The animation of the model. 45 | Default: '' 46 | 47 | Returns: The attachment entity that was created. 48 | --------------------------------------------------------------------------------------------------------------------------- 49 | AttachManager:ChangeModel( entity, modelName[, scale ]) 50 | Change the model or scale of an attachment entity created with AttachManager:AttachModel(..) 51 | 52 | Params: 53 | * entity - The attachment entity to change the model of. Has to be made using AttachManager:AttachModel(..) 54 | * modelName - The new modelName for the attachment. The model needs to have been added with AttachManager:AddModel(..) 55 | * scale - (Optional) The new scale for the model. 56 | Default: 1 57 | --------------------------------------------------------------------------------------------------------------------------- 58 | 59 | Examples: 60 | --========================================================================================================================= 61 | 62 | --Add model data to the AttachManager model library at your initialisation: 63 | AttachManager:AddModel( 'models/items/enchantress/anuxi_summer_spear/anuxi_summer_spear.vmdl' ) 64 | AttachManager:AddModel( 'models/items/huskar/burning_spear/burning_spear.vmdl', Vector( 0, 100, 0 ) ) 65 | AttachManager:AddModel( 'models/items/faceless_void/battlefury/battlefury.vmdl', Vector( 0, -110, 70 ), Vector( 0, 0, 1 ) ) 66 | 67 | --========================================================================================================================= 68 | 69 | --Create an attachment with some offset and direction relative to their parent 70 | local offset = Vector( 50, 0, 0 ) 71 | local direction = Vector( 0, 0, 1 ) 72 | local scale = 2 73 | local animation = 'bindPose' 74 | 75 | local attachment = AttachManager:AttachModel( unit, 'models/items/enchantress/anuxi_summer_spear/anuxi_summer_spear.vmdl', 76 | 'attach_attack1', offset, direction, scale, animation ) 77 | 78 | --========================================================================================================================= 79 | 80 | --Change the model/scale of an existing attachment maintaining its position/direction 81 | local newScale = 4 82 | AttachManager:ChangeModel( attachment, 'models/items/faceless_void/battlefury/battlefury.vmdl', newScale ) 83 | 84 | --========================================================================================================================= 85 | 86 | ]] 87 | 88 | --Class definition 89 | if AttachManager == nil then 90 | AttachManager = class({}) 91 | AttachManager.models = {} 92 | end 93 | 94 | --Draw an axis system at a unit's attach point 95 | function AttachManager:DrawAxes( unit, attachment, duration, scale ) 96 | --Get start time 97 | local timerStart = GameRules:GetGameTime() 98 | 99 | --Repeat every frame 100 | Timers:CreateTimer( function() 101 | local axisLength = scale or 50 102 | local attach = unit:ScriptLookupAttachment( attachment ) 103 | local attachPos = unit:GetAttachmentOrigin( attach ) 104 | local attachRot = unit:GetAttachmentAngles( attach ) 105 | local attachQA = QAngle( attachRot.x, attachRot.y, attachRot.z ) 106 | 107 | --Calculate orientation 108 | local xAxis = RotatePosition( Vector( 0, 0, 0 ), RotationDelta( QAngle( 0, 0, 0 ), attachQA ) , Vector( axisLength, 0, 0 ) ) 109 | local yAxis = RotatePosition( Vector( 0, 0, 0 ), RotationDelta( QAngle( 0, 90, 0 ), attachQA ) , Vector( -axisLength, 0, 0 ) ) 110 | local zAxis = RotatePosition( Vector( 0, 0, 0 ), RotationDelta( QAngle( 270, 0, 0 ), attachQA ) , Vector( -axisLength, 0, 0 ) ) 111 | 112 | --Draw the lines 113 | DebugDrawLine( attachPos, attachPos + xAxis, 255, 0, 0, false, 0.02 ) 114 | DebugDrawLine( attachPos, attachPos + yAxis, 0, 255, 0, false, 0.02 ) 115 | DebugDrawLine( attachPos, attachPos + zAxis, 0, 0, 255, false, 0.02 ) 116 | 117 | --Enforce duration 118 | if GameRules:GetGameTime() - timerStart < duration then 119 | return 0 120 | else 121 | return nil 122 | end 123 | end) 124 | end 125 | 126 | --Add a model to the AttachManager's mode library 127 | function AttachManager:AddModel( modelName, initialOffset, initialDirection ) 128 | --Handle default values 129 | local offV = initialOffset or Vector( 0, 0, 0 ) 130 | local dirV = initialDirection or Vector( 1, 0, 0 ) 131 | 132 | --Insert in library 133 | AttachManager.models[modelName] = { offset = Vector( offV.z, offV.y, offV.x ), direction = dirV } 134 | end 135 | 136 | --Create an attachment 137 | function AttachManager:AttachModel( unit, modelName, parent, offset, direction, scale, animation ) 138 | --Check if the model is in the model library 139 | if AttachManager.models[ modelName ] == nil then 140 | Warning( '[AttachManager] Attachment with model '..modelname..' not found. Use AddModel( model[, initialOffset, initialDirection]) to add it first.' ) 141 | return 142 | end 143 | 144 | --Animation default 145 | local animation = animation or '' 146 | 147 | --Create the attachment 148 | local attachment = SpawnEntityFromTableSynchronous( 'prop_dynamic', {model = modelName, DefaultAnim = animation }) 149 | 150 | --Set attachment values 151 | attachment.unit = unit 152 | attachment.parent = parent 153 | 154 | attachment.attachOffset = offset or Vector( 0, 0, 0 ) 155 | attachment.attachDirection = direction or Vector( 1, 0, 0 ) 156 | 157 | --Set the attachment's model/position/rotation/scale 158 | AttachManager:ChangeModel( attachment, modelName, scale ) 159 | 160 | --Return the created attachment 161 | return attachment 162 | end 163 | 164 | --Change the model/scale of an existing attachment 165 | function AttachManager:ChangeModel( entity, modelName, scale ) 166 | --Check if the model is in the model library 167 | if AttachManager.models[ modelName ] == nil then 168 | Warning( '[AttachManager] Attachment with model '..modelname..' not found. Use AddModel( model[, initialOffset, initialDirection]) to add it first.' ) 169 | return 170 | end 171 | 172 | --Set model 173 | entity:SetModel( modelName ) 174 | 175 | --Set scale 176 | entity.scale = scale or 1 177 | entity:SetModelScale( entity.scale ) 178 | 179 | --Unparent first to set position/rotation in world coords 180 | entity:SetParent( nil, '' ) 181 | 182 | --Fetch model data 183 | local modelData = AttachManager.models[ modelName ] 184 | 185 | --Get initial offsets 186 | local initialOffset = modelData.offset * entity.scale 187 | local initialDirection = modelData.direction 188 | local initialRotation = VectorToAngles( initialDirection ) 189 | 190 | --Get parent data 191 | local attach = entity.unit:ScriptLookupAttachment( entity.parent ) 192 | local attachPos = entity.unit:GetAttachmentOrigin( attach ) 193 | local attachRot = entity.unit:GetAttachmentAngles( attach ) 194 | local attachQA = QAngle( attachRot.x, attachRot.y, attachRot.z ) 195 | 196 | --Calculat rotations 197 | local relativeDir = entity.attachDirection 198 | local rotationalOffset = VectorToAngles( relativeDir ) 199 | 200 | local propRot = initialRotation 201 | local rotation = RotationDelta( rotationalOffset, QAngle( 0, 0, 0 ) ) 202 | 203 | local delta = RotationDelta( rotation , attachQA ) 204 | 205 | --Calculate offset from attachment 206 | local offset = entity.attachOffset 207 | local correctOffset = RotatePosition( Vector(0, 0, 0), delta, offset ) 208 | 209 | --Calculate initial offset 210 | local offset2 = RotatePosition( Vector(0,0,0), delta, initialOffset ) 211 | 212 | --Set origin 213 | entity:SetAbsOrigin( attachPos - offset2 + correctOffset ) 214 | 215 | --Set angles 216 | local angles = RotationDelta( initialRotation, delta ) 217 | entity:SetAngles( angles.x, angles.y, angles.z ) 218 | 219 | --Attach to parent 220 | entity:SetParent( entity.unit, entity.parent ) 221 | end -------------------------------------------------------------------------------- /MotionControllers.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | MotionControllers library. 3 | 4 | Basically does what we expect from valve motion controllers but don't get. 5 | 6 | By: Perry 7 | Date: September, 2015 8 | 9 | The rules are simple: 10 | -If there are multiple motion controller modifiers on one unit, the modifier with the highest priority is applied, 11 | the rest IS NOT. 12 | -If there are multiple modifiers with the highest priority, the modifier applied last overrules the others. 13 | 14 | Interface: 15 | ---------------------------------------------------------------------------------------------------------------------- 16 | MotionControllers:Register( modifier, priority ) 17 | Register a modifier as a motion controller with some priority. Call this in the modifier's OnCreated(..). 18 | 19 | Params: 20 | * modifier - The modifier to register as motion controller, usually 'self' in OnCreated(..). 21 | * priority - The priority of the motion controller, possible values are: 22 | - DOTA_MOTION_CONTROLLER_PRIORITY_LOWEST 23 | - DOTA_MOTION_CONTROLLER_PRIORITY_LOW 24 | - DOTA_MOTION_CONTROLLER_PRIORITY_MEDIUM 25 | - DOTA_MOTION_CONTROLLER_PRIORITY_HIGH 26 | - DOTA_MOTION_CONTROLLER_PRIORITY_HIGHEST 27 | ---------------------------------------------------------------------------------------------------------------------- 28 | modifier:DoMotion( motionFunction ) 29 | Motion controllers should call this everytime they are trying to apply some motion. Internally validates if the 30 | motion controller is applied or not. Will execute the motionFunction if the motion controller is applied, therefore 31 | should contain all the motion code for the modifier. 32 | 33 | Params: 34 | * motionFunction - The function to execute or not depending on if the motion controller has priority over the others. 35 | Typically this function contains a SetAbsOrigin. 36 | ---------------------------------------------------------------------------------------------------------------------- 37 | MotionControllers:NonModifierMotion( priority, unit, motionFunction ) 38 | Perform a motion with some priority on a unit without using any modifier. 39 | 40 | Params: 41 | * priority - The priority of the motion controller, possible values are: 42 | - DOTA_MOTION_CONTROLLER_PRIORITY_LOWEST 43 | - DOTA_MOTION_CONTROLLER_PRIORITY_LOW 44 | - DOTA_MOTION_CONTROLLER_PRIORITY_MEDIUM 45 | - DOTA_MOTION_CONTROLLER_PRIORITY_HIGH 46 | - DOTA_MOTION_CONTROLLER_PRIORITY_HIGHEST 47 | * unit - The unit the motion is applied to. 48 | * motionFunction - The function to execute or not depending on if the motion has priority over other motion controllers. 49 | Typically this function contains a SetAbsOrigin. 50 | ---------------------------------------------------------------------------------------------------------------------- 51 | ]] 52 | MotionControllers = class({}) 53 | 54 | --Register a modifier as a motion controller with soem priority 55 | function MotionControllers:Register( modifier, priority ) 56 | modifier.MotionControllers = { 57 | priority = priority, 58 | creation_time = GameRules:GetGameTime() 59 | } 60 | 61 | function modifier:DoMotion( motion ) 62 | MotionControllers:ValidateMotion( modifier, motion ) 63 | end 64 | end 65 | 66 | --Validate a motioncontroller. Decides whether the motion controller is applied or not. 67 | function MotionControllers:ValidateMotion( modifier, motion ) 68 | --Get modifier info 69 | local parent = modifier:GetParent() 70 | local priority = modifier.MotionControllers.priority 71 | local creation_time = modifier.MotionControllers.creation_time 72 | 73 | --Assume this motioncontroller is not blocked for now 74 | local blocked = false 75 | 76 | --Loop over all modifiers on the unit 77 | local modifiers = parent:FindAllModifiers() 78 | for _, m in pairs( modifiers ) do 79 | --Only check other motion controllers 80 | if m.MotionControllers ~= nil and m ~= modifier then 81 | --Check if the priority of the other motion controller outranks this one 82 | if m.MotionControllers.priority > priority then 83 | blocked = true 84 | break 85 | end 86 | 87 | --If the priorities are equal, check which motion controller was applied last 88 | if m.MotionControllers.priority == priority and m.MotionControllers.creation_time > creation_time then 89 | blocked = true 90 | break 91 | end 92 | end 93 | end 94 | 95 | --Execute the motion function if it didn't get blocked by another motion controller 96 | if not blocked then 97 | motion() 98 | end 99 | end 100 | 101 | --Validate a motion not attached to a motion controller 102 | function MotionControllers:NonModifierMotion( priority, unit, motion ) 103 | --Assume this motioncontroller is not blocked for now 104 | local blocked = false 105 | 106 | --Loop over all modifiers on the unit 107 | local modifiers = unit:FindAllModifiers() 108 | for _, m in pairs( modifiers ) do 109 | --Only check other motion controllers 110 | if m.MotionControllers ~= nil then 111 | --Check if the priority of the other motion controller outranks this one 112 | if m.MotionControllers.priority > priority then 113 | blocked = true 114 | break 115 | end 116 | end 117 | end 118 | 119 | --Execute the motion function if it didn't get blocked by another motion controller 120 | if not blocked then 121 | motion() 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /OrderFilter.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Overwrite the existing order filter to allows multiple order filters using the default API. 3 | 4 | It is important to require this file from or after Activate function in addon_game_mode.lua 5 | is called, otherwise GameRules:GetGameModeEntity will return nil. 6 | 7 | Example usage: 8 | --Filter 1 9 | GameRules:GetGameModeEntity():SetExecuteOrderFilter( function( self, order ) 10 | print('Filter 1') 11 | return true 12 | end, ORDER_FILTER_PRIORITY_LOW ) 13 | 14 | --Filter 2 15 | GameRules:GetGameModeEntity():SetExecuteOrderFilter( function( self, order ) 16 | print('Filter 2') 17 | return true 18 | end ) 19 | 20 | --Filter 3 21 | GameRules:GetGameModeEntity():SetExecuteOrderFilter( DynamicWrap( MyGameMode, 'OrderFilter' ), self ) 22 | 23 | --Output For each order: 24 | Filter 2 25 | Filter 3 26 | Filter 1 27 | ]] 28 | if orderFilterOverwritten == nil then 29 | orderFilterOverwritten = true 30 | 31 | ORDER_FILTER_PRIORITY_LOWEST = 0 32 | ORDER_FILTER_PRIORITY_LOW = 1 33 | ORDER_FILTER_PRIORITY_NORMAL = 2 34 | ORDER_FILTER_PRIORITY_HIGH = 3 35 | ORDER_FILTER_PRIORITY_HIGHEST = 4 36 | 37 | --Save a list of different functions 38 | orderFilters = {} 39 | 40 | --Set the actual order filter to the function that just iterates over all filters 41 | GameRules:GetGameModeEntity():SetExecuteOrderFilter( function( self, order ) 42 | for _,filter in ipairs( orderFilters ) do 43 | if filter.func( filter.context, order ) ~= true then 44 | return false 45 | end 46 | end 47 | 48 | return true 49 | end, {} ) 50 | 51 | --Overwrite the original function 52 | GameRules:GetGameModeEntity().SetExecuteOrderFilter = function( self, filterFunc, context, priority ) 53 | if type( context ) == 'number' then 54 | priority = context 55 | context = self 56 | end 57 | 58 | --Set default priority if it's not set 59 | if priority == nil then 60 | priority = ORDER_FILTER_PRIORITY_NORMAL 61 | end 62 | 63 | table.insert( orderFilters, {func = filterFunc, priority = priority, context = context } ) 64 | 65 | --Sort table based on priorities 66 | table.sort( orderFilters, function( a, b ) return a.priority - b.priority end ) 67 | end 68 | end 69 | 70 | -------------------------------------------------------------------------------- /PseudoRNG.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This class provides a Pseudo-random RNG, meaning it works with 3 | a pseudo-random probability distribution instead of a static random chance. 4 | The benefit of this is that you reduce the randomness in gameplay. If you 5 | want some more in-depth explanation see: 6 | http://dota2.gamepedia.com/Pseudo-random_distribution 7 | 8 | ----HOW TO USE----- 9 | Create a pseudo-random RNG like this: 10 | 11 | local rng = PseudoRNG.create( 0.25 ) -- immitates a 25% chance 12 | 13 | Then whenever you want to know if something procs, use this: 14 | 15 | if rng:Next() then 16 | --proc 17 | else 18 | --didn't proc 19 | end 20 | 21 | UPDATE: added ChoicePseudoRNG, this class will use a pseudo-random distribution to 22 | choose an element based on its probability. Items chosen will have a lower probability 23 | next choice, items not chosen will have a higher probability. 24 | 25 | ----HOW TO USE----- 26 | Create a pseudo-random ChoiceRNG like this: 27 | NOTE: The input probabilities should add up to 1! 28 | 29 | local choiceRNG = ChoicePseudoRNG.create( {0.2, 0.1, 0.3, 0.4} ) -- Adds 4 items with percentages 20%, 10%, 30% and 40% 30 | 31 | When you need to choose one of the elements you put in use: 32 | 33 | local result = choiceRNG:Choose() 34 | 35 | Result will contain a number from 1 to the number of elements you put in, signifying the items index in the input list. 36 | (1 is the item with prob 0.2, 2 is the item with prob 0.1 etc...) 37 | 38 | Author: Perry 39 | ]] 40 | PseudoRNG = {} 41 | PseudoRNG.__index = PseudoRNG 42 | 43 | --construct a PseudoRNG for a certain chance (0 - 1; 25% -> 0.25) 44 | function PseudoRNG.create( chance ) 45 | local rng = {} -- our new object 46 | setmetatable(rng, PseudoRNG) 47 | 48 | if chance < 0.0001 then 49 | print("[PseudoRNG] Warning, chance is extremely low. Are you sure you intended chance = " .. chance .. "?") 50 | end 51 | 52 | rng:Init( chance ) 53 | return rng 54 | end 55 | 56 | function PseudoRNG:Init( chance ) 57 | self.failedTries = 0 58 | --calculate the constant 59 | self.cons = PseudoRNG:CFromP( chance ) 60 | end 61 | 62 | function PseudoRNG:CFromP( P ) 63 | local Cupper = P 64 | local Clower = 0 65 | local Cmid = 0 66 | 67 | local p1 = 0 68 | local p2 = 1 69 | 70 | while true do 71 | Cmid = (Cupper + Clower) / 2; 72 | p1 = PseudoRNG:PFromC( Cmid ) 73 | if math.abs(p1 - p2) <= 0 then 74 | break 75 | end 76 | 77 | if p1 > P then 78 | Cupper = Cmid 79 | else 80 | Clower = Cmid 81 | end 82 | 83 | p2 = p1 84 | end 85 | 86 | return Cmid 87 | end 88 | 89 | function PseudoRNG:PFromC( C ) 90 | local pOnN = 0 91 | local pByN = 0 92 | local sumPByN = 0 93 | 94 | local maxFails = math.ceil( 1/ C ) 95 | 96 | for N=1,maxFails do 97 | pOnN = math.min(1, N * C) * (1 - pByN) 98 | pByN = pByN + pOnN 99 | sumPByN = sumPByN + N * pOnN 100 | end 101 | 102 | return 1/sumPByN 103 | end 104 | 105 | --Use this to check if an ab 106 | function PseudoRNG:Next() 107 | -- P(N) = C * N 108 | local P = self.cons * (self.failedTries + 1) 109 | if RandomFloat( 0, 1 ) <= P then 110 | --success! 111 | self.failedTries = 0 112 | return true 113 | else 114 | --failure 115 | self.failedTries = self.failedTries + 1 116 | return false 117 | end 118 | end 119 | 120 | ------------------------------------ 121 | -- Pseudo-Random Choice - choose between a number of probabilities 122 | ------------------------------------ 123 | ChoicePseudoRNG = {} 124 | ChoicePseudoRNG.__index = ChoicePseudoRNG 125 | --construct a ChoicePseudoRNG from a list of probabilities, they should add up to 1 . 126 | function ChoicePseudoRNG.create( probs ) 127 | local rng = {} -- our new object 128 | setmetatable(rng, ChoicePseudoRNG) 129 | 130 | rng:Init( probs ) 131 | return rng 132 | end 133 | 134 | function ChoicePseudoRNG:Init( probs ) 135 | self.probs = {} --the probability the drop should be around 136 | self.curProbs = {} --the current probability 137 | self.cons = {} --the minimum value for this probability 138 | self.total = 0 139 | 140 | for _,chance in pairs( probs ) do 141 | self.probs[#self.probs+1] = chance 142 | self.curProbs[#self.curProbs+1] = chance 143 | self.cons[#self.cons+1] = PseudoRNG:CFromP( chance ) -- calculate the minimum 144 | self.total = self.total + chance 145 | end 146 | 147 | --scramble the distribution a bit before using 148 | for i=0, RandomInt(5, 16) do 149 | self:Choose() 150 | end 151 | end 152 | 153 | --Use this to choose one of the elements, returns the index of the chosen item (starts at 1!) 154 | function ChoicePseudoRNG:Choose() 155 | local rand = RandomFloat( 0, 1 ) * self.total 156 | local cumulative = 0 157 | 158 | local choice = #self.cons 159 | 160 | --loop over all probabilities we have 161 | for i=1,#self.probs do 162 | --the number we generated is below the current probability and all previous probabilities 163 | --we choose this i 164 | if cumulative + self.curProbs[i] > rand then 165 | choice = i 166 | break 167 | else 168 | --otherwise add this probability to the cumulative value and continue 169 | cumulative = cumulative + self.curProbs[i] 170 | end 171 | end 172 | 173 | --reduce the probability of the item we just chose 174 | self.curProbs[choice] = self.cons[choice] 175 | 176 | --update our total value 177 | self.total = self.cons[choice] 178 | 179 | --distribute the 'extra probability' we got from our choice over all indices we didn't choose 180 | for i=1,#self.cons do 181 | if i ~= choice then 182 | --use the P(N) = C * N formula to set a new percentage for each non-chosen element 183 | self.curProbs[i] = self.curProbs[i] + self.cons[i] 184 | --add this to the total 185 | self.total = self.total + self.curProbs[i] 186 | end 187 | end 188 | 189 | return choice 190 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LuaLibraries 2 | ============ 3 | 4 | Some useful lua libraries for Dota 2 addons. 5 | 6 | Currently available libraries: 7 | - PseudoRNG - A library implementing random number generators using a pseudo-random distribution. For more details see http://dota2.gamepedia.com/Pseudo-random_distribution. 8 | - AttachManager - Provides a way of *reliably* attaching models to unit attachments. 9 | - MotionControllers - A small library providing a nice interface for prioritising modifiers that perform a motion. Useful when you need some modifiers' motion to be prioritised over others. 10 | - OrderFilter - A hack that helps when creating libraries that require SetExecuteOrderFilter. Overwrites the default SetExecuteOrderFilter to allow for multiple calls with different filtering functions. (Should not be required BEFORE addon_game_mode.lua Activate()) 11 | - API-JSONDumper - Dumps an object to the JSON format, can be used to dump the dota lua API by dumping _G. 12 | 13 | Detailed documentation for each library can be found in the library file. 14 | 15 | You can use these libraries by requiring them. 16 | To use PseudoRNG.lua for example, in addon_game_mode.lua add 'require("PseudoRNG")' 17 | --------------------------------------------------------------------------------