Name | Type | is_array | initial_value |
Asw1_Angle | real | Yes | |
Asw1_Damage | real | Yes | |
Asw1_DeltaX | real | Yes | |
Asw1_DeltaY | real | Yes | |
Asw1_DistanceCurrent | real | Yes | |
Asw1_DistanceMax | real | Yes | |
Asw1_Effect | effect | Yes | |
Asw1_GroupDamage | group | Yes | |
Asw1_GroupDamaged | group | Yes | |
Asw1_IndexContainer | integer | Yes | |
Asw1_IndexCount | integer | No | |
Asw1_IndexCurrent | integer | No | |
Asw1_IndexProcessor | integer | No | |
Asw1_IndexRecycleContainer | integer | Yes | |
Asw1_IndexRecycleCount | integer | No | |
Asw1_IndexTotalCount | integer | No | |
Asw1_LevelAbility | integer | Yes | |
Asw1_Point1 | location | Yes | |
Asw1_Point2 | location | Yes | |
Asw1_Point3 | location | Yes | |
Asw1_Speed | real | Yes | |
Asw1_UnitDummy | unit | Yes | |
Asw1_UnitTrigger | unit | Yes |
UnitIndexManager = {}
UnitIndexManager.__index = UnitIndexManager
function UnitIndexManager:GetInstance()
local instance = {
debugMode = true,
freeIds = {},
currentId = 1,
unitData = setmetatable({}, { __mode = "k" }),
mapBounds = nil,
enterTrigger = nil,
deathTrigger = nil
}
setmetatable(instance, UnitIndexManager)
return instance
end
function UnitIndexManager:GetfreeIds()
if #self.freeIds > 0 then
return table.remove(self.freeIds, 1)
else
local id = self.currentId
self.currentId = self.currentId + 1
return id
end
end
function UnitIndexManager:RecycleId(id)
if id ~= nil and id >= 1 then
table.insert(self.freeIds, id)
end
end
function UnitIndexManager:AssignId(unit)
if unit == nil then
if self.debugMode then
print("Error: Tried to assign ID to nil unit")
end
return false
end
local id = self:GetfreeIds()
if id ~= nil then
SetUnitUserData(unit, id)
self.unitData[id] = unit
if self.debugMode then
print("Assigned ID:", id, "to unit:", GetUnitName(unit))
end
return true
end
return false
end
function UnitIndexManager:RemoveId(unit)
if unit == nil then
if self.debugMode then
print("Error: Tried to remove ID from nil unit")
end
return false
end
local id = GetUnitUserData(unit)
if id ~= nil and id >= 1 then
self:RecycleId(id)
SetUnitUserData(unit, 0)
self.unitData[id] = nil
if self.debugMode then
print("Removed ID:", id, "from unit:", GetUnitName(unit))
end
return true
end
return false
end
function UnitIndexManager:OnUnitEnter()
local unit = GetTriggerUnit()
if unit and UnitAlive(unit) and not BlzIsUnitInvulnerable(unit) then
self:AssignId(unit)
elseif self.debugMode then
print("Warning: Spawn event triggered with no valid unit")
end
end
function UnitIndexManager:OnUnitDeath()
local unit = GetTriggerUnit()
if unit then
self:RemoveId(unit)
elseif self.debugMode then
print("Warning: Remove event triggered with no unit")
end
end
function UnitIndexManager:Initialize()
self.mapBounds = GetPlayableMapRect() or GetWorldBounds()
self.enterTrigger = CreateTrigger()
TriggerRegisterEnterRectSimple(self.enterTrigger, self.mapBounds)
TriggerAddAction(self.enterTrigger, function()
self:OnUnitEnter()
end)
self.deathTrigger = CreateTrigger()
for i = 1, (bj_MAX_PLAYER_SLOTS or 24) do
TriggerRegisterPlayerUnitEvent(self.deathTrigger, Player(i - 1), EVENT_PLAYER_UNIT_DEATH, nil)
end
TriggerAddAction(self.deathTrigger, function()
self:OnUnitDeath()
end)
local preplacedGroup = GetUnitsInRectAll(self.mapBounds)
ForGroup(preplacedGroup, function()
local unit = GetEnumUnit()
if unit and GetUnitUserData(unit) == 0 then
self:AssignId(unit)
end
end)
DestroyGroup(preplacedGroup)
end
local function InitializeUnitIndexManager()
local unitIdInstance = UnitIndexManager:GetInstance()
unitIdInstance:Initialize()
end
local initializeTrigger = CreateTrigger()
TriggerRegisterTimerEventSingle(initializeTrigger, 0.05, false)
TriggerAddAction(initializeTrigger, InitializeUnitIndexManager)
DirectDynamicDamageDetection = {}
DirectDynamicDamageDetection.__index = DirectDynamicDamageDetection
function DirectDynamicDamageDetection:GetInstance()
local instance = {
damageData = setmetatable({}, { __mode = "v" }),
texttagPool = {},
texttagCleanupTimer = CreateTimer(),
damageCleanupTimer = CreateTimer(),
isInitialized = false,
maxtexttagPoolSize = 64,
damageEventTrigger = nil
}
setmetatable(instance, DirectDynamicDamageDetection)
return instance
end
function DirectDynamicDamageDetection:CleanUpdamageData()
for sourceId, targetData in pairs(self.damageData) do
for targetId, damageEntry in pairs(targetData) do
if not damageEntry.sourceUnit or not damageEntry.targetUnit then
targetData[targetId] = nil
end
end
if next(targetData) == nil then
self.damageData[sourceId] = nil
end
end
end
function DirectDynamicDamageDetection:CleanUpTextTag()
local i = #self.texttagPool
while i >= 1 do
local tag = self.texttagPool[i]
if not tag or not IsTextTagVisible(tag) then
if tag then DestroyTextTag(tag) end
table.remove(self.texttagPool, i)
end
i = i - 1
end
end
function DirectDynamicDamageDetection:RecycleTextTag(tag)
if tag then
if #self.texttagPool < self.maxtexttagPoolSize then
SetTextTagVisibility(tag, false)
table.insert(self.texttagPool, tag)
else
DestroyTextTag(tag)
end
end
end
function DirectDynamicDamageDetection:GetTextTag()
if #self.texttagPool > 0 then
return table.remove(self.texttagPool)
end
return CreateTextTag()
end
function DirectDynamicDamageDetection:GetUnitZ(unit)
return unit and (GetUnitFlyHeight(unit) + 60) or 60
end
function DirectDynamicDamageDetection:OnUnitDamage()
local eventType = GetTriggerEventId()
local sourceUnit = GetEventDamageSource()
local targetUnit = BlzGetEventDamageTarget()
if not sourceUnit or not targetUnit then return end
local sourceId = GetUnitUserData(sourceUnit)
local targetId = GetUnitUserData(targetUnit)
if not self.damageData[sourceId] then
self.damageData[sourceId] = {}
end
if eventType == EVENT_PLAYER_UNIT_DAMAGING then
self.damageData[sourceId][targetId] = {
sourceUnit = sourceUnit,
targetUnit = targetUnit,
damage = GetEventDamage()
}
elseif eventType == EVENT_PLAYER_UNIT_DAMAGED then
local damageEntry = self.damageData[sourceId][targetId]
local damage = GetEventDamage()
if damageEntry and damageEntry.sourceUnit == sourceUnit and damageEntry.targetUnit == targetUnit then
if damage >= 1 then
local tag = self:GetTextTag()
if tag then
SetTextTagText(tag, "-" .. R2I(damage + 0.5), 0.020)
SetTextTagPos(tag, GetUnitX(targetUnit), GetUnitY(targetUnit), self:GetUnitZ(targetUnit))
SetTextTagColor(tag, 255, 125, 125, 255)
SetTextTagVelocity(tag, 0.00, 0.03)
SetTextTagPermanent(tag, false)
SetTextTagLifespan(tag, 1.25)
SetTextTagFadepoint(tag, 0.75)
SetTextTagVisibility(tag, true)
end
end
self.damageData[sourceId][targetId] = nil
end
end
end
function DirectDynamicDamageDetection:Initialize()
if self.isInitialized then return end
self.isInitialized = true
self.damageEventTrigger = CreateTrigger()
local maxPlayers = bj_MAX_PLAYER_SLOTS or 24
for i = 1, maxPlayers do
TriggerRegisterPlayerUnitEvent(self.damageEventTrigger, Player(i - 1), EVENT_PLAYER_UNIT_DAMAGING, nil)
TriggerRegisterPlayerUnitEvent(self.damageEventTrigger, Player(i - 1), EVENT_PLAYER_UNIT_DAMAGED, nil)
end
TriggerAddAction(self.damageEventTrigger, function()
self:OnUnitDamage()
end)
TimerStart(self.texttagCleanupTimer, 2.5, true, function()
self:CleanUpTextTag()
end)
TimerStart(self.damageCleanupTimer, 60.0, true, function()
self:CleanUpdamageData()
end)
end
local initializeTrigger = CreateTrigger()
TriggerRegisterTimerEventSingle(initializeTrigger, 0.10, false)
TriggerAddAction(initializeTrigger, function()
local damageDetectionInstance = DirectDynamicDamageDetection:GetInstance()
damageDetectionInstance:Initialize()
end)
--[[
PseudoIllusionsSystem - A system for creating pseudo-illusions by Glucose
Features:
- Creates illusions of target units with modified damage properties
- Syncs stats, abilities, and items with the original unit
- Handles illusion expiration and cleanup
Usage:
1. Call PseudoIllusionsSystem:GetInstance() to create an instance
2. Configure with your ability/pseudo-buff IDs
3. Call :Initialize() or :DelayedInitialize() to start the system
--]]
-- Create the main system table
PseudoIllusionsSystem = {}
PseudoIllusionsSystem.__index = PseudoIllusionsSystem
--[[
Creates a new instance of the PseudoIllusionsSystem
@param abilityId: The ability ID that marks illusion units (default: 'API0')
@param spellId: The spell ID that triggers illusion creation (default: 'API1')
@param buffString: The order string for the pseudo-buff to apply (default: "metamorphosis")
@param buffId: The pseudo-buff ID to disable (default: 'API2')
@param duration: How long illusions should last in seconds (default: 60.0)
@param offset: Distance from target to spawn illusions (default: 200)
@return: A new PseudoIllusionsSystem instance
--]]
function PseudoIllusionsSystem:GetInstance(abilityId, spellId, buffString, buffId, duration, offset)
-- Create instance with default or provided values
local instance = {
abilityId = abilityId or FourCC('API0'), -- Ability ID to identify illusions
spellId = spellId or FourCC('API1'), -- Spell ID that triggers illusion creation
buffString = buffString or "metamorphosis", -- Order string for pseudo-buff to apply
buffId = buffId or FourCC('API2'), -- Buff ID to disable on illusions
illusionDuration = duration or 60.0, -- How long illusions last (seconds)
illusionOffset = offset or 200, -- Spawn distance from target
illusionData = {}, -- Table to track active illusions
isInitialized = false, -- Flag to prevent double initialization
damagingTrigger = nil, -- Trigger for damage modification
initializeTrigger = nil, -- Trigger for illusion creation
}
setmetatable(instance, PseudoIllusionsSystem)
return instance
end
--[[
Handles cleanup when an illusion unit dies
@param illusionData: The data table for the dying illusion
--]]
function PseudoIllusionsSystem:OnUnitDeath(illusionData)
if not illusionData or not illusionData.triggeringUnit then return end
local illusionUnit = illusionData.triggeringUnit
-- Create death effect
local effect = AddSpecialEffect("Abilities\\Spells\\Orc\\MirrorImage\\MirrorImageDeathCaster.mdl",
GetUnitX(illusionUnit), GetUnitY(illusionUnit))
DestroyEffect(effect)
-- Clean up stored data
local unitId = GetUnitUserData(illusionUnit)
self.illusionData[unitId] = nil
-- Remove the unit
RemoveUnit(illusionUnit)
-- Clean up all associated triggers and timers
for _, resource in pairs({"expirationTimer", "syncTimer", "itemTrigger", "deathTrigger"}) do
if illusionData[resource] then
if resource:find("Timer") then
DestroyTimer(illusionData[resource])
else
DestroyTrigger(illusionData[resource])
end
illusionData[resource] = nil
end
end
end
--[[
Handles item pickup attempts by illusions (prevents them from taking items)
@param illusionData: The data table for the illusion attempting pickup
--]]
function PseudoIllusionsSystem:OnUnitPickupItem(illusionData)
if not illusionData or not illusionData.triggeringUnit then return end
local illusionUnit = illusionData.triggeringUnit
local item = GetManipulatedItem()
if not item then return end
-- Drop the item back where it was picked up
SetItemPosition(item, GetUnitX(illusionUnit), GetUnitY(illusionUnit))
end
--[[
Modifies damage dealt by or to illusions
Called during the EVENT_PLAYER_UNIT_DAMAGING event
--]]
function PseudoIllusionsSystem:OnUnitDamaging()
if not GetEventDamageSource() or not BlzGetEventDamageTarget() then return end
local damageSource = GetEventDamageSource()
local damageTarget = BlzGetEventDamageTarget()
local damage = GetEventDamage()
-- If damage source is an illusion, reduce its damage output
if GetUnitAbilityLevel(damageSource, self.abilityId) == 1 then
BlzSetEventDamage(damage * 0.25) -- Illusions deal 25% damage
return
end
-- If damage target is an illusion, increase damage taken
if GetUnitAbilityLevel(damageTarget, self.abilityId) == 1 then
BlzSetEventDamage(damage * 2.0) -- Illusions take 200% damage
return
end
end
--[[
Creates an illusion when the spell is cast
Called during the EVENT_PLAYER_UNIT_SPELL_EFFECT event
--]]
function PseudoIllusionsSystem:OnUnitSpellEffect()
if not GetSpellAbilityUnit() or not GetSpellTargetUnit() then return end
local caster = GetSpellAbilityUnit()
local player = GetOwningPlayer(caster)
local target = GetSpellTargetUnit()
-- Get target unit's type ID and validate
local targetTypeId = GetUnitTypeId(target)
if not targetTypeId or targetTypeId == 0 then return end
-- Convert unit type ID to illusion type ID (prepend 'I' to rawcode)
local illusionTypeString = "I" .. string.sub(string.char(
(targetTypeId >> 24) & 0xFF,
(targetTypeId >> 16) & 0xFF,
(targetTypeId >> 8) & 0xFF,
targetTypeId & 0xFF
), 2)
local illusionTypeId = (string.byte(illusionTypeString, 1) << 24) |
(string.byte(illusionTypeString, 2) << 16) |
(string.byte(illusionTypeString, 3) << 8) |
string.byte(illusionTypeString, 4)
if not illusionTypeId then return end
-- Create illusion unit at random position around target
local illusionUnit = CreateUnit(player, illusionTypeId,
GetUnitX(target) + self.illusionOffset * Cos(GetRandomReal(0, 6.283)),
GetUnitY(target) + self.illusionOffset * Sin(GetRandomReal(0, 6.283)),
GetUnitFacing(target))
if not illusionUnit then return end
-- Set up illusion data storage
local illusionData = {
triggeringUnit = illusionUnit,
expirationTimer = CreateTimer(), -- Timer for illusion duration
syncTimer = CreateTimer(), -- Timer for initial stat sync
itemTrigger = CreateTrigger(), -- Trigger for item pickup
deathTrigger = CreateTrigger(), -- Trigger for death
}
self.illusionData[GetUnitUserData(illusionUnit)] = illusionData
-- Set up expiration timer
TimerStart(illusionData.expirationTimer, self.illusionDuration, false, function()
if UnitAlive(illusionUnit) then
KillUnit(illusionUnit)
end
DestroyTimer(GetExpiredTimer())
end)
-- Apply blue tint to illusion for local player
if GetLocalPlayer() == player then
SetUnitVertexColor(illusionUnit, 15, 63, 183, 223)
end
-- Play creation effect
local effect = AddSpecialEffectTarget("Abilities\\Spells\\Items\\AIil\\AIilTarget.mdl", illusionUnit, "origin")
DestroyEffect(effect)
-- Sync hero level if target is a hero
if IsUnitType(target, UNIT_TYPE_HERO) then
local targetHeroLevel = GetHeroLevel(target)
if GetHeroLevel(illusionUnit) ~= targetHeroLevel then
SetHeroLevel(illusionUnit, targetHeroLevel, false)
end
SuspendHeroXP(illusionUnit, true) -- Prevent illusion from gaining XP
end
-- Apply buff and disable it (standard illusion behavior)
IssueImmediateOrder(illusionUnit, self.buffString)
BlzUnitDisableAbility(illusionUnit, self.buffId, true, true)
-- Initial stat sync timer
TimerStart(illusionData.syncTimer, 0.00, false, function()
if UnitAlive(illusionUnit) and UnitAlive(target) then
-- Sync health and mana values
BlzSetUnitMaxHP(illusionUnit, BlzGetUnitMaxHP(target))
SetUnitState(illusionUnit, UNIT_STATE_LIFE, GetUnitState(target, UNIT_STATE_LIFE))
SetUnitState(illusionUnit, UNIT_STATE_MANA, GetUnitState(target, UNIT_STATE_MANA))
BlzSetUnitBaseDamage(illusionUnit, BlzGetUnitBaseDamage(target, 0), 0)
end
DestroyTimer(GetExpiredTimer())
illusionData.syncTimer = nil
end)
-- Copy abilities from target to illusion
local i = 0
while true do
local ability = BlzGetUnitAbilityByIndex(target, i)
if not ability then break end
-- Skip item abilities
if not BlzGetAbilityBooleanField(ability, ABILITY_BF_ITEM_ABILITY) then
local abilityId = BlzGetAbilityId(ability)
UnitAddAbility(illusionUnit, abilityId)
SetUnitAbilityLevel(illusionUnit, abilityId, GetUnitAbilityLevel(target, abilityId))
end
i = i + 1
end
-- Copy items from target to illusion
for i = 1, bj_MAX_INVENTORY do
local item = UnitItemInSlot(target, i - 1)
if item then
local illusionItem = CreateItem(GetItemTypeId(item), GetUnitX(illusionUnit), GetUnitY(illusionUnit))
-- Copy item charges if applicable
local charges = GetItemCharges(item)
if charges > 0 then
SetItemCharges(illusionItem, charges)
end
UnitAddItem(illusionUnit, illusionItem)
end
end
-- Set up triggers for item pickup and death
TriggerRegisterUnitEvent(illusionData.itemTrigger, illusionUnit, EVENT_UNIT_PICKUP_ITEM)
TriggerRegisterUnitEvent(illusionData.deathTrigger, illusionUnit, EVENT_UNIT_DEATH)
TriggerAddAction(illusionData.itemTrigger, function() self:OnUnitPickupItem(illusionData) end)
TriggerAddAction(illusionData.deathTrigger, function() self:OnUnitDeath(illusionData) end)
end
--[[
Initializes the system by setting up required triggers
--]]
function PseudoIllusionsSystem:Initialize()
if self.isInitialized then return end
self.isInitialized = true
-- Create triggers
self.damagingTrigger = CreateTrigger()
self.initializeTrigger = CreateTrigger()
-- Register events for all players
for i = 1, bj_MAX_PLAYER_SLOTS do
TriggerRegisterPlayerUnitEvent(self.damagingTrigger, Player(i - 1), EVENT_PLAYER_UNIT_DAMAGING)
TriggerRegisterPlayerUnitEvent(self.initializeTrigger, Player(i - 1), EVENT_PLAYER_UNIT_SPELL_EFFECT)
end
-- Add actions to triggers
TriggerAddAction(self.damagingTrigger, function() self:OnUnitDamaging() end)
TriggerAddAction(self.initializeTrigger, function() self:OnUnitSpellEffect() end)
-- Only trigger on the specific spell ID
TriggerAddCondition(self.initializeTrigger, Condition(function() return GetSpellAbilityId() == self.spellId end))
end
--[[
Initializes the system after a delay
@param delay: How long to wait before initialization (default: 0.20)
--]]
function PseudoIllusionsSystem:DelayedInitialize(delay)
TimerStart(CreateTimer(), delay or 0.20, false, function()
self:Initialize()
DestroyTimer(GetExpiredTimer())
end)
end
--[[
Cleans up the system, destroying all triggers and active illusions
--]]
function PseudoIllusionsSystem:Destroy()
-- Clean up damage trigger
if self.damagingTrigger then
DestroyTrigger(self.damagingTrigger)
self.damagingTrigger = nil
end
-- Clean up spell trigger
if self.initializeTrigger then
DestroyTrigger(self.initializeTrigger)
self.initializeTrigger = nil
end
-- Clean up all active illusions
for _, illusionData in pairs(self.illusionData) do
self:OnUnitDeath(illusionData)
end
-- Reset system state
self.illusionData = {}
self.isInitialized = false
end
-- Create a default instance of the system
local illusionsInstance = PseudoIllusionsSystem:GetInstance(
FourCC('API0'), -- Ability ID to mark illusions
FourCC('API1'), -- Spell ID that creates illusions
"metamorphosis", -- Buff order string
FourCC('API2'), -- Buff ID to disable
60.0, -- Illusion duration (seconds)
200 -- Spawn offset distance
)
-- Initialize after a short delay
illusionsInstance:DelayedInitialize(0.20)