| 09-13-2008, 07:36 PM | #1 |
So, what does this do? Well, basically its a way of detecting when a unit dies, goes out of scope or returns to life. PurplePoot and I have closed all of the issues we found here, and it should now be bug free. We expect you guys will find plenty of bugs - please try and test this to oblivion. It adjusts for animated units, works fine with devour, units in transports dying, and reincarnation, resurrection, and normal cases. As far as we know, in those cases it is fine, but they'll probably be a few more special cases. Credits: - PurplePoot, for most of the testing and ideas for this. - Bobo_The_Kodo for the initial idea about undefend on death/removal. It should be noted that although I wrote this code, most of the actual credit for the non-obvious ideas in it should go to Poot, such as the checking for if the unit is summoned to see if it is animated or not. The code is in its rough and ready form for testing, rather than a fully polished release candidate. JASS:library RemovalDetection initializer Init // Notes: CANNOT have two defend based abilities on same unit, or fires twice. // ASSUMES that summoned units cannot be revived in any way. This ought to be true. // If not, replace IsAnimated with 'return false' and don't use animate dead, or something. // Devour ain't a problem, but they'll die and then decay, you just can't see them decaying. globals private constant integer SPELL_ID = 'A000' // must be defend based spell private constant integer ORDER_ID = 852056 // undefend endglobals private function AddAbility takes unit u returns nothing // have ALL defend based spells here if (GetUnitAbilityLevel(u, 'A000') + GetUnitAbilityLevel(u, 'Adef')) == 0 then // check needed for our abil coz of animate dead call UnitAddAbility(u, SPELL_ID) endif endfunction private function IsAnimated takes unit u returns boolean if IsUnitType(u, UNIT_TYPE_SUMMONED) then call BJDebugMsg("Animated") return true endif return GetUnitAbilityLevel(u, 'BUad') > 0 endfunction // DO NOT EDIT BELOW HERE globals private boolean array dead[401000] private boolean array animated[401000] private integer index endglobals private function H2I takes handle h returns integer return h return 0 endfunction private function GetIndex takes handle h returns integer return H2I(h) - 0x100000 endfunction private function Ordered takes nothing returns boolean if GetIssuedOrderId() == ORDER_ID then set index = GetIndex(GetTriggerUnit()) if IsUnitType(GetTriggerUnit(), UNIT_TYPE_DEAD) == true then if dead[index] then set dead[index] = false // it has just gone out of scope call BJDebugMsg("Out of scope") else if animated[index] then set animated[index] = false call BJDebugMsg("Out of scope from animated") else call BJDebugMsg("Died or removed (one)") set dead[index] = true // either dying, or first of remove from live calls endif endif else if dead[index] then call BJDebugMsg("Revived") if IsAnimated(GetTriggerUnit()) then set animated[index] = true endif set dead[index] = false // it has just been resurrected/reincarnated endif // the else is just a normal call endif endif return false endfunction private function Ex takes nothing returns boolean call AddAbility(GetFilterUnit()) return false endfunction private function Enters takes nothing returns boolean call AddAbility(GetTriggerUnit()) return false endfunction private function Init takes nothing returns nothing local group g = CreateGroup() local rect r = GetWorldBounds() local region world = CreateRegion() local trigger t = CreateTrigger() local integer i = 0 call RegionAddRect(world, r) call GroupEnumUnitsInRect(g, r, Condition(function Ex)) call DestroyGroup(g) set g = null call RemoveRect(r) set r = null call TriggerAddCondition(t, Condition(function Enters)) call TriggerRegisterEnterRegion(t, world, null) set t = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER) call TriggerAddCondition(t, Condition(function Ordered)) loop exitwhen i >= 16 call SetPlayerAbilityAvailable(Player(i), SPELL_ID, false) set i = i + 1 endloop endfunction endlibrary |
| 09-13-2008, 07:39 PM | #2 |
Some stuff we haven't got around to implementing or noting, or that isn't needed but is related:
EDIT: I have a really ghetto hack for detecting reincarnation, but it's pretty much an abomination at present. I'm trying to work out how to make it suck a bit less, if possible. EDIT2: Here's what I have for a full script so far. Doesn't support dynamic triggers, but everyone knows they suck anyways. Supports reincarnation detection. EDIT3: I should note that reincarnation with a 0 second duration won't fire the Reincarnation Start event, but all higher intervals will. JASS:library RemovalDetection initializer Init needs TimerUtils // Notes: CANNOT have two defend based abilities on same unit, or fires twice. // ASSUMES that summoned units cannot be revived in any way. This ought to be true. // If not, replace IsAnimated with 'return false' and don't use animate dead, or something. // Devour ain't a problem, but they'll die and then decay, you just can't see them decaying. //Config globals private constant integer SPELL_ID = 'A000' // must be defend based spell private constant integer ORDER_ID = 852056 // undefend private constant integer MAX_ARRAY_SIZE = 401000 //for the unit flag arrays endglobals private function AddAbility takes unit u returns nothing // have ALL defend based spells here if (GetUnitAbilityLevel(u, 'A000') + GetUnitAbilityLevel(u, 'Adef')) == 0 then call UnitAddAbility(u, SPELL_ID) endif endfunction private function IsAnimated takes unit u returns boolean if IsUnitType(u, UNIT_TYPE_SUMMONED) then // check needed due to animate dead sucking debug call BJDebugMsg("Animated") return true endif return GetUnitAbilityLevel(u, 'BUad') > 0 endfunction // DO NOT EDIT BELOW HERE globals //Unit flags private boolean array dead[MAX_ARRAY_SIZE] private boolean array animated[MAX_ARRAY_SIZE] private boolean array reincarnated[MAX_ARRAY_SIZE] //Event handling private trigger array reincS private trigger array reincF private trigger array rez private trigger array oos private integer reincSMax = 0 private integer reincFMax = 0 private integer rezMax = 0 private integer oosMax = 0 private integer cnt = 0 //Event responses private unit trg = null private boolean amd = false endglobals //"Event Responses" and event registration function TriggerRegisterReincarnationEvent takes trigger t, boolean finish returns nothing if finish then set reincF[reincFMax] = t set reincFMax = reincFMax + 1 else set reincS[reincSMax] = t set reincSMax = reincSMax + 1 endif endfunction function TriggerRegisterResurrectionEvent takes trigger t returns nothing set rez[rezMax] = t set rezMax = rezMax + 1 endfunction function TriggerRegisterOutOfScopeEvent takes trigger t returns nothing set oos[oosMax] = t set oosMax = oosMax + 1 endfunction public function GetTriggeringUnit takes nothing returns unit return trg endfunction public function GetAnimated takes nothing returns boolean return amd endfunction //System private function H2I takes handle h returns integer return h return 0 endfunction private function GetIndex takes handle h returns integer return H2I(h) - 0x100000 endfunction private struct Data integer i unit u static method create takes unit u, integer i returns Data local Data dat = Data.allocate() set dat.u = u set dat.i = i return dat endmethod endstruct private function Child takes nothing returns nothing local Data dat = GetTimerData(GetExpiredTimer()) if reincarnated[dat.i] then set trg = dat.u if GetUnitTypeId(trg) != 0 then set cnt = 0 loop exitwhen cnt >= reincSMax if TriggerEvaluate(reincS[cnt]) then call TriggerExecute(reincS[cnt]) endif set cnt = cnt + 1 endloop debug call BJDebugMsg("Reincarnating") else set reincarnated[dat.i] = false debug call BJDebugMsg("Removed") endif endif set dat.u = null call dat.destroy() call ReleaseTimer(GetExpiredTimer()) endfunction private function Ordered takes nothing returns boolean local integer index = GetIndex(GetTriggerUnit()) local timer t set trg = GetTriggerUnit() if GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_ORDER then if GetIssuedOrderId() == ORDER_ID then set amd = false if IsUnitType(trg,UNIT_TYPE_DEAD) then if dead[index] or animated[index] then if dead[index] then set dead[index] = false //it has just gone out of scope debug call BJDebugMsg("Out of scope") else set animated[index] = false debug call BJDebugMsg("Out of scope from animated") set amd = true endif set cnt = 0 loop exitwhen cnt >= oosMax if TriggerEvaluate(oos[cnt]) then call TriggerExecute(oos[cnt]) endif set cnt = cnt + 1 endloop else set dead[index] = true set reincarnated[index] = true //Set to false if the unit set t = NewTimer() //is registered to have died call SetTimerData(t,Data.create(trg,index)) call TimerStart(t,0,false,function Child) set t = null endif elseif dead[index] then if IsAnimated(trg) then set animated[index] = true set amd = true endif if reincarnated[index] then set reincarnated[index] = false set cnt = 0 loop exitwhen cnt >= reincFMax if TriggerEvaluate(reincF[cnt]) then call TriggerExecute(reincF[cnt]) endif set cnt = cnt + 1 endloop debug call BJDebugMsg("Reincarnated") elseif not IsUnitType(trg,UNIT_TYPE_HERO) then set cnt = 0 //hero revive already has an event loop exitwhen cnt >= rezMax if TriggerEvaluate(rez[cnt]) then call TriggerExecute(rez[cnt]) endif set cnt = cnt + 1 endloop debug call BJDebugMsg("Resurrected") endif set dead[index] = false //it has just been resurrected/reincarnated endif //the else is just a normal call endif else set reincarnated[index] = false //The order fires before the death event debug call BJDebugMsg("Died") //and thus lame workarounds are required endif return false endfunction private function Ex takes nothing returns boolean call AddAbility(GetFilterUnit()) return false endfunction private function Enters takes nothing returns boolean call AddAbility(GetTriggerUnit()) return false endfunction private function Init takes nothing returns nothing local group g = CreateGroup() local rect r = GetWorldBounds() local region world = CreateRegion() local trigger t = CreateTrigger() local integer i = 0 call RegionAddRect(world, r) call GroupEnumUnitsInRect(g, r, Condition(function Ex)) call DestroyGroup(g) set g = null call RemoveRect(r) set r = null call TriggerAddCondition(t, Condition(function Enters)) call TriggerRegisterEnterRegion(t, world, null) set t = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER) call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH) call TriggerAddCondition(t, Condition(function Ordered)) loop exitwhen i >= 16 call SetPlayerAbilityAvailable(Player(i), SPELL_ID, false) set i = i + 1 endloop endfunction endlibrary |
