HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

GetKillingUnit() not working as it should?

06-28-2006, 05:18 AM#1
Alevice
Ok, probably not that, but there is something wrong. This is my trigger

Collapse JASS:
function SplitHero_SplitsExpired takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit Caster = GetHandleUnit(GetTriggeringTrigger(),"Caster")
    local unit Body = GetHandleUnit(Caster,"Body")
    local unit Mind = GetHandleUnit(Caster,"Mind")

    local integer BonusAgi = 0
    local integer BonusStr = 0
    local integer BonusInt = 0
    call BJDebugMsg("SplitHero_SplitsExpired initiated")

    set BonusAgi = GetHandleInt(GetTriggeringTrigger(),"BonusAgi")
    set BonusStr = GetHandleInt(GetTriggeringTrigger(),"BonusStr")
    set BonusInt = GetHandleInt(GetTriggeringTrigger(),"BonusInt")

    call SetHeroAgi(Caster,GetHeroAgi(Caster,false)+BonusAgi,true)
    call SetHeroStr(Caster,GetHeroStr(Caster,false)+BonusStr,true)
    call SetHeroInt(Caster,GetHeroInt(Caster,false)+BonusInt,true)
    call ShowUnitShow(Caster)

//    call DestroyTrigger(GetTriggeringTrigger())
endfunction

function SplitHero_SplitKillCond takes nothing returns boolean
    local unit Body = GetHandleUnit(GetTriggeringTrigger(),"Body")
    local unit Mind = GetHandleUnit(GetTriggeringTrigger(),"Mind")
    call BJDebugMsg("GetKillingUnit: "+GetUnitName(GetKillingUnit()))
    if ( ( GetKillingUnitBJ() == Body ) ) then
        call BJDebugMsg("Yay!")
        return true
    endif
    if ( ( GetKillingUnitBJ() == Mind ) ) then
        call BJDebugMsg("Yay!")
        return true
    endif
    return false
endfunction

function SplitHero_SplitKill takes nothing returns nothing
    local trigger t = GetHandleTrigger(GetTriggeringTrigger(),"SplitExpirationTimer")
    local unit Caster = GetHandleUnit(GetTriggeringTrigger(),"Caster")
    local unit Body = GetHandleUnit(Caster,"Body")
    local unit Mind = GetHandleUnit(Caster,"Mind")
    local integer BonusAgi = 0
    local integer BonusStr = 0
    local integer BonusInt = 0

    call BJDebugMsg("SplitHero_SplitKill initiated")
    
    set BonusAgi = GetHandleInt(t,"BonusAgi")
    set BonusStr = GetHandleInt(t,"BonusStr")
    set BonusInt = GetHandleInt(t,"BonusInt")

    if(GetKillingUnit() == Body) then
        set BonusAgi = BonusAgi + 1
        set BonusStr = BonusStr + 2
    elseif(GetKillingUnit() == Mind) then
        set BonusAgi = BonusAgi + 1
        set BonusInt = BonusInt + 2
    endif
    call BJDebugMsg("GetKillingUnit: "+GetUnitName(GetKillingUnit()))
    call BJDebugMsg("BonusStr: "+I2S(BonusStr))
    call BJDebugMsg("BonusAgi: "+I2S(BonusAgi))
    call BJDebugMsg("BonusInt: "+I2S(BonusInt))

    call SetHandleInt(t,"BonusAgi",BonusAgi)
    call SetHandleInt(t,"BonusStr",BonusStr)
    call SetHandleInt(t,"BonusInt",BonusInt)


//    call SetHandleHandle(GetTriggeringTrigger(),"Caster",null)
//    call SetHandleHandle(GetTriggeringTrigger(),"Body",null)
//    call SetHandleHandle(GetTriggeringTrigger(),"Mind",null)

//    call DestroyTrigger(GetTriggeringTrigger())
endfunction

function SplitHero_StoreData takes nothing returns nothing
    local trigger SplitKillTrigger = CreateTrigger()
    local trigger SplitExpirationTrigger = CreateTrigger()
    local trigger t2 = CreateTrigger()
    local timer SplitExpirationTimer = CreateTimer()
    
    local unit Caster = GetTriggerUnit()
    local player p = GetOwningPlayer(Caster)
    local location CasterLoc= GetUnitLoc(Caster)
    local unit Body = CreateUnitAtLoc(p,'o003',OffsetLocation(CasterLoc, 128.00, 0),GetUnitFacing(Caster))
    local unit Mind = CreateUnitAtLoc(p,'o002',OffsetLocation(CasterLoc, -128.00, 0),GetUnitFacing(Caster))
    
    call ShowUnitHide(Caster)

    call SetHandleHandle(SplitKillTrigger,"Caster",Caster)
    call SetHandleHandle(SplitExpirationTrigger,"Caster",Caster)
    call SetHandleHandle(Caster,"Body",Body)
    call SetHandleHandle(Caster,"Mind",Mind)
    call SetHandleHandle(SplitKillTrigger,"SplitExpirationTimer",SplitExpirationTrigger)

    call UnitApplyTimedLife(Body, 'Bspl', 30.00)
    call UnitApplyTimedLife(Mind, 'Bspl', 30.00)
//    call TimerStart(SplitExpirationTimer, 60.00 ,false, null)

    call TriggerRegisterAnyUnitEventBJ( SplitKillTrigger, EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddCondition( SplitKillTrigger, Condition( function SplitHero_SplitKillCond ) )
    call TriggerAddAction( SplitKillTrigger, function SplitHero_SplitKill )
    call BJDebugMsg("SplitKillTrigger registered")

    call TriggerRegisterTimerExpireEvent( SplitExpirationTrigger, SplitExpirationTimer )
    call TriggerAddAction( SplitExpirationTrigger, function SplitHero_SplitsExpired )
    call BJDebugMsg("SplitExpirationTrigger registered")
    call SetHandleHandle(GetTriggeringTrigger(),"Caster",null)
    call RemoveLocation(CasterLoc)

endfunction

//===========================================================================
function Trig_StoreSplitHeroData_Conditions takes nothing returns boolean
    return not(GetSpellAbilityId() != 'A003')
endfunction

function Trig_StoreSplitHeroData_Actions takes nothing returns nothing
    call SplitHero_StoreData()
endfunction

//===========================================================================
function InitTrig_StoreSplitHeroData takes nothing returns nothing
    set gg_trg_StoreSplitHeroData = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_StoreSplitHeroData, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition( gg_trg_StoreSplitHeroData, Condition( function Trig_StoreSplitHeroData_Conditions ) )
    call TriggerAddAction( gg_trg_StoreSplitHeroData, function Trig_StoreSplitHeroData_Actions )
endfunction


Leaks aside (I know there are a lot, my proirity is to get this working, then clean it), can anyone see whats wrong with my SplitHero_SplitKillCond function?

See, the trigger linked to it should run when the Killer (not the Killed) unit is either Body or Mind. Debug shows me that the Killer is indeed either of those, but for some reason, it returns at false, as call BJDebugMsg("Yay!") is never shown and the actions never fire, instead, the Yay! message and the actions run when the conditions are not met (ie, when the killer is neither body or mind)!

I know this used to work, but when i meade some crazy changes it suddendly stopped working, I ignore why.

Help?
06-28-2006, 12:03 PM#2
Rising_Dusk
Collapse JASS:
    call SetHandleHandle(SplitKillTrigger,"Caster",Caster)
    call SetHandleHandle(SplitExpirationTrigger,"Caster",Caster)
    call SetHandleHandle(Caster,"Body",Body)
    call SetHandleHandle(Caster,"Mind",Mind)
    call SetHandleHandle(SplitKillTrigger,"SplitExpirationTimer",SplitExpirationTrigger)

That's where you're attaching variables to different other variables.
Notice, you never attach Body or Mind to the trigger, but instead to the caster.

Now we look at your condition..
Collapse JASS:
    local unit Body = GetHandleUnit(GetTriggeringTrigger(),"Body")
    local unit Mind = GetHandleUnit(GetTriggeringTrigger(),"Mind")
See the problem? You're getting handles from the trigger that you never set, so it'll spaz out and not give correct values.
What you should do in your condition is this for how you set it up.

Collapse JASS:
function SplitHero_SplitKillCond takes nothing returns boolean
    local unit Caster = GetHandleUnit(GetTriggeringTrigger(), "Caster")
    local unit Body = GetHandleUnit(Caster,"Body")
    local unit Mind = GetHandleUnit(Caster,"Mind")
    call BJDebugMsg("GetKillingUnit: "+GetUnitName(GetKillingUnit()))
    if GetKillingUnit() == Body or GetKillingUnit() == Mind then
        call BJDebugMsg("Yay!")
        return true
    endif
    return false
endfunction

This should fix your problem.
06-28-2006, 02:15 PM#3
PipeDream
I think we should have two version of attached var systems. One of them would store type information and warn if you try to grab the wrong type, which would also catch uninitialized data. Problem is that handle extensions automatically typecast to handles so we have a generic set for all handles. So switching to "SetHandleUnit" or the table equivalent would only be appropriate for new maps. For testing you would use the safe versions, then for production releases of the map you could just paste over the fast versions .

Ex:
Collapse JASS:
function SetHandleUnit takes handle h, string name, function u returns nothing
    local string key = I2S(HtoI(h))
    call StoreInteger(GC(),key,"data_"+name,HtoI(u))
    call StoreString(GC(),key,"type_"+name,"Unit")
endfunction

function GetHandleUnit takes handle h, string name returns unit
    local string key = I2S(HtoI(h))
    local string stype = GetStoredString(GC(),key,"type_"+name)
    if(stype != "Unit") then
        if(stype != "") then
            call BJDebugMsg("### Error:  GetHandleUnit retrieved data of type "+stype)
        else
            call BJDebugMsg("### Warning:  GetHandleUnit retrieved uninitialized data")
        endif
    endif
    return GetStoredInteger(GC(),key,"data_"+name)
    return null
endfunction
06-28-2006, 04:40 PM#4
Alevice
Quote:
Originally Posted by Rising_Dusk

That's where you're attaching variables to different other variables.
Notice, you never attach Body or Mind to the trigger, but instead to the caster.

Now we look at your condition..
Collapse JASS:
    local unit Body = GetHandleUnit(GetTriggeringTrigger(),"Body")
    local unit Mind = GetHandleUnit(GetTriggeringTrigger(),"Mind")
See the problem? You're getting handles from the trigger that you never set, so it'll spaz out and not give correct values.

Gaaaah, that's the change I made I forgot to correct! T.T I knew it was gonna be something that silly, because as you can see, everywhere else the "extraction" is correct.

Thanks a lot for pointing it out. I tend to overlook such details quite often.

PipeDream: This is where it wold be nice to have a preprocessor, huh? ;P
06-29-2006, 05:24 AM#5
Alevice
Hi again, thansk to you I got it to work greatly most of it.

However, I don't know what is the relation, but now, whenever he casts the spell, the Caster unit won't respond to most of my commands. That is, if he is killing a group of units, he won't respond to any other command until he has killed them all. If i send him to patrol/escort an unit, he won't respond to anything at all until the unit dies.

If it helps, the spell is based off Avatar and the script is the following:

Collapse That Spellow Bastard:
constant function SplitsExpirationTime takes nothing returns real
    return 30.00
endfunction

constant function BodyType takes nothing returns integer
    return 'o003'
endfunction

constant function MindType takes nothing returns integer
    return 'o002'
endfunction

constant function SpawningFX takes nothing returns string
    return "Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl"
endfunction


function SplitHero_SplitsExpired takes nothing returns nothing
    local timer t = GetExpiredTimer()

    local trigger trg = GetHandleTrigger(t,"SplitKillTrigger")

    local unit Caster = GetHandleUnit(t,"Caster")
    local unit Body = GetHandleUnit(Caster,"Body")
    local unit Mind = GetHandleUnit(Caster,"Mind")
 
    local integer BonusAgi = GetHandleInt(t,"BonusAgi")
    local integer BonusStr = GetHandleInt(t,"BonusStr")
    local integer BonusInt = GetHandleInt(t,"BonusInt")

    local location CasterLoc= GetUnitLoc(Body)
    call DestroyEffect(AddSpecialEffectLoc("Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl",GetUnitLoc(Body)))
    call DestroyEffect(AddSpecialEffectLoc("Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl",GetUnitLoc(Mind)))
    call ShowUnitHide(Body)
    call ShowUnitHide(Mind)
    
    call SetUnitPositionLoc(Caster,CasterLoc)

    call SetHeroAgi(Caster,GetHeroAgi(Caster,false)+BonusAgi,true)
    call SetHeroStr(Caster,GetHeroStr(Caster,false)+BonusStr,true)
    call SetHeroInt(Caster,GetHeroInt(Caster,false)+BonusInt,true)

    call PauseUnit(Caster, false)
    call ShowUnitShow(Caster)
    call AddSpecialEffectTarget("Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl", Caster, "overhead")
    
    call SetHandleInt(t,"BonusAgi",0)
    call SetHandleInt(t,"BonusStr",0)
    call SetHandleInt(t,"BonusInt",0)

    call SetHandleHandle(t,"Caster",null)
    call SetHandleHandle(trg,"Caster",null)
    call SetHandleHandle(Caster,"Body",null)
    call SetHandleHandle(Caster,"Mind",null)

    call SetHandleHandle(trg,"SplitExpirationTimer",null)
    call SetHandleHandle(t,"SplitKillTrigger",null)

    set Caster = null
    set Body = null
    set Mind = null

    call RemoveLocation(CasterLoc)
    call DestroyTrigger(trg)
endfunction

function SplitHero_SplitKillCond takes nothing returns boolean
    local unit Caster = GetHandleUnit(GetTriggeringTrigger(),"Caster")
    local unit Body = GetHandleUnit(Caster,"Body")
    local unit Mind = GetHandleUnit(Caster,"Mind")
    local boolean flag = false

    if ( ( GetKillingUnit() == Body ) or ( GetKillingUnit() == Mind ) ) then
        set flag = true
    endif
    set Caster = null
    set Body = null
    set Mind = null

    return flag
endfunction

function SplitHero_SplitKill takes nothing returns nothing
    local timer t = GetHandleTimer(GetTriggeringTrigger(),"SplitExpirationTimer")
    local unit Caster = GetHandleUnit(GetTriggeringTrigger(),"Caster")
    local unit Body = GetHandleUnit(Caster,"Body")
    local unit Mind = GetHandleUnit(Caster,"Mind")
    local integer BonusAgi = 0
    local integer BonusStr = 0
    local integer BonusInt = 0

    call BJDebugMsg("SplitHero_SplitKill initiated")
    
    set BonusAgi = GetHandleInt(t,"BonusAgi")
    set BonusStr = GetHandleInt(t,"BonusStr")
    set BonusInt = GetHandleInt(t,"BonusInt")

    if(GetKillingUnit() == Body) then
        set BonusAgi = BonusAgi + 1
        set BonusStr = BonusStr + 2
    elseif(GetKillingUnit() == Mind) then
        set BonusAgi = BonusAgi + 1
        set BonusInt = BonusInt + 2
    endif

    call BJDebugMsg("GetKillingUnit: "+GetUnitName(GetKillingUnit()))
    call BJDebugMsg("BonusStr: "+I2S(BonusStr))
    call BJDebugMsg("BonusAgi: "+I2S(BonusAgi))
    call BJDebugMsg("BonusInt: "+I2S(BonusInt))

    call SetHandleInt(t,"BonusAgi",BonusAgi)
    call SetHandleInt(t,"BonusStr",BonusStr)
    call SetHandleInt(t,"BonusInt",BonusInt)

    set t = null
    set Caster = null
    set Body = null
    set Mind = null

endfunction

function SplitHero_StoreData takes nothing returns nothing

    local trigger SplitKillTrigger = CreateTrigger()
    local timer SplitExpirationTimer = CreateTimer()
    
    local unit Caster = GetTriggerUnit()
    local player p = GetOwningPlayer(Caster)
    local location CasterLoc= GetUnitLoc(Caster)
    local unit Body = CreateUnitAtLoc(p,BodyType(),OffsetLocation(CasterLoc, 128.00, 0),GetUnitFacing(Caster))
    local unit Mind = CreateUnitAtLoc(p,MindType(),OffsetLocation(CasterLoc, -128.00, 0),GetUnitFacing(Caster))

    call DestroyEffect(AddSpecialEffectTarget(SpawningFX(), Body, "overhead"))
    call DestroyEffect(AddSpecialEffectTarget(SpawningFX(), Mind, "overhead"))
    call PolledWait(0.25)
    call PauseUnit(Caster, true)
    call ShowUnitHide(Caster)

    if (p == GetLocalPlayer()) then
        call SelectUnit(Body, true)
        call SelectUnit(Mind, true)
    endif

    call SetHandleHandle(SplitKillTrigger,"Caster",Caster)
    call SetHandleHandle(SplitExpirationTimer,"Caster",Caster)
    call SetHandleHandle(Caster,"Body",Body)
    call SetHandleHandle(Caster,"Mind",Mind)
    call SetHandleHandle(SplitKillTrigger,"SplitExpirationTimer",SplitExpirationTimer)
    call SetHandleHandle(SplitExpirationTimer,"SplitKillTrigger",SplitKillTrigger)

    call UnitApplyTimedLife(Body, 'Bspl', SplitsExpirationTime() + 0.10)
    call UnitApplyTimedLife(Mind, 'Bspl', SplitsExpirationTime() + 0.10)
    call TimerStart(SplitExpirationTimer, SplitsExpirationTime() ,false, function SplitHero_SplitsExpired)

    call TriggerRegisterAnyUnitEventBJ( SplitKillTrigger, EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddCondition( SplitKillTrigger, Condition( function SplitHero_SplitKillCond ) )
    call TriggerAddAction( SplitKillTrigger, function SplitHero_SplitKill )
    call BJDebugMsg("SplitKillTrigger registered")

    call SetHandleHandle(GetTriggeringTrigger(),"Caster",null)
    call RemoveLocation(CasterLoc)

endfunction

//===========================================================================
function Trig_StoreSplitHeroData_Conditions takes nothing returns boolean
    return not(GetSpellAbilityId() != 'A003')
endfunction

function Trig_StoreSplitHeroData_Actions takes nothing returns nothing
    call SplitHero_StoreData()
endfunction

//===========================================================================
function InitTrig_StoreSplitHeroData takes nothing returns nothing
    set gg_trg_StoreSplitHeroData = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_StoreSplitHeroData, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition( gg_trg_StoreSplitHeroData, Condition( function Trig_StoreSplitHeroData_Conditions ) )
    call TriggerAddAction( gg_trg_StoreSplitHeroData, function Trig_StoreSplitHeroData_Actions )
endfunction

Possibly relevant functions are SplitHero_StoreData and SplitHero_SplitsExpired (if you have been wondering, yes, it is for the spell making session :P).

Thanks in advance
06-29-2006, 06:05 AM#6
Rising_Dusk
I cant really see why he would not respond to ANY commands at all.
Nothing in there prevents him from taking commands, just a few pauses which are rectified and a hide/unhide setup.
I'd really have to see the spell in action and know what exactly was going wrong and what exactly I should be looking for in the script.

If you submit the preliminary to the spell session, we can view it and then suggest improvements or vital bug fixes.
06-29-2006, 01:54 PM#7
Vexorian
Quote:
I think we should have two version of attached var systems. One of them would store type information and warn if you try to grab the wrong type, which would also catch uninitialized data. Problem is that handle extensions automatically typecast to handles so we have a generic set for all handles. So switching to "SetHandleUnit" or the table equivalent would only be appropriate for new maps. For testing you would use the safe versions, then for production releases of the map you could just paste over the fast versions .

That's overkill also 10 types would make you need a couple of 100 different get functions
06-29-2006, 03:18 PM#8
Alevice
Quote:
Originally Posted by Rising_Dusk
I cant really see why he would not respond to ANY commands at all.
Nothing in there prevents him from taking commands, just a few pauses which are rectified and a hide/unhide setup.
I'd really have to see the spell in action and know what exactly was going wrong and what exactly I should be looking for in the script.

If you submit the preliminary to the spell session, we can view it and then suggest improvements or vital bug fixes.


Err, I'd prefer not submit it yet, it is in a rather emabarrasing state as it is right now, which is why I will post it here meanwhile.
Attached Files
File type: w3xego.w3x (20.3 KB)
06-29-2006, 03:59 PM#9
PipeDream
Quote:
That's overkill also 10 types would make you need a couple of 100 different get function
I don't mean GetTimerUnit. I can't think of any times that would have saved me debug time. So same number of accessors along with n mutators instead of one. You're right, the automatic type promotion is still a huge help.
It seems to me that this is long overdue.
06-29-2006, 07:59 PM#10
Rising_Dusk
Well Alev, I tested it multiple times, and aside from the spells on the split units not being very clean or well chosen, it appears to work.

What did you mean the caster was being frozen before?
I didn't witness any caster freeze errors in testing it.
06-29-2006, 08:55 PM#11
Alevice
Quote:
Originally Posted by Rising_Dusk
Well Alev, I tested it multiple times, and aside from the spells on the split units not being very clean or well chosen,

Hence the "rather embarrasing" state. It was actually just a quick setup to test my spell, more balancing will come when I get the actual spell working right.

Quote:
Originally Posted by Rising_Dusk
What did you mean the caster was being frozen before?
I didn't witness any caster freeze errors in testing it.

The caster doesn't freeze at all. As you might have seen, there are a few peasants you can kill. When you start killing them as the split units, and during the middle of the fight the spell 'times out' and the caster reassembles, the caster will start attacking the peasants, ignoring any other issue I give to him, until he has killed them all. Then, any order assigned cannot be interrumpted at all, liek if you order him to move somewhere, you cannot order him anything else until he reaches his destination. If you order him to patrol a unit, he won't stop unless the patrolled unit dies.
06-29-2006, 09:59 PM#12
Rising_Dusk
I see..
That is very weird.

I most likely blame WC3 itself.
I don't think Storm, Earth, and Fire was meant to be used on already SEandF'd units, might weird the system out a bit.
Your code doesn't appear to do anything regarding ignoring orders at all, so this sort of a bug wouldn't be associated with that.

You might consider triggering the unit splits rather than using SEandF as the means.
It would likely work then.
06-29-2006, 10:12 PM#13
Alevice
Quote:
Originally Posted by Rising_Dusk
I see..
That is very weird.

I most likely blame WC3 itself.
I don't think Storm, Earth, and Fire was meant to be used on already SEandF'd units, might weird the system out a bit.
Your code doesn't appear to do anything regarding ignoring orders at all, so this sort of a bug wouldn't be associated with that.

You might consider triggering the unit splits rather than using SEandF as the means.
It would likely work then.

Thing is, the current state of the spell (the one used by the hero, not the split units, which I need to change too) doesn't use SEaF (my original and the one used currently by the splits did, but couldn't find a way to track the spawns, gah), but Avatar (randomly picked spell, because it didn't need a target, I might consider using something else), and the split units are created manually.

The error happens wether or not I use the SEaF spells on the split units. Gah.

In any case, I will clean it up units wise, and post it already as my entry.
06-30-2006, 12:04 AM#14
Rising_Dusk
I would use Berserk instead of Avatar.
It might work that way, try it.

Also, you could track the splits with dynamic triggers.
If a split unit casts a split, create X and Y unit, attach them to trigger, etc.
Might get complicated, but it would work.

'Tis a neat spell either way. :P
06-30-2006, 01:06 AM#15
Alevice
Quote:
Originally Posted by Rising_Dusk
I would use Berserk instead of Avatar.
It might work that way, try it.

True that. Will certainly try it.

Quote:
Originally Posted by Rising_Dusk
Also, you could track the splits with dynamic triggers.
If a split unit casts a split, create X and Y unit, attach them to trigger, etc.
Might get complicated, but it would work.

That's actually what I am doing with avatar :). What I meant is that when I was using SEaF, that was not possible (at least for me), since I could not truly figure out which units were spawned.

Quote:
Originally Posted by Rising_Dusk
'Tis a neat spell either way. :P

That's a compliment I didn't expect to see...

Either way, I must get backl to work on that

*POOF*