Skip to content

Instantly share code, notes, and snippets.

@DelusionalLogic
Created July 3, 2012 14:50
Show Gist options
  • Save DelusionalLogic/3040200 to your computer and use it in GitHub Desktop.
Save DelusionalLogic/3040200 to your computer and use it in GitHub Desktop.
Veigarsuperfunhouse
Circle = {
-- ===========================
-- Creates a new circle of the specified center and radius.
-- ===========================
New = function(self)
local c = {}
setmetatable(c, {__index = self})
return c
end,
-- ===========================
-- Determines if the point is contained within the circle.
-- ===========================
Contains = function(self, point)
return point:Distance(self.center) < self.radius
or math.abs(point:Distance(self.center) - self.radius) < 1e-9
end,
-- ===========================
-- Get the string representation of the circle.
-- ===========================
ToString = function(self)
return "{center: " .. self.center:ToString() .. ", radius: " .. tostring(self.radius) .. "}"
end,
}
--[[
Prediction calculation example v0.1
Written by h0nda
]]
-- linear prediction with constant time
LinearPredictionConstT = {}
player = GetMyHero()
function LinearPredictionConstT:new(range, time)
local object = { range = range, time = time, memory={}, prediction={}}
setmetatable(object, { __index = LinearPredictionConstT })
return object
end
function LinearPredictionConstT:check_if_target(obj)
return obj ~= nil and obj.team ~= player.team and obj.dead == false and obj.visible == true
end
function LinearPredictionConstT:tick()
local count = heroManager.iCount
local position, v,ft,dt
for i = 1, count, 1 do
local object = heroManager:GetHero(i)
if self:check_if_target(object) and (player:GetDistance(object)<=self.range or self.range ==0 )then
if self.memory[object.charName] then
ft = self.time
dt = GetTickCount()-self.memory[object.charName].t
position = Vector:New({x=object.x,z=object.z})
old_position = self.memory[object.charName].p
dp = position-old_position
self.prediction[object.charName]=position+((dp)/dt)*ft
end
self.memory[object.charName]={p=Vector:New(object.x,object.z),t=GetTickCount()}
else
self.prediction[object.charName]=nil
end
end
end
function LinearPredictionConstT:getPredictionFor(champ_charName)
return self.prediction[champ_charName]
end
-- ===========================
-- Minimum enclosing circle algorithm
-- ---------------------------
-- Much of this code was inspired from the ruby implementation at:
-- [http://www.dseifert.net/code/mec/source.html]
-- ===========================
-- ===========================
-- Copy the array portion of the table.
-- ===========================
function CopyArray(t)
local ret = {}
for i,v in ipairs(t) do
ret[i] = v
end
return ret
end
-- ===========================
-- Create a new array, minus the element at the specified index
-- ===========================
function DeleteAt(t, index)
local ret = {}
for i,v in ipairs(t) do
if i ~= index then
table.insert(ret, v)
end
end
return ret
end
-- ===========================
-- Joins arrays t1 and t2 and outputs only the unique elements.
-- ===========================
function JoinUniqueArrayElements(t1, t2)
local ret = {}
local unique = {}
-- Place the elements of t1 and t2 into the unique dictionary
for i,p in ipairs(t1) do
unique["x" .. p.x .. "z" .. p.z] = p
end
for i,p in ipairs(t2) do
unique["x" .. p.x .. "z" .. p.z] = p
end
-- Insert each element of unique into the return array.
for k,p in pairs(unique) do
table.insert(ret, p)
end
return ret
end
-- ===========================
-- Minimum Enclosing Circle object and algorithm
-- ===========================
MEC = {
-- ===========================
-- Create a new MEC table
-- ===========================
New = function(self, points)
local mec = {}
mec = setmetatable({}, {__index = self})
mec.circle = nil
mec.points = {} -- a table of x,z coordinates
if points then
mec:SetPoints(points)
end
return mec
end,
-- ===========================
-- Set the points used to compute the MEC.
-- points is an array, starting at 1.
-- ===========================
SetPoints = function(self, points)
-- Set the points
self.points = points
for i, p in ipairs(self.points) do
p = Vector:New(p)
end
end,
-- ===========================
-- Computes the half hull of a set of points
-- ===========================
HalfHull = function(left, right, pointTable, factor)
local input = CopyArray(pointTable)
table.insert(input, right)
local half = {}
table.insert(half, left)
for i,p in ipairs(input) do
table.insert(half, p)
while #half >= 3 do
local dir = factor * Vector.Direction(half[(#half+1)-3], half[(#half+1)-1], half[(#half+1)-2])
if dir <= 0 then
half = DeleteAt(half, #half-1)
else
break
end
end
end
return half
end,
-- ===========================
-- Computes the set of points that represent the
-- convex hull of the set of points
-- ===========================
ConvexHull = function(self)
local a = self.points
local left = a[1]
local right = a[#a]
local upper = {}
local lower = {}
-- Partition remaining points into upper and lower buckets.
for i = 2, #a-1 do
local dir = Vector.Direction(left, right, a[i])
if dir < 0 then
table.insert(upper, a[i])
else
table.insert(lower, a[i])
end
end
local upperHull = self.HalfHull(left, right, upper, -1)
local lowerHull = self.HalfHull(left, right, lower, 1)
return JoinUniqueArrayElements(upperHull, lowerHull)
end,
-- ===========================
-- Compute the MEC.
-- ===========================
Compute = function(self)
self.circle = self.circle or Circle:New()
-- Make sure there are some points.
if #self.points == 0 then return self.circle end
-- Handle degenerate cases first
if #self.points == 1 then
self.circle.center = self.points[1]
self.circle.radius = 0
self.circle.radiusPoint = self.points[1]
elseif #self.points == 2 then
local a = self.points
self.circle.center = a[1]:Center(a[2])
self.circle.radius = a[1]:Distance(self.circle.center)
self.circle.radiusPoint = a[1]
else
local a = self:ConvexHull()
local point_a = a[1]
local point_b = nil
local point_c = a[2]
if not point_c then
self.circle.center = point_a
self.circle.radius = 0
self.circle.radiusPoint = point_a
return self.circle
end
-- Loop until we get appropriate values for point_a and point_c
while true do
point_b = nil
local best_theta = 180.0
-- Search for the point "b" which subtends the smallest angle a-b-c.
for i,point in ipairs(self.points) do
if (not point:Equals(point_a)) and (not point:Equals(point_c)) then
local theta_abc = point:AngleBetween(point_a, point_c)
if theta_abc < best_theta then
point_b = point
best_theta = theta_abc
end
end
end
-- If the angle is obtuse, then line a-c is the diameter of the circle,
-- so we can return.
if best_theta >= 90.0 or (not point_b) then
self.circle.center = point_a:Center(point_c)
self.circle.radius = point_a:Distance(self.circle.center)
self.circle.radiusPoint = point_a
return self.circle
end
local ang_bca = point_c:AngleBetween(point_b, point_a)
local ang_cab = point_a:AngleBetween(point_c, point_b)
if ang_bca > 90.0 then
point_c = point_b
elseif ang_cab <= 90.0 then
break
else
point_a = point_b
end
end
local ch1 = (point_b - point_a):Scale(0.5)
local ch2 = (point_c - point_a):Scale(0.5)
local n1 = ch1:NormalLeft()
local n2 = ch2:NormalLeft()
ch1 = point_a + ch1
ch2 = point_a + ch2
self.circle.center = Vector.InfLineIntersection (ch1, n1, ch2, n2)
self.circle.radius = self.circle.center:Distance(point_a)
self.circle.radiusPoint = point_a
end
return self.circle
end,
}
function __check_if_target(obj,range,R)
return obj ~= nil and obj.team == TEAM_ENEMY and not obj.dead and obj.visible and player:GetDistance(obj) <= (range + R)
--return obj ~= nil
end
function __check_if_near_target(obj,target,range,R)
return obj ~= nil and obj.team == target.team and not obj.dead and obj.visible and obj:GetDistance(target) <= R*2
end
function FindGroupCenterNearTarget(target,R,range)
local playerCount = heroManager.iCount
local points = {}
for i = 1, playerCount, 1 do
local object = heroManager:GetHero(i)
if __check_if_near_target(object,target,range,R) or object == target then -- finding enemies near our target. grouping them in points table.
table.insert(points, Vector:New(object.x,object.z))
end
end
return CalcSpellPosForGroup(R,range,points)
end
function FindGroupCenterFromNearestEnemies(R,range)
local playerCount = heroManager.iCount
local points = {}
for i = 1, playerCount, 1 do
local object = heroManager:GetHero(i)
if __check_if_target(object,range,R) then -- finding enemies in our range (spell range + AoE radius) and grouping them in points table.
table.insert(points, Vector:New(object.x,object.z))
end
end
return CalcSpellPosForGroup(R,range,points)
end
-- ============================================================
-- Weee's additional stuff:
-- ============================================================
-- =======================
-- CalcCombosFromString is used to fill table "comboTableToFill[]" with unique
-- combinations (with size of comboSize) generated from table "targetsTable[]".
-- =======================
function CalcCombosFromString(comboString,index_number,comboSize,targetsTable,comboTableToFill)
if string.len(comboString) == comboSize then
local b = {}
for i=1,string.len(comboString),1 do
local ai = tonumber(string.sub(comboString,i,i))
table.insert(b,targetsTable[ai])
end
return table.insert(comboTableToFill,b)
end
for i = index_number, #targetsTable, 1 do
CalcCombosFromString(comboString..i,i+1,comboSize,targetsTable,comboTableToFill)
end
end
-- =======================
-- CalcSpellPosForGroup is used to get optimal position for your AoE circle spell (annie ult, brand W, etc).
-- It will always return MEC with position, where spell will hit most players and where players will be staying closer to each other.
-- =======================
function CalcSpellPosForGroup(spellRadius,spellRange,enemyTable)
if #enemyTable == 1 then
--PrintChat("Casting shit on player (inside MEC lib)")
return { center = { x = enemyTable[1].x, z = enemyTable[1].z } }
end
local combos = {
[5] = {}, -- 5-player combos
[4] = {}, -- 4-player combos
[3] = {}, -- 3-player combos
[2] = {}, -- 2-player combos
}
mec = MEC:New()
for j = #enemyTable,2,-1 do
CalcCombosFromString("",1,j,enemyTable,combos[j])
--for i,v in ipairs(combos[j]) do
--printTable(v)
--end
--print(j.."-player combinations: "..#combos[j].."\n")
--PrintChat(j.."-player combinations: "..#combos[j])
local spellPos = nil
for i,v in ipairs(combos[j]) do
mec:SetPoints(v)
local c = mec:Compute()
if c.radius <= spellRadius and (spellPos == nil or c.radius < spellPos.radius) then
spellPos = Circle:New()
spellPos.center = c.center
spellPos.radius = c.radius
--print("MEC for combo #"..i.." of "..#combos[j]..": "..spellPos:ToString())
--PrintChat("MEC for combo #"..i.." of "..#combos[j]..": "..spellPos:ToString())
--PrintChat("true")
--print()
end
end
if spellPos ~= nil then return spellPos end
end
end
TARGET_LOW_HP = 1
TARGET_NEAR = 2
TARGET_FAR = 3
DAMAGE_MAGIC = 1
DAMAGE_PHYSICAL = 2
targetMode=" "
mouseRange = 400 -- distance from mouse the target must be to attack if targetMouse is true.
targetMouse = false --default
TargetSelector = {}
player = GetMyHero()
mouseArray = {}
Array = { --A blank array for enemies and mode flags.
{enemy = {charName="###"}, mode = "target", priorityNum = 0},
{enemy = {charName="###"}, mode = "target", priorityNum = 0},
{enemy = {charName="###"}, mode = "target", priorityNum = 0},
{enemy = {charName="###"}, mode = "target", priorityNum = 0},
{enemy = {charName="###"}, mode = "target", priorityNum = 0}
}
CommandArray = { -- a Table of commands. Easier to add new word commands later.
{command=".ignore", targetflag=true, --targetflag true means it requires ".ignore TargetName" to work
method= function(i)
Array[i].mode = "ignore"
PrintChat(" >> Ignoring "..Array[i].enemy.charName)
end
},
{command=".target", targetflag=true,
method= function(i)
Array[i].mode="target"
PrintChat(" >> Targeting "..Array[i].enemy.charName)
end
},
{command=".focus", targetflag=true,
method= function(i)
local j
targetMode = "Priority"
PrintChat(" >> Priority mode activated")
Array[i].mode= "target"
PrintChat(" >> Focusing "..Array[i].enemy.charName)
for j=1, #Array, 1 do
Array[j].priorityNum = 0
end
Array[i].priorityNum = math.huge
end
},
{command=".mostap", targetflag = false,
method = function(i)
targetMode = "MostAP"
PrintChat(" >> Focusing non-ignored enemy with most AP!")
end
},
{command=".lowhp", targetflag = false,
method = function(i)
targetMode = "LowHP"
PrintChat(" >> Focusing non-ignored enemy with lowest effective HP")
end
},
{command=".near", targetflag = false,
method = function(i)
targetMode = "Near"
PrintChat(" >> Focusing the nearest non-ignored enemy")
end
},
{command=".far", targetflag = false,
method = function(i)
targetMode = "Far"
PrintChat(" >> Focusing the furthest non-ignored enemy")
end
},
{command=".printarray", targetflag = false,
method = function(i)
local i
for i=1,#Array, 1 do
if Array[i].enemy.charName ~= "###" then
PrintChat(" >> Enemy "..i.." : "..Array[i].enemy.charName.." Mode= "..Array[i].mode.." Priority = "..Array[i].priorityNum)
end
end
PrintChat(" >> Target Mode: "..targetMode)
if targetMouse == true then
PrintChat(" >> Closest to mouse")
end
end
},
{command=".primode", targetflag = false,
method = function(i)
targetMode = "Priority"
PrintChat(" >> Priority mode activated.")
end
},
{command=".setpriority", targetflag = true,
method = function(i)
end
},
{command=".mouse",targetflag = false,
method = function(i)
if targetMouse == false then
targetMouse = true
PrintChat(" >> Targeting Enemies "..mouseRange.." away from mouse.")
else
targetMouse = false
PrintChat(" >> Mouse targeting deactivated.")
end
end
}
}
function TargetSelector:new(mode, range, damageType)
local object = { mode = mode, range = range, damageType = damageType or DAMAGE_MAGIC, target = nil, paused=false }
setmetatable(object, { __index = TargetSelector })
if mode == TARGET_LOW_HP then --just a conversion for consistency
targetMode= "LowHP"
elseif mode == TARGET_NEAR then
targetMode= "Near"
elseif mode == TARGET_FAR then
targetMode= "Far"
end
return object
end
function TargetSelector:getChatCommand(text,playername)
local i,j,n
for j=1,#CommandArray,1 do
for i=1, #Array, 1 do
if string.find(text,Array[i].enemy.charName)~=nil or CommandArray[j].targetflag == false then --true if found a character name that matches an enemy OR it doesn't need a target name
if string.find(text,CommandArray[j].command)~=nil then --if found a given command
if string.find(text,"%d")~=nil then --found a number after command set priority
n = tonumber(string.sub(text,string.find(text,"%d")))
Array[i].priorityNum = n
targetMode = "Priority"
PrintChat(""..Array[i].enemy.charName.."'s priority set to "..n)
PrintChat(" >> Priority mode activated.")
else
CommandArray[j].method(i) --proceed with commands actions
if CommandArray[j].targetflag == false then --if command was a mode change not requiring a target, then for loop breaks
break
end
end
end
end
end
end
end
function TargetSelector:buildarray() --populates Array with enemy champion objects
local count = heroManager.iCount
local i
local n=1
for i = 1, count, 1 do
local object = heroManager:GetHero(i)
if object ~= nil and object.team ~= player.team then
Array[n].enemy = object
n=n+1
end
end
end
function TargetSelector:printarray() -- will print all enemies, their mode, and the targeting mode.
for i=1,#Array, 1 do
if Array[i].enemy.charName ~= "###" then
PrintChat(" >> Enemy "..i.." : "..Array[i].enemy.charName.." Mode= "..Array[i].mode.." Priority = "..Array[i].priorityNum)
end
end
PrintChat(" >> Target Mode: "..targetMode)
end
function CalculateMagicDmg(target, spellDamage) --TODO replace this with working target:CalculateMagicDamage() in next update
local magicArmor = target.magicArmor - player.magicPen
if magicArmor > 0 then
magicArmor = magicArmor * (1-(player.magicPenPercent/100))
else
magicArmor = 0
end
return spellDamage * (100/(100+magicArmor))
end
function TargetSelector:tick(time)
if targetMouse == true then
self:findHeroNearestMouse()
end
if self.paused then return end
if targetMode==" " then
targetMode = self.mode
end
if targetMode == "LowHP" then
self:low_hp()
elseif targetMode == "Far" then
self:far()
elseif targetMode == "Near" then
self:near()
elseif targetMode == "MostAP" then
self:mostAP()
elseif targetMode == "Priority" then
self:highestPriority()
end
end
function TargetSelector:pause()
self.paused = true
end
function TargetSelector:resume()
self.paused = false
end
function TargetSelector:highestPriority()
local highestP = 0
local highest = nil
if targetMouse == true then
for i=1, #mouseArray, 1 do
if mouseArray[i].mode ~= "ignore" then
if self:check_if_target(mouseArray[i].enemy) then
if player:GetDistance(object)<=self.range then
if mouseArray[i].priorityNum >= highestP then
highest = mouseArray[i].enemy
highestP=mouseArray[i].priorityNum
end
end
end
end
end
else
for i=1, #Array, 1 do
if Array[i].mode ~= "ignore" then
if self:check_if_target(Array[i].enemy) then
if player:GetDistanceTo(Array[i].enemy)<=self.range then
if Array[i].priorityNum >= highestP then
highest = Array[i].enemy
highestP=Array[i].priorityNum
end
end
end
end
end
end
self.target = highest
end
function TargetSelector:low_hp()
local tmp
local count,object
local ignoreflag=false
if targetMouse == true then
for i = 1, #mouseArray, 1 do
if mouseArray[i].mode ~= "ignore" then
if self:check_if_target(mouseArray[i].enemy) and player:GetDistance(object) <= self.range
and
(tmp == nil or
((self.damageType == DAMAGE_MAGIC and tmp.health * (100/CalculateMagicDmg(tmp,100)) > mouseArray[i].enemy.health * (100/CalculateMagicDmg(mouseArray[i].enemy,100)))
or
(self.damageType == DAMAGE_PHYSICAL and tmp.health * (player.totalDamage/player:CalculateDamage(tmp)) > mouseArray[i].enemy.health * (player.totalDamage/player:CalculateDamage(mouseArray[i].enemy))))
) then
tmp = mouseArray[i].enemy
end
end
end
else
if Array[1].enemy.charName == "###" then --backwards compatibility. If true, then not using updated script for text input.
count = heroManager.iCount
else
count = #Array
end
for i = 1, count, 1 do
if Array[1].enemy.charName == "###" then --backwards compatibility
object = heroManager:GetHero(i)
elseif Array[i].mode == "ignore" then
ignoreflag = true
elseif Array[i].mode ~= "ignore" then
ignoreflag = false
object = Array[i].enemy
end
if ignoreflag == false then
if self:check_if_target(object) and player:GetDistance(object) <= self.range
and
(tmp == nil or
((self.damageType == DAMAGE_MAGIC and tmp.health * (100/CalculateMagicDmg(tmp,100)) > object.health * (100/CalculateMagicDmg(object,100)))
or
(self.damageType == DAMAGE_PHYSICAL and tmp.health * (player.totalDamage/player:CalculateDamage(tmp)) > object.health * (player.totalDamage/player:CalculateDamage(object))))
) then
tmp = object
end
end
end
end
self.target = tmp
end
function TargetSelector:near()
local nc = nil
local count,object
local smallest_dist = self.range+1
local ignoreflag = false
if targetMouse == true then
for i = 1, #mouseArray, 1 do
if mouseArray[i].mode ~= "ignore" then
if self:check_if_target(mouseArray[i].enemy) then
local d = player:GetDistance(mouseArray[i].enemy)
if d<=self.range and d<smallest_dist then
nc = mouseArray[i].enemy
smallest_dist = d
end
end
end
end
else
if Array[1].enemy.charName == "###" then --backwards compatibility. If true, then not using updated script for text input.
count = heroManager.iCount
else
count = #Array
end
for i = 1, count, 1 do
if Array[1].enemy.charName == "###" then --backwards compatibility
object = heroManager:GetHero(i)
elseif Array[i].mode == "ignore" then
ignoreflag = true
elseif Array[i].mode ~= "ignore" then
ignoreflag = false
object = Array[i].enemy
end
if ignoreflag==false then
if self:check_if_target(object) then
local d = player:GetDistance(object)
if d<=self.range and d<smallest_dist then
nc = object
smallest_dist = d
end
end
end
end
end
self.target = nc
end
function TargetSelector:far()
local fc = nil
local biggest_dist = -1
local count,object
local ignoreflag = false
if targetMouse == true then
for i = 1, #mouseArray, 1 do
if mouseArray[i].mode ~= "ignore" then
if self:check_if_target(mouseArray[i].enemy) then
local d = player:GetDistance(mouseArray[i].enemy)
if d<=self.range and d>biggest_dist then
fc = mouseArray[i].enemy
biggest_dist = d
end
end
end
end
else
if Array[1].enemy.charName == "###" then --backwards compatibility. If true, then not using updated script for text input.
count = heroManager.iCount
else
count = #Array
end
for i = 1, count, 1 do
if Array[1].enemy.charName == "###" then --backwards compatibility
object = heroManager:GetHero(i)
elseif Array[i].mode == "ignore" then
ignoreflag = true
elseif Array[i].mode ~= "ignore" then
ignoreflag = false
object = Array[i].enemy
end
if ignoreflag == false then
if self:check_if_target(object) then
local d = player:GetDistance(object)
if d<=self.range and d>biggest_dist then
fc = object
biggest_dist = d
end
end
end
end
end
self.target = fc
end
function TargetSelector:mostAP()
local highestAP = 0
local highest = nil
local ignoreflag = false
if targetMouse == true then
for i=1, #mouseArray, 1 do
if mouseArray[i].mode ~= "ignore" then
if self:check_if_target(mouseArray[i].enemy) then
if player:GetDistance(object)<=self.range then
if mouseArray[i].enemy.ap >= highestAP then
highest = mouseArray[i].enemy
highestAP=mouseArray[i].enemy.ap
end
end
end
end
end
else
for i=1, #Array, 1 do
if Array[i].mode ~= "ignore" then
if self:check_if_target(Array[i].enemy) then
if player:GetDistanceTo(Array[i].enemy)<=self.range then
if Array[i].enemy.ap >= highestAP then
highest = Array[i].enemy
highestAP=Array[i].enemy.ap
end
end
end
end
end
end
self.target = highest
end
function TargetSelector:findHeroNearestMouse()
local i
local j=1
mouseArray = { --A blank array for enemies near the Mouse
{enemy = {charName="###"}, mode = "target", priorityNum = 0},
{enemy = {charName="###"}, mode = "target", priorityNum = 0},
{enemy = {charName="###"}, mode = "target", priorityNum = 0},
{enemy = {charName="###"}, mode = "target", priorityNum = 0},
{enemy = {charName="###"}, mode = "target", priorityNum = 0}
}
for i=1, #Array, 1 do
if self:check_if_target(Array[i].enemy) then
if (self:distanceFromMouse(Array[i].enemy) <= mouseRange) then
mouseArray[j].enemy = Array[i].enemy
mouseArray[j].mode = Array[i].mode
mouseArray[j].priorityNum = Array[i].priorityNum
j = j+1
end
end
end
end
function TargetSelector:distanceFromMouse(target)
if target ~= nil then
return math.floor(math.sqrt((target.x-MousePos.x)*(target.x-MousePos.x) + (target.z-MousePos.z)*(target.z-MousePos.z)))
else return math.huge
end
end
function TargetSelector:check_if_target(obj)
return obj ~= nil and obj.team ~= player.team and obj.visible and not obj.dead
end
Vector = {
-- ===========================
-- Sets the metatable of the "point" table to
-- the Vector table. Kind of like dynamic inheritance.
-- ===========================
New = function(self,...)
local n = table.getn(arg)
local point
if(n==1) then
point = arg[1]
if not point or type(point) ~= "table" or point.x == nil or point.z == nil then
return
end
end
if(n==2) then
point = {x=arg[1], z=arg[2]}
end
setmetatable(point, {__index = self,
__add = self.Add,
__sub = self.Sub,
__eq = self.Equals,
__unm = self.Negative,
__mul = self.Mult,
__div = self.Div,
__lt = self.Lt,
__le = self.Le,
__tostring = self.ToString})
return point
end,
-- ===========================
-- Overload the "+" operator
-- ===========================
Add = function(self, other)
return Vector:New(self.x + other.x, self.z + other.z)
end,
-- ===========================
-- Overload the "-" operator
-- ===========================
Sub = function(self, other)
return Vector:New(self.x - other.x, self.z - other.z)
end,
-- ===========================
-- Check if the other point is the same as self.
-- ===========================
Equals = function(self, other)
return self.x == other.x and self.z == other.z
end,
-- ===========================
-- [Static] Check to see if x and y are close, as defined by eps (if it is passed)
-- ===========================
CloseTo = function(x, y, eps)
if not eps then
eps = 1e-9
end
return math.abs(x - y) <= eps
end,
-- ===========================
-- [Static] Get the intersection point of lines L1=(base1,v1) L2=(base2,v2)
-- ===========================
InfLineIntersection = function(base1, v1, base2, v2)
if Vector.CloseTo(v1.x, 0.0) and Vector.CloseTo(v2.z, 0.0) then
return Vector:New({x = base1.x, z = base2.z})
end
if Vector.CloseTo(v1.z, 0.0) and Vector.CloseTo(v2.x, 0.0) then
return Vector:New({x = base2.x, z = base1.z})
end
local m1 = (not Vector.CloseTo(v1.x, 0.0)) and v1.z / v1.x or 0.0
local m2 = (not Vector.CloseTo(v2.x, 0.0)) and v2.z / v2.x or 0.0
if Vector.CloseTo(m1, m2) then
return nil
end
local c1 = base1.z - m1 * base1.x
local c2 = base2.z - m2 * base2.x
local ix = (c2 - c1) / (m1 - m2)
local iy = m1 * ix + c1
if Vector.CloseTo(v1.x, 0.0) then
return Vector:New({x = base1.x, z = base1.x * m2 + c2})
end
if Vector.CloseTo(v2.x, 0.0) then
return Vector:New({x = base2.x, z = base2.x * m1 + c1})
end
return Vector:New({x = ix, z = iy})
end,
-- ===========================
-- Returns a positive number if self > other, 0 if self == other, and
-- a negative number if self < other.
-- ===========================
CompareTo = function(self, other)
local ret = self.x - other.x
if ret == 0 then
ret = self.z - other.z
end
return ret
end,
-- ===========================
-- Scale the current point to get a new point.
-- ===========================
Scale = function(self, factor)
return Vector:New({x = self.x * factor, z = self.z * factor})
end,
-- ===========================
-- Get the string representation of the point.
-- ===========================
ToString = function(self)
return "(" .. tostring(self.x) .. ", " .. tostring(self.z) .. ")"
end,
-- ===========================
-- Computes the angle formed by p1 - self - p2
-- ===========================
AngleBetween = function(self, p1, p2)
local vect_p1 = p1 - self
local vect_p2 = p2 - self
local theta = vect_p1:Polar() - vect_p2:Polar()
if theta < 0.0 then
theta = theta + 360.0
end
if theta > 180.0 then
theta = 360.0 - theta
end
return theta
end,
-- ===========================
-- Compute the polar angle to this point
-- ===========================
Polar = function(self)
theta = 0.0
if Vector.CloseTo(self.x, 0) then
if self.z > 0 then
theta = 90.0
elseif self.z < 0 then
theta = 270.0
end
else
theta = math.deg(math.atan(self.z/self.x))
if self.x < 0.0 then
theta = theta + 180.0
end
if theta < 0.0 then
theta = theta + 360.0
end
end
return theta
end,
-- ===========================
-- Gets the left normal according to self's position.
-- ===========================
NormalLeft = function(self)
return Vector:New({x = self.z, z = -self.x})
end,
-- ===========================
-- Get the center between self and the other point.
-- ===========================
Center = function(self, otherVector)
return Vector:New({x = (self.x + otherVector.x) / 2, z = (self.z + otherVector.z) / 2})
end,
-- ===========================
-- Get the distance between myself and the other point.
-- ===========================
Distance = function(self, otherVector)
local dx = self.x - otherVector.x
local dy = self.z - otherVector.z
return math.sqrt(dx * dx + dy * dy)
end,
-- ===========================
-- Tests if a point is left|on|right of an infinite line
-- return >0 for p2 left of line through p0 and p1.
-- return =0 for p2 on the line.
-- return <0 for p2 right of the line.
-- ===========================
Direction = function(p0, p1, p2)
return (p0.x-p1.x)*(p2.z-p1.z) - (p2.x-p1.x)*(p0.z-p1.z)
end,
-- ===========================
-- h0nda added staff
-- ===========================
Cross = function(self,other)
return self.x * other.z - self.z * other.x
end,
Negative = function(self)
return Vector:New({x=-1*self.x,z=-1*self.z})
end,
Mult = function (a, b)
if type(a) == "number" then
return Vector.new({x=b.x * a, y=b.z * a})
elseif type(b) == "number" then
return Vector:New({x=a.x * b, z=a.z * b})
else
return Vector:New({x=a.x * b.x, z=a.z * b.z})
end
end,
Div = function(a,b)
if type(a) == "number" then
return Vector.new({x=b.x / a, y=b.z / a})
elseif type(b) == "number" then
return Vector:New({x=a.x / b, z=a.z / b})
else
return Vector:New({x= a.x / b.x, z=a.z / b.z})
end
end,
Lt = function(a,b)
return a.x < b.x or (a.x == b.x and a.z < b.z)
end,
Le = function(a,b)
return a.x <= b.x and a.z <= b.z
end,
Clone = function(self)
return Vector:New({x=self.x, z=self.z})
end,
Unpack = function(self)
return self.x, self.z
end,
Length = function ( self )
return math.sqrt(self.x * self.x + self.z * self.z)
end,
LengthSQ = function ( self)
return self.x * self.x + self.z * self.z
end,
Normalize = function (self )
local len = self:Length()
self.x = self.x / len
self.z = self.z / len
return self
end,
Normalized = function (self )
return self / self:Length()
end,
Rotate = function(phi)
local c = math.cos(phi)
local s = math.sin(phi)
self.x = c * self.x - s * self.z
self.z = s * self.x + c * self.z
return self
end,
Rotated = function(phi)
return self:clone():rotate(phi)
end,
Perpendicular = function()
return Vector:New({x=-self.z, z=self.x})
end,
ProjectOn = function(other)
return (self * other) * other / other:lenSq()
end,
}
--[[
Veigar 2.0~ by llama
]]--
--HotKeys
EHK=69 --HK for "E" by default
cageTeamHK= 71 -- hk for "G" by default
spaceHK = 90 --hk for "X" by default
--Skill attributes
qrange=650
eradius=350 -- event horizon's radius has bounds from 300 to 400
erange=600
ecastspeed=350 --calculated from tick values before and after cast of even horizon
wrange = 900
wradius = 245 --maximum radius of W
wLandParticle = "dark_matter_tar"
DFGID = 3128
--Options
WAfterStun = true --if true, will attempt to cast W on a stunned target
spacebarToWin = true --true will use full combo on ts.target
comboDelay = 500 --time (in ms) to delay the combo. Prevents spam of combos before the first combo is complete.
assumeWHits = true --assume W will hit the target it was cast towards. This will premeptively subtract wDmg from target.health, if: it is the combo's focus target and W is still in the air.
stealOption = true --will attempt to auto-steal with up to 3 different spells, if the target is killable.
drawERange=true --draws E's range+radius
eCircleColor = 0xB820C3 -- purple by default
drawWRange=true --draws W's range
wCircleColor = 0xEA3737 -- orange by default
drawQRange=true --draws Q's (or R's) range
qCircleColor = 0x19A712 --green by default
drawKillable = true --draws a few circles around a killable target with full combo
circleRadius = 200 --radius of circle drawn
circleThickness = 75 --Higher means more vibrant circle.
--code
player = GetMyHero()
ECastActive = false
cageTeamActive = false
spacebarActive = false
ticknow=0
wInAir = false
wTarget = nil
comboflag = false
function altDoFile(name)
dofile(debug.getinfo(1).source:sub(debug.getinfo(1).source:find(".*\\")):sub(2)..name)end
altDoFile("libs/target_selector.lua")
altDoFile("libs/vector.lua")
altDoFile("libs/linear_prediction_const_t.lua")
altDoFile("libs/mec.lua")
altDoFile("libs/circle.lua")
lp = LinearPredictionConstT:new(erange+eradius,ecastspeed)
ts = TargetSelector:new( TARGET_LOW_HP,erange+eradius,DAMAGE_MAGIC )
ts:buildarray()
playername=player.charName
function Timer()
local dis = 0
local players = heroManager.iCount
local CircX = nil
local CircZ = nil
local i,j,stealtarget
ts:tick()
lp:tick()
if CanUseSpell(Slots._W) == SpellState.READY and WAfterStun then
castWspell()
end
if spacebarActive == true and targetvalid(ts.target) then
performcombo(ts.target,false)
end
if stealOption == true then
for i=1, players, 1 do
stealtarget = heroManager:GetHero(i)
if targetvalid(stealtarget) then
performcombo(stealtarget,true)
end
end
end
if ECastActive == true and CanUseSpell(Slots._E) == SpellState.READY and targetvalid(ts.target) then
--Begin comparing all targets to each other
for i=1, players, 1 do
for j=1, players, 1 do
local target1 = heroManager:GetHero(i)
local target2 = heroManager:GetHero(j)
if targetvalid(target1) and targetvalid(target2) and target1.charName ~= target2.charName then --make sure both targets are valid enemies and in spell range
if targetsinradius(target1,target2) and CircX == nil and CircY == nil then --true if a double stun is possible
CircX,CircZ = calcdoublestun(target1,target2) --calculates coords for stun
end
end
end
end
if CircX == nil or CircZ == nil then --true if double stun coords were not found
if player:GetDistance(ts.target) <= (eradius+erange) then
CircX,CircZ = calcsinglestun() --calculate stun coords for a single target
end
end
if CircX ~= nil and CircZ ~= nil then --true if any coords were found
player:Attack(ts.target)
CastSpell(Slots._E,CircX,CircZ)
end
end
if cageTeamActive == true and ts.target~=nil then
local spellPos = FindGroupCenterFromNearestEnemies(eradius,erange)
if spellPos ~=nil then
CastSpell(Slots._E,spellPos.center.x,spellPos.center.z)
end
end
end
function targetvalid(target)
return target ~= nil and target.team ~= player.team and target.visible and not target.dead and player:GetDistance(target) <= (eradius + erange)
end
function targetsinradius(target1,target2)
local dis,dis1,dis2
local predicted1 = lp:getPredictionFor(target1.charName)
local predicted2 = lp:getPredictionFor(target2.charName)
if predicted1 and predicted2 then
dis = math.sqrt((predicted2.x-predicted1.x)^2+(predicted2.z-predicted1.z)^2) --find the distance between the two targets
dis1 = math.sqrt((predicted1.x-player.x)^2+(predicted1.z-player.z)^2) --distance from player to predicted target 1
dis2 = math.sqrt((predicted2.x-player.x)^2+(predicted2.z-player.z)^2) --distance from player to predicted target 2
end
return dis ~=nil and dis <= (eradius*2) and dis1 <=(eradius + erange) and dis2 <=(eradius + erange)
end
function calcdoublestun(target1,target2)
local CircX = nil
local CircZ = nil
local predicted1 = lp:getPredictionFor(target1.charName)
local predicted2 = lp:getPredictionFor(target2.charName)
if predicted1 and predicted2 then
local h1=predicted1.x
local k1=predicted1.z
local h2=predicted2.x
local k2=predicted2.z
local u = (h1)^2 + (h2)^2 - 2*(h1)*(h2) - (k1)^2 + (k2)^2
local w = k1 - k2
local v = h2 - h1
local a = 4*(w^2 + v^2)
local b = 4*(u*w - 2*((v)^2)*(k1))
local c = (u)^2 - 4*((v^2))*(eradius^2-k1^2)
local Z1 = ((-b) + math.sqrt((b)^2 - 4*a*c))/(2*a) --Z coord for first solution
local Z2 = ((-b) - math.sqrt((b)^2 - 4*a*c))/(2*a) --Z coord for second solution
local d = (Z1 - k1)^2 - (eradius)^2
local e = (Z1 - k2)^2 - (eradius)^2
local X1 = ((h2)^2 - (h1)^2 - d + e)/(2*v) -- X Coord for first solution
local p = (Z2 - k1)^2 - (eradius)^2
local q = (Z2 - k2)^2 - (eradius)^2
local X2 = ((h2)^2 - (h1)^2 - p + q)/(2*v) --X Coord for second solution
--determine if these 2 points are within range, and which is closest
local dis1 = math.sqrt((X1 - player.x)^2+(Z1 - player.z)^2)
local dis2 = math.sqrt((X2 - player.x)^2+(Z2 - player.z)^2)
if dis1 <= (eradius + erange) and dis1 <=dis2 then
CircX = X1
CircZ = Z1
end
if dis2 <= (eradius + erange) and dis2 <dis1 then
CircX = X2
CircZ = Z2
end
end
return CircX,CircZ
end
function calcsinglestun()
if(ts.target ~= nil ) and CanUseSpell(Slots._E) == SpellState.READY then
local predicted = lp:getPredictionFor(ts.target.charName)
if predicted then
local CircX=nil
local CircZ=nil
local dis=math.sqrt((player.x-predicted.x)^2+(player.z-predicted.z)^2)
CircX=predicted.x+eradius*((player.x-predicted.x)/dis)
CircZ=predicted.z+eradius*((player.z-predicted.z)/dis)
return CircX,CircZ
end
end
end
function Hotkey(msg,key)
if msg == KEY_DOWN then
if key == EHK then
ECastActive = true
elseif key == cageTeamHK then
cageTeamActive = true
elseif key == spaceHK then
spacebarActive = true
end
else
if key == EHK then
ECastActive = false
elseif key == cageTeamHK then
cageTeamActive = false
elseif key == spaceHK then
spacebarActive = false
end
end
end
function castWspell()
local spellPos
local playercount = heroManager.iCount
for i=1, playercount, 1 do
local target = heroManager:GetHero(i)
if targetvalid(target) then
local count = target.buffCount
for j =0, count-1, 1 do
if target:getBuff(j) ~= nil then
if string.find(target:getBuff(j), "Stun") ~= nil then
if player:GetDistance(target) <= wrange then
--spellPos = FindGroupCenterNearTarget(target,wradius,wrange)
if spellPos ~= nil then
CastSpell(Slots._W,spellPos.center.x,spellPos.center.z)
else
CastSpell(Slots._W, target)
end
--wInAir = true
--wTarget = target
end
end
end
end
end
end
end
function performcombo(target,stealflag)
local wDmg,targetHealth
local skillArray = {}
skillArray,wDmg =findSkillDamage(target)
targetHealth = target.health
if wInAir == true and assumeWHits == true and wTarget == target then --will assume W will hit the current target.
targetHealth = targetHealth - wDmg
end
if targetvalid(target) then --check if target is alive, etc..
comboflag = false -- allows the use of a combo if set to false
end
if GetTickCount() > ticknow+comboDelay and targetHealth >= 0 then --prevents from combo spam. Delay between combos
--1 spell to kill
if comboflag == false then
for i=1,#skillArray, 1 do
if (skillArray[i].damage > targetHealth) and skillArray[i].state and targetvalid(target) then
CastSpell(skillArray[i].combo,target)
comboflag = true
ticknow = GetTickCount()
end
end
end
--2 spells to kill
if comboflag == false then
for j=1, #skillArray, 1 do
for i=1, #skillArray, 1 do
if i ~= j then
if ((skillArray[i].damage + skillArray[j].damage) > targetHealth) and skillArray[i].state and skillArray[j].state and targetvalid(target) then
if skillArray[i].dfgFlag == true then --want to use dfg first in the combo, if possible
CastSpell(skillArray[i].combo,target)
elseif skillArray[j].dfgFlag == true then
CastSpell(skillArray[j].combo,target)
end
CastSpell(skillArray[i].combo,target) --use combo, dfg will already be on CD if ready
CastSpell(skillArray[j].combo,target)
comboflag = true
ticknow = GetTickCount()
end
end
end
end
end
-- 3 spells to kill
if comboflag == false then
for k=1, #skillArray, 1 do
for j=1, #skillArray, 1 do
for i=1, #skillArray, 1 do
if i~=j and i~=k and j~=k then
if ((skillArray[i].damage + skillArray[j].damage + skillArray[k].damage) > targetHealth) and skillArray[i].state and skillArray[j].state and skillArray[k].state and targetvalid(target) then
if skillArray[i].dfgFlag == true then --want to use dfg first in the combo, if possible
CastSpell(skillArray[i].combo,target)
elseif skillArray[j].dfgFlag == true then
CastSpell(skillArray[j].combo,target)
elseif skillArray[k].dfgFlag == true then
CastSpell(skillArray[k].combo,target)
end
CastSpell(skillArray[i].combo,target) --use combo, dfg will already be on cd, if ready.
CastSpell(skillArray[j].combo,target)
CastSpell(skillArray[k].combo,target)
comboflag = true
ticknow = GetTickCount()
end
end
end
end
end
end
---- Usese 4 spells, even if the kill is not possible
if comboflag == false and targetvalid(target) and stealflag == false then --will only use all four spells if spacebar is active
for i=1, #skillArray, 1 do
if skillArray[i].dfgFlag == true and skillArray[i].state then --use dfg first in combo
CastSpell(skillArray[i].combo,target)
end
end
for i=1, #skillArray, 1 do
CastSpell(skillArray[1].combo,target) --use combo, dfg will already be on cd, if ready.
CastSpell(skillArray[2].combo,target)
CastSpell(skillArray[3].combo,target)
CastSpell(skillArray[4].combo,target)
comboflag = true
ticknow = GetTickCount()
end
end
end
end
function findSkillDamage(target)
local i, invSlot, igniteSlot, igniteDmg, qDmg, wDmg, rDmg, dfgDmg
local ItemSlot = {Slots.ITEM_1,Slots.ITEM_2,Slots.ITEM_3,Slots.ITEM_4,Slots.ITEM_5,Slots.ITEM_6,}
local skillArray = {}
local totalDmg = 0
for i=1, #ItemSlot, 1 do
if player:getInventorySlot(ItemSlot[i]) == (DFGID) then
invSlot = ItemSlot[i]
end
end
if invSlot ~= nil then
dfgDmg = (.25+(.04*(math.floor(player.ap/100))))*target.health
if dfgDmg < 200 then
dfgDmg = 200
end
elseif invSlot == nil then
dfgDmg = 0
end
if GetSpellData(Slots.SUMMONER_1).name == "SummonerDot" then
igniteSlot = Slots.SUMMONER_1
elseif GetSpellData(Slots.SUMMONER_2).name == "SummonerDot" then
igniteSlot = Slots.SUMMONER_2
end
if igniteSlot ~=nil then
igniteDmg = 50 + 20 * player.level
else
igniteDmg = 0
end
qDmg = player:CalcMagicDamage(target, 45*(GetSpellData(Slots._Q).level-1)+80+ (.6*player.ap))
wDmg = player:CalcMagicDamage(target, 50*(GetSpellData(Slots._W).level-1)+120+ (1*player.ap))
rDmg = player:CalcMagicDamage(target, 125*(GetSpellData(Slots._R).level-1)+250+ (1.2*player.ap) + (.8 * target.ap))
skillArray = {
{damage = qDmg, state = CanUseSpell(Slots._Q) == SpellState.READY, combo = Slots._Q, dfgFlag = false},
{damage = dfgDmg, state = false, combo = invSlot, dfgFlag = true},
{damage = igniteDmg, state = false, combo = igniteSlot, dfgFlag = false},
{damage = rDmg, state = CanUseSpell(Slots._R) == SpellState.READY, combo = Slots._R, dfgFlag = false}
}
if invSlot ~= nil then
skillArray[2].state = CanUseSpell(invSlot) == SpellState.READY
end
if igniteSlot ~=nil then
skillArray[3].state = CanUseSpell(igniteSlot) == SpellState.READY
end
--calculate total damage possible
for i=1, #skillArray, 1 do
if skillArray[i].state then
totalDmg = totalDmg + skillArray[i].damage
end
end
if CanUseSpell(Slots._W) == SpellState.READY and CanUseSpell(Slots._E) then
totalDmg = totalDmg + wDmg
end
skillArray.total = totalDmg
return skillArray,wDmg
end
function Drawer()
local i,j,players,qDmg
if drawQRange==true then
DrawCircle(player.x, player.y, player.z, qrange, qCircleColor)
end
if drawWRange==true then
DrawCircle(player.x,player.y,player.z, wrange, wCircleColor)
end
if drawERange==true then
DrawCircle(player.x,player.y,player.z, erange+eradius, eCircleColor)
end
if drawKillable == true then
players = heroManager.iCount
for i=1, players, 1 do
target = heroManager:GetHero(i)
if target~= nil then
if target.team ~= player.team and target.visible and not target.dead then
skillArray,wDmg = findSkillDamage(target)
if skillArray ~= nil then
if skillArray.total > target.health then
for j = 0, circleThickness do
DrawCircle(target.x, target.y, target.z, circleRadius + j*2, 0xFFFF0000)
end
end
end
end
end
end
end
end
function test(text)
ts:getChatCommand(text,playername)
end
function DetectWLand(object)
if string.find(object.charName,wLandParticle)~=nil then
wInAir = false
end
end
if player.charName == "Veigar" then
BoL:addMsgHandler(Hotkey)
BoL:addTickHandler(Timer,100)
BoL:addDrawHandler(Drawer)
BoL:addChatHandler(test)
BoL:addCreateObjHandler(DetectWLand)
PrintChat(" >> Veigar's Super Fun House 2.0 loaded!")
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment