Starting Guide
To execute your script move the .lua
file into the %cheat_dir%/scripts
folder.
Callbacks
Most of your code will be executed inside the callbacks. To register the callback function, your script should return a table in the following format:
return {
CallbackName = FuncHandler,
}
Example
-- much more convenient way for big scripts
local script = {}
function script.OnUpdate()
print("OnUpdate")
end
return script
or
return {
OnUpdate = function()
print("OnUpdate")
end,
}
Example Scripts
There is example scripts you can rely on:
Debug Script
---@diagnostic disable: undefined-global, param-type-mismatch, inject-field
local debug = {}
local tab = Menu.Create("General", "Debug", "Debug", "Debug")
local inworld_group = tab:Create("In-World")
local callbacks_group = tab:Create("Callbacks")
local inworld_settings_group = tab:Create("In-World Settings", 1)
local callbacks_settings_group = tab:Create("Callbacks Settings", 2)
--#region UI
local ui = {}
ui.inworld = {}
ui.inworld.global_switch = inworld_group:Switch("In-World Enabled", false)
ui.inworld.name = inworld_group:Switch("Unit Name", false)
ui.inworld.position = inworld_group:Switch("Unit Position", false)
ui.inworld.modifier = inworld_group:Switch("Modifiers", false)
ui.inworld.ability = inworld_group:Switch("Abilities", false)
ui.inworld.item = inworld_group:Switch("Items", false)
ui.inworld.modifier_state = inworld_group:Switch("Modifier State", false)
ui.inworld.modifier_state_duration = inworld_group:Switch("Modifier State Duration", false)
ui.inworld_settings = {}
ui.inworld_settings.hero_only = inworld_settings_group:Switch("Only heroes", true)
ui.inworld_settings.on_draw = inworld_settings_group:Switch("Render in OnDraw \a{primary}fps drop", false)
ui.callbacks = {}
ui.callbacks.modifier = callbacks_group:Switch("Modifier", false)
ui.callbacks.animation = callbacks_group:Switch("Animation", false)
ui.callbacks.add_entity = callbacks_group:Switch("Entity Create/Remove", false)
ui.callbacks.projectile = callbacks_group:Switch("Projectile", false)
ui.callbacks.particle = callbacks_group:Switch("Particle", false)
ui.callbacks.gesture = callbacks_group:Switch("Gesture", false)
ui.callbacks.sound = callbacks_group:Switch("Sound", false)
ui.callbacks.order = callbacks_group:Switch("Unit Order", false)
ui.callbacks_settings = {}
ui.callbacks_settings.divider = callbacks_settings_group:Switch("Add 'divider' in the end of the log message", true)
ui.callbacks_settings.add_more_info = callbacks_settings_group:Switch("More info. Starts with [m] prefix", true)
--#endregion
local font = Render.LoadFont("Arial", 0, 500)
local function add_divider()
if ui.callbacks_settings.divider:Get() then
print("+---+---+---+---+---+---+---+")
end
end
-- we can't modify the original table in callbacks, so we need to copy it to add more info
function table.copy(t)
local u = { }
for k, v in pairs(t) do u[k] = v end
return u
end
--#region Callbacks
--#region Modifier
function debug.OnModifierCreate(ent, mod)
if not ui.callbacks.modifier:Get() then return end
print("OnModifierCreate")
local modifier_name = Modifier.GetName(mod)
local owner = Ability.GetOwner(Modifier.GetAbility(mod))
local owner_name = NPC.GetUnitName(owner)
print(("%s | %s -> %s"):format(modifier_name, owner_name, NPC.GetUnitName(ent)))
add_divider()
end
function debug.OnModifierDestroy(ent, mod)
if not ui.callbacks.modifier:Get() then return end
print("OnModifierDestroy")
local modifier_name = Modifier.GetName(mod)
local owner = Ability.GetOwner(Modifier.GetAbility(mod))
local owner_name = NPC.GetUnitName(owner)
print(("%s | %s -> %s"):format(modifier_name, owner_name, NPC.GetUnitName(ent)))
add_divider()
end
--#endregion
--#region Animation
function debug.OnUnitAnimation(a)
if not ui.callbacks.animation:Get() then return end
print("OnUnitAnimation")
if (ui.callbacks_settings.add_more_info:Get() and a.unit and Entity.IsNPC(a.unit)) then
a = table.copy(a)
local unit_name = NPC.GetUnitName(a.unit)
a["[m]unit_name"] = unit_name
end
print(a)
add_divider()
end
function debug.OnUnitAnimationEnd(a)
if not ui.callbacks.animation:Get() then return end
print("OnUnitAnimationEnd")
if (ui.callbacks_settings.add_more_info:Get() and a.unit and Entity.IsNPC(a.unit)) then
a = table.copy(a)
local unit_name = NPC.GetUnitName(a.unit)
a["[m]unit_name"] = unit_name
end
print(a)
add_divider()
end
--#endregion
--#region EntityCreate
function debug.OnEntityCreate(entity)
if not ui.callbacks.add_entity:Get() then return end
print('OnEntityCreate')
-- can't use NPC.GetUnitNameor Abilit.GetName because entity is not fully filled in the first tick
local type = (function ()
if Entity.IsAbility(entity) then
return "Ability"
elseif Entity.IsNPC(entity) then
return "NPC"
elseif Entity.IsPlayer(entity) then
return "Player"
else
return "Entity"
end
end)()
print(("%s | %s | %d"):format(type, Entity.GetClassName(entity), Entity.GetIndex(entity)))
add_divider()
end
function debug.OnEntityDestroy(entity)
if not ui.callbacks.add_entity:Get() then return end
print('OnEntityDestroy')
local type = (function ()
if Entity.IsAbility(entity) then
return "Ability"
elseif Entity.IsNPC(entity) then
return "NPC"
elseif Entity.IsPlayer(entity) then
return "Player"
else
return "Entity"
end
end)()
print(("%s | %s | %d"):format(type, Entity.GetClassName(entity), Entity.GetIndex(entity)))
add_divider()
end
--#endregion
--#region Projectile
function debug.OnProjectile(proj)
-- range autoatacks, target abilities
if not ui.callbacks.projectile:Get() then return end
print("OnProjectile")
if ui.callbacks_settings.add_more_info:Get() then
proj = table.copy(proj)
if (proj.source and Entity.IsNPC(proj.source)) then
local unit_name = NPC.GetUnitName(proj.source)
proj["[m]unit_name"] = unit_name
end
if (proj.target and Entity.IsNPC(proj.target)) then
local unit_name = NPC.GetUnitName(proj.target)
proj["[m]target_name"] = unit_name
end
end
print(proj)
add_divider()
end
function debug.OnLinearProjectileCreate(proj)
-- mirana's arrow
if not ui.callbacks.projectile:Get() then return end
print("OnLinearProjectileCreate")
if ui.callbacks_settings.add_more_info:Get() then
proj = table.copy(proj)
if (proj.source and Entity.IsNPC(proj.source)) then
local unit_name = NPC.GetUnitName(proj.source)
proj["[m]unit_name"] = unit_name
end
end
print(proj)
add_divider()
end
function debug.OnProjectileLoc(proj)
if not ui.callbacks.projectile:Get() then return end
-- tinker's rockets
print("OnProjectileLoc")
print(proj)
add_divider()
end
--#endregion
--#region Particle
local particle_name_map = {}
function debug.OnParticleCreate(prt)
if not ui.callbacks.particle:Get() then return end
print("OnParticleCreate")
if ui.callbacks_settings.add_more_info:Get() then
prt = table.copy(prt)
if (prt.entity and Entity.IsNPC(prt.entity)) then
local unit_name = NPC.GetUnitName(prt.entity)
prt["[m]entity_name"] = unit_name
end
if (prt.entityForModifiers and Entity.IsNPC(prt.entityForModifiers)) then
local unit_name = NPC.GetUnitName(prt.entityForModifiers)
prt["[m]entityForModifiers_name"] = unit_name
end
particle_name_map[prt.index] = prt.name
end
print(prt)
add_divider()
end
function debug.OnParticleUpdate(prt)
if not ui.callbacks.particle:Get() then return end
-- wisp's tentacles spam
if (prt.controlPoint == 2 and prt.position == Vector(1.0, 1.0, 1.0)) then
return
end
print("OnParticleUpdate")
if ui.callbacks_settings.add_more_info:Get() then
prt = table.copy(prt)
if particle_name_map[prt.index] then
prt["[m]name"] = particle_name_map[prt.index]
end
end
print(prt)
add_divider()
end
function debug.OnParticleUpdateEntity(prt)
if not ui.callbacks.particle:Get() then return end
print("OnParticleUpdateEntity")
if ui.callbacks_settings.add_more_info:Get() then
prt = table.copy(prt)
if (prt.entity and Entity.IsNPC(prt.entity)) then
local unit_name = NPC.GetUnitName(prt.entity)
prt["[m]entity_name"] = unit_name
end
if particle_name_map[prt.index] then
prt["[m]name"] = particle_name_map[prt.index]
end
end
print(prt)
add_divider()
end
function debug.OnParticleUpdateFallback(prt)
if not ui.callbacks.particle:Get() then return end
print("OnParticleUpdateFallback")
if ui.callbacks_settings.add_more_info:Get() then
prt = table.copy(prt)
if particle_name_map[prt.index] then
prt["[m]name"] = particle_name_map[prt.index]
end
end
print(prt)
add_divider()
end
function debug.OnParticleDestroy(prt)
if not ui.callbacks.particle:Get() then return end
print("OnParticleDestroy")
if ui.callbacks_settings.add_more_info:Get() then
prt = table.copy(prt)
if particle_name_map[prt.index] then
prt["[m]name"] = particle_name_map[prt.index]
end
end
print(prt)
add_divider()
particle_name_map[prt.index] = nil
end
--#endregion
--#region Gesture
function debug.OnUnitAddGesture(a)
if not ui.callbacks.gesture:Get() then return end
print("OnUnitAddGesture")
print(a)
add_divider()
end
--#endregion
--#region Sound
function debug.OnStartSound(obj)
if not ui.callbacks.sound:Get() then return end
print("OnStartSound")
if ui.callbacks_settings.add_more_info:Get() then
obj = table.copy(obj)
if (obj.source and Entity.IsNPC(obj.source)) then
local unit_name = NPC.GetUnitName(obj.source)
obj["[m]source_name"] = unit_name
end
end
print(obj)
add_divider()
end
--#endregion
--#region Order
local flipped_order_enum = {}
for i, v in pairs(Enum.UnitOrder) do
flipped_order_enum[v] = i
end
local flipped_issuer_enum = {}
for i, v in pairs(Enum.PlayerOrderIssuer) do
flipped_issuer_enum[v] = i
end
function debug.OnPrepareUnitOrders(order)
if not ui.callbacks.order:Get() then return end
print("OnPrepareUnitOrders")
if ui.callbacks_settings.add_more_info:Get() then
order = table.copy(order)
if (order.npc and Entity.IsNPC(order.npc)) then
local unit_name = NPC.GetUnitName(order.npc)
order["[m]npc_name"] = unit_name
end
if (order.target and Entity.IsNPC(order.target)) then
local unit_name = NPC.GetUnitName(order.target)
order["[m]target_name"] = unit_name
end
if (order.ability and Entity.IsAbility(order.ability)) then
local ability_name = Ability.GetName(order.ability)
order["[m]ability_name"] = ability_name
end
if (order.order) then
local order_name = flipped_order_enum[order.order]
if order_name then
order["[m]order_name"] = order_name
end
end
end
print(order)
add_divider()
end
--#endregion
--#endregion
local text_color <const> = Color(255, 255, 255, 255)
local font_size <const> = 14
local line_height <const> = 18
local floor = math.floor
local flipped_modstate_enum = {}
for i, v in pairs(Enum.ModifierState) do
flipped_modstate_enum[v] = i
end
local function inworld_processing()
if not ui.inworld.global_switch:Get() then return end
local render_names = ui.inworld.name:Get()
local render_position = ui.inworld.position:Get()
local render_modifiers = ui.inworld.modifier:Get()
local render_abilities = ui.inworld.ability:Get()
local render_items = ui.inworld.item:Get()
local render_modifier_state = ui.inworld.modifier_state:Get()
local render_modifier_state_duration = ui.inworld.modifier_state_duration:Get()
local list = ui.inworld_settings.hero_only:Get() and Heroes.GetAll() or NPCs.GetAll()
for i, unit in pairs(list) do
if unit then
local pos = Entity.GetAbsOrigin(unit)
local screen_pos, is_visible = pos:ToScreen()
if not is_visible then
goto continue
end
if render_names then
local text = ("%s | %s | %d"):format(
Entity.GetClassName(unit),
Entity.GetClassName(unit),
Entity.GetIndex(unit)
)
Render.Text(font, font_size, text, screen_pos, text_color)
screen_pos = screen_pos + Vec2(0, line_height)
end
if render_position then
local text = ("%d, %d, %d"):format(
floor(pos.x),
floor(pos.y),
floor(pos.z)
)
Render.Text(font, font_size, text, screen_pos, text_color)
screen_pos = screen_pos + Vec2(0, line_height)
end
if render_modifiers then
local modifiers = NPC.GetModifiers(unit)
if modifiers then
for _, mod in pairs(modifiers) do
local text = Modifier.GetName(mod)
Render.Text(font, font_size, text, screen_pos, text_color)
screen_pos = screen_pos + Vec2(0, line_height)
end
end
end
if render_abilities then
for i = 0, 25 do
local ab = NPC.GetAbilityByIndex(unit, i)
if ab then
local text = ("%d: %s"):format(i, Ability.GetName(ab))
Render.Text(font, font_size, text, screen_pos, text_color)
screen_pos = screen_pos + Vec2(0, line_height)
end
end
end
if render_items then
for i = 0, 20 do
local item = NPC.GetItemByIndex(unit, i)
if item then
local text = ("%d: %s"):format(i, Ability.GetName(item))
Render.Text(font, font_size, text, screen_pos, text_color)
screen_pos = screen_pos + Vec2(0, line_height)
end
end
end
if render_modifier_state then
for state_name, state_value in pairs(Enum.ModifierState) do
local has_state = NPC.HasState(unit, state_value)
if (has_state) then
local text = state_name
Render.Text(font, font_size, text, screen_pos, text_color)
screen_pos = screen_pos + Vec2(0, line_height)
end
end
end
if render_modifier_state_duration then
local payload = {}
for i = 0, Enum.ModifierState.MODIFIER_STATE_LAST, 1 do
payload[i] = true
end
local states = NPC.GetStatesDuration(unit, payload, false)
for mod_state, duration in pairs(states) do
if (duration > 0.0) then
local name = flipped_modstate_enum[mod_state]
if (name) then
local text = ("%s: %.2f"):format(name, duration)
Render.Text(font, font_size, text, screen_pos, text_color)
screen_pos = screen_pos + Vec2(0, line_height)
end
end
end
end
end
::continue::
end
end
function debug.OnDraw()
if not ui.inworld_settings.on_draw:Get() then
return
end
inworld_processing()
end
function debug.OnUpdate()
if ui.inworld_settings.on_draw:Get() then
return
end
inworld_processing()
end
return debug
Example
local example = {}
--#region UI
local tab = Menu.Create("General", "Main", "Example")
tab:Icon("\u{f6b6}")
local group = tab:Create("Main"):Create("Group")
local ui = {}
ui.global_switch = group:Switch("Global Switch", false, "\u{f00c}")
ui.auto_phase = group:Switch("Auto Phase Boots", true, "panorama/images/items/phase_boots_png.vtex_c")
ui.custom_radius = group:Slider("Custom Radius", 0, 1000, 0, function (value)
if value == 0 then return "Disabled" end
return tostring(value)
end)
ui.custom_radius:Icon("\u{f1ce}")
ui.radius_color = group:ColorPicker("Radius Color", Color(255, 255, 255), "\u{f53f}")
ui.vbe_render = group:Switch("VBE Render", false, "\u{f06e}")
ui.particle_notif = group:Switch("Particle Notification", false, "\u{f0f3}")
ui.global_switch:SetCallback(function ()
ui.auto_phase:Disabled(not ui.global_switch:Get())
ui.custom_radius:Disabled(not ui.global_switch:Get())
ui.radius_color:Disabled(not ui.global_switch:Get())
ui.vbe_render:Disabled(not ui.global_switch:Get())
ui.particle_notif:Disabled(not ui.global_switch:Get())
end, true)
--#endregion UI
--#region Vars
local my_hero = nil
local particle = nil
local need_update_particle = false
local need_update_color = false
ui.custom_radius:SetCallback(function ()
need_update_particle = true
if ui.global_switch:Get() then
ui.radius_color:Disabled(ui.custom_radius:Get() == 0)
end
end, true)
ui.radius_color:SetCallback(function ()
need_update_color = true
end)
--#endregion Vars
local auto_phase = function ()
if not ui.auto_phase:Get() then return end
local item = NPC.GetItem(my_hero, "item_phase_boots")
if not item then return end
if not Ability.IsCastable(item, NPC.GetMana(my_hero)) then return end
if not NPC.IsRunning(my_hero) then return end
Ability.CastNoTarget(item)
return true
end
local custom_radius = function ()
if ui.custom_radius:Get() == 0 or need_update_particle or not Entity.IsAlive(my_hero) then
Particle.Destroy(particle)
particle = nil
need_update_particle = false
return
end
if not particle then
particle = Particle.Create("particles/ui_mouseactions/drag_selected_ring.vpcf", Enum.ParticleAttachment.PATTACH_ABSORIGIN_FOLLOW, my_hero)
Particle.SetControlPoint(particle, 2, Vector(ui.custom_radius:Get(), 255, 255))
local color = ui.radius_color:Get()
color = Vector(color.r, color.g, color.b)
Particle.SetControlPoint(particle, 1, color)
end
if particle and need_update_color then
local color = ui.radius_color:Get()
color = Vector(color.r, color.g, color.b)
Particle.SetControlPoint(particle, 1, color)
end
end
local font = Render.LoadFont("MuseoSansEx", Enum.FontCreate.FONTFLAG_ANTIALIAS)
local vbe_render = function ()
if not ui.vbe_render:Get() then return end
local is_visible = NPC.IsVisibleToEnemies(my_hero)
if not is_visible then return end
local pos = Entity.GetAbsOrigin(my_hero) + Vector(0, 0, NPC.GetHealthBarOffset(my_hero))
local render_pos, pos_is_visible = Render.WorldToScreen(pos)
if not pos_is_visible then return end
local x, y = render_pos.x, render_pos.y - 80
local text = "Visible"
local text_size = Render.TextSize(font, 30, text)
x = x - text_size.x / 2
Render.Text(font, 30, text, Vec2(x + 1, y + 1), Color(0, 0, 0))
Render.Text(font, 30, text, Vec2(x, y), Color(255, 255, 255))
end
--#region Callbacks
example.OnParticleCreate = function (data)
if not ui.global_switch:Get() or not ui.particle_notif:Get() then return end
--[[ data:
{
"index": "4",
"entity_id": "-1",
"fullName": "particles/units/heroes/hero_crystalmaiden/maiden_crystal_nova.vpcf",
"hash": "3519047136",
"particleNameIndex": "-2119646217882970990",
"name": "maiden_crystal_nova",
"attachType": "2",
"entityForModifiers": "<userdata>",
"entity_for_modifiers_id": "214"
}
]]
if data.entityForModifiers then
local str = '<img class="HeroIcon" src="file://{images}/heroes/'..Entity.GetUnitName(data.entityForModifiers)..'.png"/>'.." "..data.fullName
Chat.Print("ConsoleChat", str) --chat
Notification ({ --side
duration = 3,
timer = 3,
hero = Entity.GetUnitName(data.entityForModifiers),
-- primary_text = "Sunstrike",
-- primary_image = "panorama/images/spellicons/invoker_sun_strike_png.vtex_c",
--secondary_image = "panorama/images/spellicons/invoker/magus_apex/invoker_sun_strike_png.vtex_c",
secondary_text = data.fullName,
-- active = false,
position = Entity.GetAbsOrigin(data.entityForModifiers),
--sound = "sounds/ui/yoink"
})
else
Chat.Print("ConsoleChat", data.fullName) --chat
Notification ({ --side
duration = 3,
timer = 3,
--hero = Entity.GetUnitName(data.entityForModifiers),
primary_text = "Particle Create",
primary_image = "panorama/images/emoticons/dotakin_roshan_stars_png.vtex_c",
secondary_image = "\u{2b}",
secondary_text = data.fullName,
-- active = false,
--position = Entity.GetAbsOrigin(data.entityForModifiers),
--sound = "sounds/ui/yoink"
})
end
end
example.OnDraw = function ()
if not ui.global_switch:Get() or not my_hero then return end
vbe_render()
end
example.OnUpdate = function ()
if not ui.global_switch:Get() then
if particle then
Particle.Destroy(particle)
particle = nil
end
return
end
if not my_hero then my_hero = Heroes.GetLocal(); return end
custom_radius()
if auto_phase() then return end
end
--#endregion Callbacks
return example
Debugging
You can use print
or log
function to print messages to the console and %cheat_dir%/debug.log
file.
This is useful for debugging your scripts.
This functions will automaticly stringify your tables
Example
-- print all towers names and their positions
for _, tower in pairs(Towers.GetAll()) do
print(NPC.GetUnitName(tower), Entity.GetAbsOrigin(tower))
end
Useful Links
Here are some useful links:
Last updated