Doppelganger v1.5

This bundle is marked as approved. It works and satisfies the submission rules.
  • Like
Reactions: Kam and Pharaoh_
Spell Name: Doppelganger v1.5

This map was inpired by UnMi. He created the Mirror of Truth spell in the hall of fame, but for some reasons, I cant open his map, maybe patch 1.24 is not done yet at that time so I decided to make this.

Full Description:
Summons a mirror portal that reveals the inner fear of an enemy unit.
An enemy unit that goes near to the portal will be forced to sleep and create it's doppelganger.
The doppelganger deals 2 times damage, but takes 2 times more damage from enemy attacks, and will disappear after 20 seconds or when its hit points is zero.

|cffffcc00Level 1|r - Portal lasts 20 seconds
|cffffcc00Level 2|r - Portal lasts 35 seconds
|cffffcc00Level 3|r - Portal lasts 50 seconds
|cffffcc00Level 4|r - Portal lasts 65 seconds
|cffffcc00Level 5|r - Portal lasts 80 seconds

//Spell Name: Doppelganger v1.5
//Created by: Mckill2009

//===HOW TO USE:
//- Make a new trigger and convert to custom text via EDIT >>> CONVERT CUSTOM TEXT
//- The trigger name MUST be >>> Doppelganger (see the name below)
//- Copy ALL that is written here and overwrite the existing texts in the custom text
//- Copy the Dummy/custom abilities/buffs etc... to your object editor
//- Make sure you inputed the correct raw codes of the base spell/buffs/dummy etc...
//- If your raw code is different, you MUST CHANGE IT...
//- You can view the raw codes by pressing CTRL+D in the object editor
//- Examples of raw codes are 'A000', 'h000' etc... 

// DPL_Hash = hashtable
// DPL_DPL_Group = group
// DPL_Timer = timer
// DPL_Portal = unit
// DPL_Counter = integer

//===IMPORTANT NOTICE: Raw codes MUST BE CHANGED if your raw code has changed as indicated below
function DPL_SpellId takes nothing returns integer
    return 'A000' //the main spell

function DPL_SleepId takes nothing returns integer
    return 'A002' //the portal will cast this spell to enemy units

function DPL_ImageCasterId takes nothing returns integer
    return 'H001' //a dummy unit hero, this is the caster of the image

function DPL_PortalId takes nothing returns integer
    return 'h000' //raw code of the portal unit, it is recommended to use the Shimmering Portal

function DPL_ItemId takes nothing returns integer
    return 'I000' //this is an item which is given to the dummy hero

function DPL_GetDuration takes integer i returns real
    return 10 + i * 15. //the life of the portal

constant function DPL_TimerInterval takes nothing returns real
    return 0.1 //dont change

constant function DPL_AoE takes nothing returns real
    return 400. //the reach of the portal casting the sleep

//==========END OF CONFIGURABLES==========

function DPL_LoopAction takes nothing returns nothing
    local unit portal = GetEnumUnit() 
    local unit first
    local integer portalID = GetHandleId(portal) 
    local real maxduration = LoadReal(udg_DPL_Hash, portalID, 1)
    local real mindur = LoadReal(udg_DPL_Hash, portalID, 2)
    if maxduration > 0 then
        call SaveReal(udg_DPL_Hash, portalID, 1, maxduration - DPL_TimerInterval())
        call SaveReal(udg_DPL_Hash, portalID, 2, mindur + DPL_TimerInterval())
        if mindur >= 9 and (LoadInteger(udg_DPL_Hash, portalID, 3)==0) then
            //setting the saved integer to 1 in order for this not to run again
            //otherwise the add ability will always run
            call SaveInteger(udg_DPL_Hash, portalID, 3, 1) 
            call UnitAddAbility(portal, DPL_SleepId())    
        elseif mindur > 9 then //it takes 9 seconds for the portal to animate it's birth
            call GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(portal), GetUnitY(portal), DPL_AoE(), null)
                set first = FirstOfGroup(bj_lastCreatedGroup)
                exitwhen first==null
                if IsUnitEnemy(portal, GetOwningPlayer(first)) and not IsUnitType(first, UNIT_TYPE_STRUCTURE) and not IsUnitType(first, UNIT_TYPE_SLEEPING) and not IsUnitType(first, UNIT_TYPE_DEAD) then
                    call IssueTargetOrder(portal, "sleep", first) 
                call GroupRemoveUnit(bj_lastCreatedGroup, first)
        call KillUnit(portal)
        call FlushChildHashtable(udg_DPL_Hash, portalID)
        call GroupRemoveUnit(udg_DPL_Group, portal)
        set udg_DPL_Counter = udg_DPL_Counter - 1
        if udg_DPL_Counter == 0 then
            call PauseTimer(udg_DPL_Timer)
    set portal = null

function DPL_CastOn takes unit u, integer spellId, integer portalId returns nothing
    local real facing = GetUnitFacing(u) * bj_DEGTORAD
    local real x = GetUnitX(u)+100*Cos(facing)
    local real y = GetUnitY(u)+100*Sin(facing)
    local integer portalID
    local integer level = GetUnitAbilityLevel(u, spellId)
    local unit portal = CreateUnit(GetTriggerPlayer(), portalId, x, y, facing)
    set portalID = GetHandleId(portal)
    call SetUnitAnimation(portal, "birth")
    call SaveReal(udg_DPL_Hash, portalID, 1, DPL_GetDuration(level))
    call SaveReal(udg_DPL_Hash, portalID, 2, 0) //mindur
    call SaveInteger(udg_DPL_Hash, portalID, 3, 0) //this is used for checking only
    call GroupAddUnit(udg_DPL_Group, portal)
    set portal = null

function DPL_GroupLooper takes nothing returns nothing
    call ForGroup(udg_DPL_Group, function DPL_LoopAction)

function DPL_CastCond takes nothing returns boolean
    local unit u
    local unit imagecaster
    if GetSpellAbilityId()==DPL_SpellId() then 
        if udg_DPL_Counter == 0 then
            call TimerStart(udg_DPL_Timer, DPL_TimerInterval(), true, function DPL_GroupLooper)
        set udg_DPL_Counter = udg_DPL_Counter + 1
        call DPL_CastOn(GetTriggerUnit(), DPL_SpellId(), DPL_PortalId())        
    elseif GetSpellAbilityId()==DPL_SleepId() then 
        set u = GetSpellTargetUnit()
        set imagecaster = CreateUnit(GetTriggerPlayer(), DPL_ImageCasterId(), GetUnitX(u), GetUnitY(u), 0)
        call UnitApplyTimedLife(imagecaster, 'BTLF', 1.0) 
        call UnitUseItemTarget(imagecaster, UnitAddItemById(imagecaster, DPL_ItemId()), u)
        set u = null
        set imagecaster = null
    return false

function InitTrig_Doppelganger takes nothing returns nothing
    local trigger t = CreateTrigger() 
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, function DPL_CastCond)
    set t = null
    set udg_DPL_Hash = InitHashtable() //hashtable
    set bj_lastCreatedUnit = CreateUnit(Player(15), DPL_ImageCasterId(),0,0,0)
    call UnitAddAbility(bj_lastCreatedUnit, DPL_SleepId())
    call RemoveUnit(bj_lastCreatedUnit)        

- Code fully optimized as per Bribe's suggestion.

- Filter Units replaced by ForGroup loop
- Buff removed coz its useless
- Global configuration gets back to function

- Timer replaced by a global loops and group
- Function setup replaced by global variable setup
- Code reduced and more readable
- Descrition changed

- Converted to JASS
- Description changed
- Sleep ability added
- Proper anmation for portal (Birth and Death)

- Spell Optimized
- Triggers reduced from 4 to 3
- Dummies reduced from 3 to 2
- Portal animation has exact timing

- Leaks cleared
- Clearing hashtables optimized

illusion, image, mirror, blademaster, portal, dummy, epic, system, target, jass, vjass

Doppelganger v1.5 (Map)

21 Feb 2012 Bribe: Great configurables and easy to use/implement. Always nice to see mckill2009 resources because you always keep it readable as well. Approved 4/5 (Recommended).




21 Feb 2012
Bribe: Great configurables and easy to use/implement. Always nice to see mckill2009 resources because you always keep it readable as well.

Approved 4/5 (Recommended).
Level 37
Mar 6, 2006
I like this spell, the idea is somewhat original.

Remove the + 0.00
  • Unit - Create 1 Dummy (portal caster) for (Owner of DP_Caster) at DP_CasterLoc facing ((Facing of DP_Caster) + 0.00) degrees
There's no need for this:
  • Hashtable - Save Handle Of(Triggering unit) as (Key dummy) of (Key (Triggering unit)) in Hash
and change this to picked unit:
  • Set DP_Mirror = (Load (Key dummy) of (Key (Picked unit)) in Hash)
  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
    • If - Conditions
      • (DP_Grp is empty) Equal to True
    • Then - Actions
      • Trigger - Turn off (This trigger)
    • Else - Actions
should be right after this:
  • Unit Group - Remove DP_Mirror from DP_Grp
  • Unit - Create 1 Dummy (illusion caster) for (Owner of (Triggering unit)) at (Position of (Target unit of ability being cast)) facing (Position of (Target unit of ability being cast))
Level 29
Mar 10, 2009
thanks guys for your positive comments...and yes I think the image is dispellable coz its based on mirror image, about the portal, no coz its a dummy...

Yeah I've noticed the leak, just forgot to make a variable for that...Im gonna fix this in the next update...

btw like I mentioned above, this spell is inpired by UnMi's Mirror of Truth spell...
Level 38
Sep 26, 2009
First, nice screenshot! The spell looks really cool.

I have to be a buzz kill. Custom value of units--- replace that with a hashtable.

(Key (Picked unit)) involves three function calls GetHandleIdBJ(GetEnumUnit())) which can be heavily optimized by stoing (Key (Picked unit)) into an integer variable before re-referencing it.

Looks quite nice!
Level 9
Dec 3, 2010
Just a few things.

Firstly, in your DPL_CAST, you forgot to null your timer. You should.

function DPL_SCOPETHEM takes nothing returns boolean
    local unit u = GetFilterUnit()
    local boolean b = IsUnitEnemy(udg_DPL_Portal, GetOwningPlayer(u)) and IsUnitType(u, UNIT_TYPE_STRUCTURE)==false and GetWidgetLife(u) > 0.405 and GetUnitAbilityLevel(u, DPL_BUFFID())==0 and udg_DPL_Count<=2
    if b==true then
        set udg_DPL_Count = udg_DPL_Count + 1
        call IssueTargetOrder(udg_DPL_Portal, "sleep", u) 
    set u =null
    return false          

Why do you use a boolean? You can just directly check the conditions in the if statement. That's one less variable.

IsUnitType(u, UNIT_TYPE_STRUCTURE)==false // instead of doing this

not IsUnitType(u, UNIT_TYPE_STRUCTURE) // you can do this

Other than that, looks pretty good code-wise.