• Check out the results of the Techtree Contest #19!
  • Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
  • Create a void inspired texture for Warcraft 3 and enter Hive's 34th Texturing Contest: Void! Click here to enter!
  • The Hive's 22nd Icon Contest: Creep Abilities is now concluded, time to vote for your favourite set of icons! Click here to vote!

Pseudo Illusions System

This bundle is marked as pending. It has not been reviewed by a staff member yet.

Pseudo Illusions System v1.0​

This is my first time creating an entire spell in Lua! In my map, I needed illusions that could mimic normal units while being easy to add, level up abilities, remove abilities, and do everything a normal unit can do.

To meet these needs, I stepped out of my comfort zone with GUI-based triggers and dove into the challenges of scripting. It was a tough journey full of struggles, but I learned a lot from everyone—and even from every AI—along the way. :xxd:

I've also just added a Jass version.

Big thanks to the Hive Workshop Discord for their endless wisdom and support!

Features​

Creates units that behave like illusions, dealing reduced damage and taking increased damage.
Copies the target unit's abilities and items.

Installation​

Copy the Pseudo Illusions System folder to your map.
Copy or create these three abilities with the exact rawcodes:
  • API0: Inventory Lock
  • API1: Trigger Ability
  • API2: Metamorphosis (used as a visual timer)
Copy or create an "I"-prefixed version of units in the Object Editor (e.g., Oake → Iake).
Make sure to set up all the required Object Editor data (see documentation for details).

Note: I've included a PSDillusionIcon.psd file for creating blue-tinted icons to represent illusions.

Update​

  • Added Jass version.
Contents

Pseudo Illusions System (Map)

Pseudo Illusions System (Map)

Initialization is a critical part of a map. I fear your initialization on Lua root will cause desyncs. Either use Total Initialization from Bribe (OnInit.map function) or hook into something such as InitBlizzard. Example:
Lua:
local oldInit = InitBlizzard
function InitBlizzard()
    oldInit()
    --initialize
end

Unit Index Manager script:
I don't understand the use of OOP here, you're not creating multiple instances, and the getInstance method has no variance.
redundant checks: UnitIndexManager:OnUnitEnter already checks if unit is nil, no need to check it again on UnitIndexManager:AssignId.
How can a triggered event be fired if the "unit" variable, with the event response, is nil? Besides that, I've never tested if creating a corpse triggers this event.

local id = GetUnitUserData(unit) this will never be nil, unless someone goes out of their way to replace the function.

Unit Indexer is going to fail if units are revived. It should be done when unit is about to be removed from the game, which can be detected by the undefend order bug.
Generally modifying GetUnitUserData isn't allowed unless this resource was a Unit Indexer system. The reason is there's already existing Unit Indexers and they most likely provide a better implementation than this one and the user is using one of them. Even so, in Lua it isn't really needed, since you can make a table, storing the unit itself as a key, with whatever value.

I took a glance at Direct Dynamic Damage Detection and I don't see its relevance for the system the resource is about. Please move it to its own folder so it doesn't get confused with the scripts that the system requires, and the system itself.
[In Addition, why does damage detection system has a GetUnitZ function?]

Lua:
-- 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
)
This would seem more appropriate as an example, and not inherent to the system.

Lua:
--[[
    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
The "illusion" already can't pickup items since it has the ability which locks the inventory, this function is redundant.
You have similar redundant checks:
if not GetEventDamageSource() or not BlzGetEventDamageTarget() then return end
It's not possible for a non-existing unit to be damaged, so the 2nd operand isn't needed.

Lua:
GetUnitX(target) + self.illusionOffset * Cos(GetRandomReal(0, 6.283)),
    GetUnitY(target) + self.illusionOffset * Sin(GetRandomReal(0, 6.283)),
That number is bj_PI*2 (TAU), you should use it.

It doesn't look like the system accounts for units with Reincarnation ability.
I think you may also have to copy the damage "number of dice" and "sides per die" onto the illusion.
Item position becomes always slot-ordered if the hero happens to have some empty slots while having an item at any slot from 2-5. It's a minor issue. If you're trying to mimic the standard illusions, then it's missing the change of unit vertex colour for allies, besides the owner of illusion. Even with experience disabled I received less exp than I should have. I've got 85 exp without illusions, 43 exp with 1 illusion. Furthermore, the illusions are currently giving exp points to enemies, and bounty if they happen to be killed, and if bounty is enabled.

I think you have a nice idea and the code itself isn't bad, but it needs some reworks in several aspects, such as:
More configurables, such as the special effect paths for the creation and the destruction of the illusions.
A clearer API: PseudoIllusionsSystem.Create(sourceUnit, illusionId, spawnOffset, damagedFactor, attackDamageFactor) -> unit
PseudoIllusionsSystem.IsIllusion(unit) -> boolean
Doesn't need to have these exact names of course.
Is it easier for the user to input the illusion Id version themselves or the system can do it itself? I don't know. However, I wouldn't risk the FourCC and reverse functions you made, specially since blizzard's function may fail in some edge case (Luashine discovered this).

And this was mostly about the Lua version. Next is the vJASS (you have JASS tag instead of vJASS :P)
 
Initialization is a critical part of a map. I fear your initialization on Lua root will cause desyncs. Either use Total Initialization from Bribe (OnInit.map function) or hook into something such as InitBlizzard. Example:
Lua:
local oldInit = InitBlizzard
function InitBlizzard()
    oldInit()
    --initialize
end

Unit Index Manager script:
I don't understand the use of OOP here, you're not creating multiple instances, and the getInstance method has no variance.
redundant checks: UnitIndexManager:OnUnitEnter already checks if unit is nil, no need to check it again on UnitIndexManager:AssignId.
How can a triggered event be fired if the "unit" variable, with the event response, is nil? Besides that, I've never tested if creating a corpse triggers this event.

local id = GetUnitUserData(unit) this will never be nil, unless someone goes out of their way to replace the function.

Unit Indexer is going to fail if units are revived. It should be done when unit is about to be removed from the game, which can be detected by the undefend order bug.
Generally modifying GetUnitUserData isn't allowed unless this resource was a Unit Indexer system. The reason is there's already existing Unit Indexers and they most likely provide a better implementation than this one and the user is using one of them. Even so, in Lua it isn't really needed, since you can make a table, storing the unit itself as a key, with whatever value.

I took a glance at Direct Dynamic Damage Detection and I don't see its relevance for the system the resource is about. Please move it to its own folder so it doesn't get confused with the scripts that the system requires, and the system itself.
[In Addition, why does damage detection system has a GetUnitZ function?]

Lua:
-- 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
)
This would seem more appropriate as an example, and not inherent to the system.

Lua:
--[[
    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
The "illusion" already can't pickup items since it has the ability which locks the inventory, this function is redundant.
You have similar redundant checks:
if not GetEventDamageSource() or not BlzGetEventDamageTarget() then return end
It's not possible for a non-existing unit to be damaged, so the 2nd operand isn't needed.

Lua:
GetUnitX(target) + self.illusionOffset * Cos(GetRandomReal(0, 6.283)),
    GetUnitY(target) + self.illusionOffset * Sin(GetRandomReal(0, 6.283)),
That number is bj_PI*2 (TAU), you should use it.

It doesn't look like the system accounts for units with Reincarnation ability.
I think you may also have to copy the damage "number of dice" and "sides per die" onto the illusion.
Item position becomes always slot-ordered if the hero happens to have some empty slots while having an item at any slot from 2-5. It's a minor issue. If you're trying to mimic the standard illusions, then it's missing the change of unit vertex colour for allies, besides the owner of illusion. Even with experience disabled I received less exp than I should have. I've got 85 exp without illusions, 43 exp with 1 illusion. Furthermore, the illusions are currently giving exp points to enemies, and bounty if they happen to be killed, and if bounty is enabled.

I think you have a nice idea and the code itself isn't bad, but it needs some reworks in several aspects, such as:
More configurables, such as the special effect paths for the creation and the destruction of the illusions.
A clearer API: PseudoIllusionsSystem.Create(sourceUnit, illusionId, spawnOffset, damagedFactor, attackDamageFactor) -> unit
PseudoIllusionsSystem.IsIllusion(unit) -> boolean
Doesn't need to have these exact names of course.
Is it easier for the user to input the illusion Id version themselves or the system can do it itself? I don't know. However, I wouldn't risk the FourCC and reverse functions you made, specially since blizzard's function may fail in some edge case (Luashine discovered this).

And this was mostly about the Lua version. Next is the vJASS (you have JASS tag instead of vJASS :p)
Thank you so much for taking the time to review my spell. I'm very new to creating custom spells in WC3 and still learning. I had an idea I wanted to try, but I wasn't sure how to begin, so I've been using Google and AI a lot to help me build it. I'm sure there are many issues with it, but hopefully, as I learn more, I'll be able to fix them all.
 
Back
Top