| 07-21-2008, 01:45 AM | #1 | |
Unit Recycler & Uber simple damage detection
By moyack - 2009 Introduction: This system is the result of working with a damage detection system that actually can be safe (AKA avoid totally the DestroyTrigger() function), so one solution to this issue is to avoid as much as possible (hopefully completely) the destruction of units and therefore the EVENT_DAMAGED triggers related to them, and the only way to make it possible is by recycling the units. The current result is a system that recycle most type of units (summoned and buildings simply don't work for their harcoded properties). Why is convenient this system? because the game won't need to allocate memory creating units, instead, they'll be backed up for a later use, which can improve your map performance. This can be very useful in maps where the unit spawning is a very common task (like AoS, footies, etc). What's the issue with buildings and summoned units?? Buildings have limitations in moving them (they can be moved but their basement remains in the original place, which won't allow the construction of new units in those places). About summoned units, their dead is totally harcoded by the summoning abilities and can't be stopped by detecting the damage, therefore they can't be recycled. How it works? This library takes into account units that are being damaged, and detects if they're going to die (if the damage dealt to a unit is enough to kill them), if that situation happens, then it will be moved to the heaven, a place that you define in your map where the units are restored, cleaned and left ready for later use. Additionally this system offer a set of replacement functions which will help to this system to recycle units more efficiently. How to install & final comments
That's all??? not actually. By doing only this the library will be able to add damage detection to units and store the fallen units into the heaven. But it's up to you to develop the code to recycle the units. How?? using the functions provided (you can see them in the library comments). Core library:library UnitRecycler //****************************************************************************** //* //* Unit Recycler //* By moyack. 2009 //* //* ============================================================================ //* Credits to Litany, DioD, Captain Griffen, Troll Brain and other nice guys * //* for their suggestions, comments and ideas. * //* ============================================================================ //* //* This library allows to your map to reuse died units, which saves memory. //* It's very useful in AoS or footies games, where the unit spawning is a //* common task. //* //* How it works? //* ============= //* //* The script detects if a unit reach the dying point (defined by the MIN_LIFE //* constant), and if the damage can kill the unit, then it is sent to the dump //* for further recycling. For custom situations like summoning or when a unit //* enters to the map, you just have to use the functions provided by this //* library. //* //* Functions //* ========= //* //* in order to recycle a unit, you can use these function: //* //* => RecycleUnit(<unit variable>) returns unit //* ----------------------------------------- //* This function takes as argument a unit, and returns the unit recycled. //* if there's a unit of the same typeid in the dump, it will use this unit //* instead of the one from the function input, removing it immediately, //* otherwise this script will return the same unit. //* //* => CreateUnitEx(player, unitid, x, y, angle) returns unit //* ------------------------------------------------------ //* Like the native function but tries to recycle the unit if avaliable in the dump //* //* => KillUnitEx(<unit variable>) returns nothing //* ------------------------------------------- //* Like the native function but it recycles the unit before killing it. //* //* => RemoveUnitEx(<unit variable>) returns nothing //* --------------------------------------------- //* Like the native function but it recycles the unit. //* //* => IsUnitDead(<unit variable>) returns boolean //* ------------------------------------------- //* This function returns a boolean argument that indicates if the unit is dead or not. //* Remember that a unit is dead if it's in the Heaven group. //* //* => ReplaceDummy(<unit variable>) returns unit //* ------------------------------------------- //* This function returns a replacement unit without killing the output unit. //* //* => CreateDummy(player, unitid, x, y, angle) returns unit //* ------------------------------------------------------ //* Like the CreateUnit function but the returned unit won't be affected by the damage //* detection nor will be recycled automatically //* //* => TriggerRegisterAnyUnitRecycleEvent(trigger t) returns nothing //* ------------------------------------------------------------- //* All the trigger registered in this way will activate when a unit is about to be recycled //* (in other words, it triggers before it dies). You can use this functions to retrieve the //* the units involved in this event: //* //* # GetRecycledUnit() returns the units that is going to be recycled //* # GetRecycleDummyUnit() returns the dummy unit which will die instead of the recycled unit //* # GetRecycleAttacker() returns the unit that "kills" the recycled unit //* //* => IsUnitDummy(unit u) returns boolean //* ----------------------------------- //* Checks if a unit can be recycled or not. Dummy units are not recycled, they're used as //* placeholder of the unit about to die... //* //* => GetUnitsInHeaven() returns group //* -------------------------------- //* Returns the group which contains the units in the heaven. Useful to do checks and //* operations on them. //* //* //* For Damage detection, you just need to use these functions: //* //* => AddDamageCondition(<Boolexpr variable>) returns nothing //* ------------------------------------------------------- //* Just add a condition function which manage the damaged unit and the script //* will use it with all the the units in the DD.D group. //* //* => DoNonDetectableDamage(unit, widget, damage, boolean_attack, boolean_ranged, attacktype, damagetype, weapontype) returns boolean //* ------------------------------------------------------------------------------------------------------------------------------- //* Like the UnitDamageTarget function, but it can be used inside condition functions. //* How to know if you need to use it? if you use UnitDamageTarget() inside a DD function and //* it freezes the game until it kills the attacked unit(s), then you have to replace that //* function by this custom one. //******************************************************************************** //* CONFIGURATION PART globals private constant real MIN_LIFE = 0.405 // the experimental death value that will be used to activate // the fake death of units. units that reach this value or less // won't die and they will be recycled. private constant boolean AUTO_LOC = false// If it's set to true, it will place automatically the heaven in one // non visible corner of the map, else, it will use the HEAVEN_POS // as a heaven location private constant player DUMMY_PLAYER = Player(15) // sets the player owner of the unit's heaven private constant real MANA_FACTOR = 0.5 // sets the initial mana amount (as percentage of maximum mana) // to recycled units when they are just placed in the game. // This constant is used only by CreateUnitEx function. private location HEAVEN_POS = Location(2700., -4700.) // Indicates the heaven's location. endglobals //* END CONFIGURATION PART globals private group Heaven // where worthy units go when they die... endglobals // this is a key functions part just to make homogeneus the unit management, please don't edit this unless // you know what are you doing, ok?? private keyword Kill private function PrepareUnit takes unit u returns nothing call SelectUnit(u, false) call UnitRemoveBuffs(u, true, true) call UnitResetCooldown(u) call SetUnitInvulnerable(u, true) call SetWidgetLife(u, GetUnitState(u, UNIT_STATE_MAX_LIFE)) call PauseUnit(u, true) call GroupAddUnit(Heaven, u) endfunction private function MoveUnit takes unit u returns nothing call SetUnitOwner(u, DUMMY_PLAYER, true) call SetUnitX(u, GetLocationX(HEAVEN_POS)) call SetUnitY(u, GetLocationY(HEAVEN_POS)) endfunction private function PlaceDummy takes unit d, unit u returns nothing call GroupAddUnit(Kill.corpse, d) call SetUnitUseFood(d, false) call SetUnitState(d, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MANA)) call SetUnitFlyHeight(d, GetUnitFlyHeight(u), 0.) //call SetCSData(d, GetCSData(u)) //used to pass attached data via CSData to the corpse... call SetUnitPathing(d, false) call SetUnitX(d, GetUnitX(u)) call SetUnitY(d, GetUnitY(u)) endfunction // end key functions... private struct Trigger // struct to manage the EventRecycleUnit private static integer i = 0 static unit R = null // recycled unit static unit D = null // dummy unit static unit A = null // attacker unit trigger t static method AddTrigger takes trigger t returns nothing local Trigger T = Trigger.allocate() set T.t = t set Trigger.i = integer(T) endmethod static method evaluate takes unit r, unit d, unit a returns nothing local integer i = 1 local Trigger T set Trigger.R = r set Trigger.D = d set Trigger.A = a loop exitwhen i > Trigger.i set T =Trigger(i) if T.t != null and TriggerEvaluate(T.t) then call TriggerExecute(T.t) endif set i = i + 1 endloop endmethod endstruct private struct Kill // struct used to manage the killed units... static group corpse // group of units that won't be touched by the damage detection (corpses and dummy units for instance) static method Do takes unit u, unit killer returns nothing local unit d = CreateUnit(GetOwningPlayer(u), GetUnitTypeId(u), GetUnitX(u), GetUnitY(u), GetUnitFacing(u)) call SetUnitInvulnerable(u, true) call PlaceDummy(d, u) call SetWidgetLife(d, 1.) call Trigger.evaluate(u, d, killer) call UnitDamageTarget(killer, d, 2., true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS) call PrepareUnit(u) call MoveUnit(u) set d = null endmethod private static method ManageSummoned takes nothing returns nothing if IsUnitInGroup(GetSummonedUnit(), Kill.corpse) then // this part will run when a corpse is resurrected, so this units are suitable to // be controlled by the damage detection. call GroupRemoveUnit(Kill.corpse, GetSummonedUnit()) call SetUnitPathing(GetSummonedUnit(), true) endif endmethod private static method onInit takes nothing returns nothing local trigger t = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SUMMON) call TriggerAddAction(t, function Kill.ManageSummoned) set Kill.corpse = CreateGroup() set t = null endmethod endstruct private struct DD // damage detection struct, because damage detection can be so easy... static group D // group of unit that will have damage detection static trigger T // Add the attacked unit to the damage detection if the unit is not in the unit group private static method AddUnit takes unit d returns nothing if not IsUnitInGroup(d, DD.D) and not IsUnitInGroup(d, Kill.corpse) then debug call DisplayTimedTextToForce(bj_FORCE_ALL_PLAYERS,2,"Added " + GetUnitName(d) + " to the DD") call TriggerRegisterUnitEvent(DD.T, d, EVENT_UNIT_DAMAGED) call GroupAddUnit(DD.D, d) endif endmethod private static method Attacked takes nothing returns nothing call DD.AddUnit(GetTriggerUnit()) endmethod private static method Spelled takes nothing returns nothing if GetSpellTargetUnit() != null then call DD.AddUnit(GetSpellTargetUnit()) endif endmethod static method AntiLoop takes nothing returns nothing call EnableTrigger(DD.T) endmethod private static method onInit takes nothing returns nothing set DD.T = CreateTrigger() set DD.D = CreateGroup() call TriggerRegisterAnyUnitEventBJ(DD.T, EVENT_PLAYER_UNIT_ATTACKED) call TriggerAddAction(DD.T, function DD.Attacked) set DD.T = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(DD.T, EVENT_PLAYER_UNIT_SPELL_EFFECT) call TriggerAddAction(DD.T, function DD.Spelled) set DD.T = CreateTrigger() endmethod endstruct // you see, it's not bigger than this :) private struct UR // unit recycling struct static group Temp = CreateGroup() static unit U = null static method FromDump takes nothing returns boolean if IsUnitInGroup(GetFilterUnit(), Heaven) and IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false then set UR.U = GetFilterUnit() endif return false endmethod static method UseRecycled takes unit u returns unit local real x = GetUnitX(u) local real y = GetUnitY(u) local real f = GetUnitFacing(u) local real m = GetUnitState(u, UNIT_STATE_MANA) local player p = GetOwningPlayer(u) set UR.U = null call GroupEnumUnitsOfTypeCounted(UR.Temp, UnitId2String(GetUnitTypeId(u)), Condition(function UR.FromDump), 1) if UR.U != null then debug call DisplayTimedTextToForce(bj_FORCE_ALL_PLAYERS,2, GetUnitName(UR.U) + " is being reused...") call SetUnitInvulnerable(UR.U, false) call GroupRemoveUnit(Heaven, UR.U) call PauseUnit(UR.U, true) call SetUnitPosition(UR.U, x, y) call SetUnitFacing(UR.U, f) call SetUnitOwner(UR.U, p, true) call SetUnitState(UR.U, UNIT_STATE_MANA, m) call SetUnitPathing(UR.U, true) call PauseUnit(UR.U, false) call RemoveUnit(u) set u = null return UR.U else return u endif endmethod private static method onRecycle takes nothing returns boolean local unit u = GetTriggerUnit() if IsUnitType(u, UNIT_TYPE_HERO) == false and IsUnitType(u, UNIT_TYPE_STRUCTURE) == false and IsUnitType(u, UNIT_TYPE_SUMMONED) == false and GetWidgetLife(u) - GetEventDamage() <= MIN_LIFE then call Kill.Do(u, GetEventDamageSource()) // place a dummy to replace the unit // and returns the corpse debug call DisplayTimedTextFromPlayer(Player(15),0,0,2, GetUnitName(u) + " has been sent to the dump...") endif set u = null return false endmethod private static method onInit takes nothing returns nothing local rect r= GetWorldBounds() set Heaven = CreateGroup() call TriggerAddCondition(DD.T, Condition(function UR.onRecycle)) if AUTO_LOC then call MoveLocation(HEAVEN_POS, GetRectMinX(r), GetRectMinY(r)) endif call RemoveRect(r) set r=null endmethod endstruct //*==================== //* User functions... * //*==================== function RecycleUnit takes unit u returns unit return UR.UseRecycled(u) endfunction function CreateUnitEx takes player p, integer id, real x, real y, real f returns unit set UR.U = null call GroupEnumUnitsOfTypeCounted(UR.Temp, UnitId2String(id), Condition(function UR.FromDump), 1) if UR.U != null then debug call DisplayTimedTextToForce(bj_FORCE_ALL_PLAYERS,2, GetUnitName(UR.U) + " is being reused...") call SetUnitInvulnerable(UR.U, false) call GroupRemoveUnit(Heaven, UR.U) call PauseUnit(UR.U, true) call SetUnitPosition(UR.U, x, y) call SetUnitFacing(UR.U, f) call SetUnitOwner(UR.U, p, true) call SetUnitState(UR.U, UNIT_STATE_MANA, GetUnitState(UR.U, UNIT_STATE_MAX_MANA) * MANA_FACTOR) call SetUnitPathing(UR.U, true) call PauseUnit(UR.U, false) return UR.U endif return CreateUnit(p, id, x, y, f) endfunction function KillUnitEx takes unit u returns nothing local unit d if IsUnitType(u, UNIT_TYPE_HERO) == false and IsUnitType(u, UNIT_TYPE_STRUCTURE) == false then set d = CreateUnit(GetOwningPlayer(u), GetUnitTypeId(u), GetUnitX(u), GetUnitY(u), GetUnitFacing(u)) call Trigger.evaluate(u, d, null) call PrepareUnit(u) call PlaceDummy(d, u) call KillUnit(d) call MoveUnit(u) endif set d = null endfunction function RemoveUnitEx takes unit u returns nothing if IsUnitType(u, UNIT_TYPE_HERO) == false and IsUnitType(u, UNIT_TYPE_STRUCTURE) == false then call Trigger.evaluate(u, null, null) call PrepareUnit(u) call MoveUnit(u) endif endfunction function IsUnitDead takes unit u returns boolean return IsUnitInGroup(u, Heaven) endfunction function ReplaceDummy takes unit u returns unit local unit d if IsUnitType(u, UNIT_TYPE_HERO) == false and IsUnitType(u, UNIT_TYPE_STRUCTURE) == false then set d = CreateUnit(GetOwningPlayer(u), GetUnitTypeId(u), GetUnitX(u), GetUnitY(u), GetUnitFacing(u)) call PlaceDummy(d, u) call SetWidgetLife(d, GetWidgetLife(u)) call PrepareUnit(u) call MoveUnit(u) call SetUnitPathing(d, true) else set d = null endif return d endfunction function CreateDummy takes player p, integer id, real x, real y, real f returns unit local unit u = CreateUnit(p, id, x, y, f) call GroupAddUnit(Kill.corpse, u) return u endfunction // TriggerRecyclefunctions. this trigger happens before the triggered unit is going to die... function TriggerRegisterAnyUnitRecycleEvent takes trigger t returns nothing call Trigger.AddTrigger(t) endfunction constant function GetRecycledUnit takes nothing returns unit // returns the recycled unit... return Trigger.R endfunction constant function GetRecycleDummyUnit takes nothing returns unit // returns the dummy unit... return Trigger.D endfunction constant function GetRecycleAttacker takes nothing returns unit // returns the attacker who "kills" the recycled unit... return Trigger.A endfunction constant function IsUnitDummy takes unit u returns boolean // Checks if a unit can be recycled or not. Dummy units are not recycled, they're used as placeholder... return IsUnitInGroup(u, Kill.corpse) endfunction constant function GetUnitsInHeaven takes nothing returns group // Checks if a unit can be recycled or not. Dummy units are not recycled, they're used as placeholder... return Heaven endfunction // Damage detection system functions... function AddDamageCondition takes boolexpr b returns nothing call TriggerAddCondition(DD.T, b) endfunction function DoNonDetectableDamage takes unit u, widget t, real damage, boolean attack, boolean ranged, attacktype AT, damagetype DT, weapontype WT returns boolean local boolean b call DisableTrigger(DD.T) set b = UnitDamageTarget(u, t, damage, attack, ranged, AT, DT, WT) call EnableTrigger(DD.T) return b endfunction endlibrary
Please download the test map for further testing. (last updated: 05/04/2009) |
| 07-21-2008, 06:10 PM | #2 |
Damage Detection only code: DD without unit recycling://****************************************************************************** //* //* Damage Detection script //* By moyack. 2009 //* //* ============================================================================ //* Credits to Litany, DioD, Captain Griffen, Troll Brain and other nice guys * //* for their suggestions, comments and ideas. * //* ============================================================================ //* //* For Damage detection, you just need to use these functions: //* //* => AddDamageCondition(<Boolexpr variable>) returns nothing //* ------------------------------------------------------- //* Just add a condition function which manage the damaged unit and the script //* will use it with all the the units in the DD.D group. //* //* => CreateDummy( player, 'Unitid', x, y, facing_Angle ) returns unit //* ---------------------------------------------------------------- //* Like the CreateUnit function, but all the units created with this function //* won't trigger the this Damage Detection script. //* //* => DoNonDetectableDamage(unit, widget, damage, boolean_attack, boolean_ranged, attacktype, damagetype, weapontype) returns boolean //* ------------------------------------------------------------------------------------------------------------------------------- //* Like the UnitDamageTarget function, but it can be used inside condition functions. //* How to know if you need to use it? if you use UnitDamageTarget() inside a DD function and //* it freezes the game until it kills the attacked unit(s), then you have to replace that //* function by this custom one. //******************************************************************************** library UnitDamage struct DD // damage detection struct static group D // group of units that will have damage detection static group Dummies // group of units which will not be detected by the system static trigger T // Add the attacked unit to the damage detection if the unit is not in the pack static method AddUnit takes unit d returns nothing if not IsUnitInGroup(d, thistype.D) and not IsUnitInGroup(d, thistype.Dummies) then debug call DisplayTimedTextFromPlayer(GetLocalPlayer(), 0,0,2,"Added " + GetUnitName(d) + " to the DD") call TriggerRegisterUnitEvent(thistype.T, d, EVENT_UNIT_DAMAGED) call GroupAddUnit(thistype.D, d) endif set d = null endmethod private static method Attacked takes nothing returns nothing call thistype.AddUnit(GetTriggerUnit()) endmethod private static method Spelled takes nothing returns nothing if GetSpellTargetUnit() != null then call thistype.AddUnit(GetSpellTargetUnit()) endif endmethod private static method onInit takes nothing returns nothing set thistype.T = CreateTrigger() set thistype.D = CreateGroup() set thistype.Dummies = CreateGroup() call TriggerRegisterAnyUnitEventBJ(thistype.T, EVENT_PLAYER_UNIT_ATTACKED) call TriggerAddAction(thistype.T, function thistype.Attacked) set thistype.T = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(thistype.T, EVENT_PLAYER_UNIT_SPELL_EFFECT) call TriggerAddAction(thistype.T, function thistype.Spelled) set thistype.T = CreateTrigger() endmethod endstruct // Damage detection system functions... function AddDamageCondition takes code func returns nothing call TriggerAddCondition(DD.T, Condition(func)) endfunction function CreateDummy takes player p, integer id, real x, real y, real f returns unit local unit u = CreateUnit(p, id, x, y, f) call GroupAddUnit(DD.Dummies, u) return u endfunction function DoSafeDamage takes unit u, widget t, real damage, boolean attack, boolean ranged, attacktype AT, damagetype DT, weapontype WT returns boolean local boolean b call DisableTrigger(DD.T) set b = UnitDamageTarget(u, t, damage, attack, ranged, AT, DT, WT) call EnableTrigger(DD.T) return b endfunction function AddUnittoDD takes unit u returns nothing call DD.AddUnit(u) endfunction endlibrary DD examples with this script Triggered Critical Strike:// Example of DoNonDetectableDamage function. scope TriggeredCriticalStrike initializer init globals private constant integer SpellID = 'A000' private constant real chance = 0.8 // 80% of chance to do the effect private constant real factor = 3. // multiplier damage factor private constant attacktype AT = ATTACK_TYPE_PIERCE // so you can set the kind of attack dealt private constant damagetype DT = DAMAGE_TYPE_NORMAL // so you can set the kind if damage dealt endglobals private function DoEffect takes nothing returns boolean local real d = GetEventDamage()// Damage dealt normally local unit u = GetTriggerUnit()// unit that receives the damage local real l = GetWidgetLife(u)// unit life local unit c = GetEventDamageSource()// unit that deals the damage local texttag t = CreateTextTag() if GetRandomReal(0,1) < chance and GetUnitAbilityLevel(c, SpellID) > 0 and d > 0 then // USE THIS FUNCTION INSTEAD OF UnitDamageTarget, otherwise you'll have an infinite loop. call DoNonDetectableDamage(c, u, d * (factor - 1), true, false, AT, DT, WEAPON_TYPE_WHOKNOWS) // the remaining damage will be done after the end of this code... // Eyecandy part :) call SetTextTagText(t, I2S(R2I(d * factor))+"!", 0.024) call SetTextTagPos(t, GetUnitX(u), GetUnitY(u), 0.00) call SetTextTagColor(t, 255, 0, 0, 255) call SetTextTagVelocity(t, 0, 0.04) call SetTextTagVisibility(t, true) call SetTextTagFadepoint(t, 2) call SetTextTagLifespan(t, 5) call SetTextTagPermanent(t, false) else call DestroyTextTag(t) endif set t = null set u = null set c = null return false endfunction private function init takes nothing returns nothing call AddDamageCondition(Condition(function DoEffect)) endfunction endscope Triggered Evasion:// requires TimerUtils scope TriggeredEvasion initializer init globals private constant integer SpellID = 'A001' private constant real chance = 0.8 // 80% of chance to do the effect private constant real absorb = 1. // 100% damage absorbtion = total evasion endglobals private struct data real l // the life the unit should have unit u static method create takes real l, unit u returns data local data d = data.allocate() set d.l = l set d.u = u return d endmethod endstruct private function Heal takes nothing returns nothing local data d = data(GetTimerData(GetExpiredTimer())) call SetWidgetLife(d.u, d.l) call d.destroy() call ReleaseTimer(GetExpiredTimer()) endfunction private function DoEffect takes nothing returns boolean local real d = GetEventDamage()// Damage dealt normally local unit u = GetTriggerUnit()// unit that receives the damage local real l = GetWidgetLife(u)// unit life local unit c = GetEventDamageSource()// unit that deals the damage local texttag t = CreateTextTag() local timer tm if GetRandomReal(0,1) < chance and GetUnitAbilityLevel(u, SpellID) > 0 and d > 0 then if l-d < 0.405 then call SetWidgetLife(u, d + 1) endif // Eyecandy part :) call SetTextTagText(t, "LOL!", 0.024) //Sorry, I did it because I need to diferentiate respect the standard MISS! call SetTextTagPos(t, GetUnitX(c), GetUnitY(c), 0.00) call SetTextTagColor(t, 255, 0, 0, 255) call SetTextTagVelocity(t, 0, 0.04) call SetTextTagVisibility(t, true) call SetTextTagFadepoint(t, 2) call SetTextTagLifespan(t, 5) call SetTextTagPermanent(t, false) set tm = NewTimer() call SetTimerData(tm, integer(data.create(l - d * (1. - absorb), u))) call TimerStart(tm, 0., false, function Heal) else call DestroyTextTag(t) endif set t = null set u = null set c = null set tm = null return false endfunction private function init takes nothing returns nothing call AddDamageCondition(Condition(function DoEffect)) endfunction endscope |
| 07-21-2008, 11:46 PM | #3 |
So it needs orbs? |
| 07-22-2008, 12:20 AM | #4 |
No, it doesn't need any orbs if you want, but if you want to use them in your triggers, then go ahead!! Edit: edited the comments in the script due to a typo. |
| 08-05-2008, 11:21 PM | #5 |
I was looking at this, and it really falters because of the use of dynamic triggers. I would much rather leak the event than deal with all of the long-since proven potential issues associated with destroying triggers at runtime. The only way to not destroy triggers as you have it is to use a single trigger and leak the event, but then you have really accomplished nothing other than porting out a small fraction of any damage detection/rendering system already in the resource section. Also, O(n) searches are unacceptable in the database. You're going to have to approach them in a different way, potentially with cache. |
| 08-08-2008, 07:12 PM | #6 |
I think I have a solution... I'm working on this so be patient please.... |
| 08-09-2008, 01:55 AM | #7 |
Ok, This is the code thus far: Unit recycler (not fully working)://****************************************************************************** //* //* Unit Recycler //* By moyack. 2008 //* //* Requires TimerUnitls & Fake CSSafety made by Vexorian. //* [url]http://www.wc3campaigns.net/showthread.php?t=101322[/url] //* //* //* This library allows to your map to reuse died units, which saves memory. //* It's very useful in AoS or footies games, where the unit spawning is a //* common task. //* //* How it works? //* ============= //* //* The script detects when a unit dies, if it has corpse, it left the corpse //* for the decayBoneTime minus a time set by the variable DECAY_DIFF. //* When this time is over, the script moves the corpes to a secret place and //* revive the unit, storing it into a dump for a further reuse. //* //* Functions //* ========= //* //* in order to recycle a unit, you can use these function: //* => RecycleUnit(<unit variable>) //* This function takes as argument a unit, and returns the a unit. If there //* //* => CreateUnitEx(player, unitid, x, y, angle) //* Like the native function but tries to recycle the unit //******************************************************************************** library UnitRecycler requires CSSafety globals private constant real BONE_DECAY_TIME = 88. // Set the duration of corpse presence in the game. // Please take this value from the game constant "BoneDecayTime" private constant real DECAY_TIME = 2. // Set the duration of flesh on dead units. // Please take this value from the game constant "DecayTime" private constant real DECAY_DIFF = 84. // the time before WC3 will remove the corpse, so the // Unit Recycler script can catch the corpse before being // automatically removed by the game. private constant integer REVIVE_ABIL = 'APrl' // I use the "Lesser Rune Resurrection" ability to // recycle the units. I suggest not to change it. private constant string REVIVE_ORDER = "resurrection" // The resurrection order private constant integer DUMMY_REVIVER = 'hmpr' // dummy unit rawcode. private constant location RECT_POS = Location(-3000., -3200.) // Indicates the position of the // RECT's center. private constant real RECT_SIDE = 100. // Set the size of the rect where the units will be recycled private constant player DUMMY_PLAYER = Player(15) // sets the player owner of the unit's dump private constant real DT = 0.1 // a value that it's not necessary to modify, but meh!!! endglobals private struct Data unit u static method Set takes unit u returns Data local Data U = Data.allocate() set U.u = u return U endmethod endstruct private struct DB private static gamecache gc static group test private static method SetFalse takes nothing returns nothing if not HaveStoredBoolean(DB.gc, "DecayBone", I2S(GetUnitTypeId(GetTriggerUnit()))) then call StoreBoolean(DB.gc, "DecayBone", I2S(GetUnitTypeId(GetTriggerUnit())), false) endif endmethod private static method SetTrue takes nothing returns nothing call StoreBoolean(DB.gc, "DecayBone", I2S(GetUnitTypeId(GetTriggerUnit())), true) endmethod static method UnitDoDecayBones takes unit u returns boolean return GetStoredBoolean(DB.gc, "DecayBone", I2S(GetUnitTypeId(GetTriggerUnit()))) endmethod private static method DoTestEx takes nothing returns nothing local Data U = GetTimerData(GetExpiredTimer()) call RemoveUnit(U.u) call U.destroy() call ReleaseTimer(GetExpiredTimer()) endmethod private static method DoTest takes nothing returns nothing local Data U local timer t if not HaveStoredBoolean(DB.gc, "DecayBone", I2S(GetUnitTypeId(GetTriggerUnit()))) then set U = Data.Set(CreateUnit(DUMMY_PLAYER, GetUnitTypeId(GetTriggerUnit()), GetLocationX(RECT_POS), GetLocationY(RECT_POS), 0.)) set t = NewTimer() call BJDebugMsg(GetUnitName(U.u) + " unittype is being tested...") //call ShowUnit(U.u, false) call GroupAddUnit(DB.test, U.u) call KillUnit(U.u) call SetTimerData(t, integer(U)) call TimerStart(t, DECAY_TIME * 1.1, false, function DB.DoTestEx) endif set t = null endmethod static method onInit takes nothing returns nothing local trigger t = CreateTrigger() set DB.gc = InitGameCache("DecayBoneData.w3v") set DB.test = CreateGroup() call TriggerRegisterEnterRectSimple(t, GetWorldBounds()) call TriggerAddAction(t, function DB.DoTest) set t = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DECAY) call TriggerAddAction(t, function DB.SetTrue) set t = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH) call TriggerAddAction(t, function DB.SetFalse) set t = null endmethod endstruct private struct UR private static group Dump // the group which stores the unit that will be used for recycling private static rect Sumon // the place where the units will be revived for later use private static method FromDump takes nothing returns boolean return IsUnitInGroup(GetFilterUnit(), UR.Dump) and IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false endmethod static method UseRecycled takes unit u returns unit local unit t local real x = GetUnitX(u) local real y = GetUnitY(u) local real f = GetUnitFacing(u) local real m = GetUnitState(u, UNIT_STATE_MANA) local player p = GetOwningPlayer(u) local group g = NewGroup() call GroupEnumUnitsOfType(g, GetUnitName(u), Condition(function UR.FromDump)) set t = FirstOfGroup(g) call ReleaseGroup(g) if t != null then call BJDebugMsg(GetUnitName(t) + " is being reused...") call GroupRemoveUnit(UR.Dump, t) call RemoveUnit(u) call PauseUnit(t, true) call SetUnitPosition(t, x, y) call SetUnitFacing(t, f) call SetUnitOwner(t, p, true) call SetUnitState(t, UNIT_STATE_MANA, m) call SetUnitPathing(t, true) call PauseUnit(t, false) set u = null return t else return u endif endmethod private static method ApplyEx takes nothing returns nothing local Data U = GetTimerData(GetExpiredTimer()) call RemoveUnit(U.u) call U.destroy() call ReleaseTimer(GetExpiredTimer()) endmethod private static method Apply takes nothing returns nothing local Data U = GetTimerData(GetExpiredTimer()) local unit c = CreateUnit(DUMMY_PLAYER, DUMMY_REVIVER, GetRectCenterX(UR.Sumon), GetRectCenterY(UR.Sumon), 0.) call UnitAddAbility(c, REVIVE_ABIL) call UnitAddAbility(c, 'Aloc') call ShowUnit(c, false) call SetUnitOwner(U.u, DUMMY_PLAYER, true) call SetUnitX(U.u, GetUnitX(c)) call SetUnitY(U.u, GetUnitY(c)) if IssueImmediateOrder(c, REVIVE_ORDER) then call SetUnitPathing(U.u, false) call SetUnitX(U.u, GetLocationX(RECT_POS)) call SetUnitY(U.u, GetLocationY(RECT_POS)) call GroupAddUnit(UR.Dump, U.u) call BJDebugMsg(GetUnitName(U.u) + " is ready for reuse...") endif set U.u = c set c = null call PauseTimer(GetExpiredTimer()) call TimerStart(GetExpiredTimer(), 1., false, function UR.ApplyEx) endmethod private static method onRecycle takes nothing returns nothing local Data U = Data.Set(GetTriggerUnit()) local timer t local real w if IsUnitType(U.u, UNIT_TYPE_HERO) == false then set t = NewTimer() call SetTimerData(t, integer(U)) if DB.UnitDoDecayBones(U.u) then call BJDebugMsg(GetUnitName(U.u) + " has decaybones. It will be sent to the dump...") set w = BONE_DECAY_TIME - DECAY_DIFF else call BJDebugMsg(GetUnitName(U.u) + " doesn't have decaybones. It will be sent to the dump faster...") set w = DECAY_TIME * 0.7 endif call TimerStart(t, w, false, function UR.Apply) else call BJDebugMsg(GetUnitName(U.u) + " is a hero. Recycling it is not needed...") call U.destroy() endif set t = null endmethod private static method IsNotTest takes nothing returns boolean return not IsUnitInGroup(GetTriggerUnit(), DB.test) endmethod private static method onInit takes nothing returns nothing local trigger t = CreateTrigger() set UR.Dump = CreateGroup() set UR.Sumon = GetRectFromCircleBJ(RECT_POS, RECT_SIDE) call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH) call TriggerAddCondition(t, Condition(function UR.IsNotTest)) call TriggerAddAction(t, function UR.onRecycle) set t = null endmethod endstruct function RecycleUnit takes unit u returns unit return UR.UseRecycled(u) endfunction function CreateUnitEx takes player p, integer id, real x, real y, real f returns unit return RecycleUnit(CreateUnit(p, id, x, y, f)) endfunction endlibrary Right now I'm having issues in the code that detects if a unit decays, sometimes work, sometimes it doesn't. Any help will be appreciated & credited. |
| 08-09-2008, 05:34 AM | #8 |
Jass code without map for actually "better to see" systems useless. Write some AOS code (creep walkers) add your system and show. note: Artillery type attacks will surely crush your system. |
| 08-09-2008, 08:54 AM | #9 |
moyack, I'm not quite sure you've grasped the problems with unit recycling. The coding in the whole map must be adjusted for it. |
| 08-09-2008, 02:21 PM | #10 | ||
Quote:
Artillery is a scenario I haven't thought... well, it's bad and good at the same time because it makes the script simpler, but less versatile.... Quote:
This should be the simplified code (not tested in game): JASS://****************************************************************************** //* //* Unit Recycler //* By moyack. 2008 //* //* Requires TimerUnitls & Fake CSSafety made by Vexorian. //* [url]http://www.wc3campaigns.net/showthread.php?t=101322[/url] //* //* //* This library allows to your map to reuse died units, which saves memory. //* It's very useful in AoS or footies games, where the unit spawning is a //* common task. //* //* How it works? //* ============= //* //* The script detects when a unit dies, if it has corpse, it left the corpse //* for the decayBoneTime minus a time set by the variable DECAY_DIFF. //* When this time is over, the script moves the corpes to a secret place and //* revive the unit, storing it into a dump for a further reuse. //* //* Functions //* ========= //* //* in order to recycle a unit, you can use these function: //* => RecycleUnit(<unit variable>) //* This function takes as argument a unit, and returns the a unit. If there //* //* => CreateUnitEx(player, unitid, x, y, angle) //* Like the native function but tries to recycle the unit //******************************************************************************** library UnitRecycler requires CSSafety globals private constant real BONE_DECAY_TIME = 88. // Set the duration of corpse presence in the game. // Please take this value from the game constant "BoneDecayTime" private constant real DECAY_DIFF = 84. // the time before WC3 will remove the corpse, so the // Unit Recycler script can catch the corpse before being // automatically removed by the game. private constant integer REVIVE_ABIL = 'APrl' // I use the "Lesser Rune Resurrection" ability to // recycle the units. private constant string REVIVE_ORDER = "resurrection" // The resurrection order private constant integer DUMMY_REVIVER = 'hmpr' // dummy unit rawcode. private constant location RECT_POS = Location(-3000., -3200.) // Indicates the position of the // RECT's center. private constant real RECT_SIDE = 100. // Set the size of the rect where the units will be recycled private constant player DUMMY_PLAYER = Player(15) // sets the player owner of the unit's dump endglobals private struct Data unit u static method Set takes unit u returns Data local Data U = Data.allocate() set U.u = u return U endmethod endstruct private struct UR private static group Dump // the group which stores the unit that will be used for recycling private static rect Sumon // the place where the units will be revived for later use private static method FromDump takes nothing returns boolean return IsUnitInGroup(GetFilterUnit(), UR.Dump) and IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false endmethod static method UseRecycled takes unit u returns unit local unit t local real x = GetUnitX(u) local real y = GetUnitY(u) local real f = GetUnitFacing(u) local real m = GetUnitState(u, UNIT_STATE_MANA) local player p = GetOwningPlayer(u) local group g = NewGroup() call GroupEnumUnitsOfType(g, GetUnitName(u), Condition(function UR.FromDump)) set t = FirstOfGroup(g) call ReleaseGroup(g) if t != null then call BJDebugMsg(GetUnitName(t) + " is being reused...") call GroupRemoveUnit(UR.Dump, t) call RemoveUnit(u) call PauseUnit(t, true) call SetUnitPosition(t, x, y) call SetUnitFacing(t, f) call SetUnitOwner(t, p, true) call SetUnitState(t, UNIT_STATE_MANA, m) call SetUnitPathing(t, true) call PauseUnit(t, false) set u = null return t else return u endif endmethod private static method ApplyEx takes nothing returns nothing local Data U = GetTimerData(GetExpiredTimer()) call RemoveUnit(U.u) call U.destroy() call ReleaseTimer(GetExpiredTimer()) endmethod private static method Apply takes nothing returns nothing local Data U = GetTimerData(GetExpiredTimer()) local unit c if IsUnitType(U.u, UNIT_TYPE_DEAD) == true then // to add compatibility with normal resurrection abilities set c = CreateUnit(DUMMY_PLAYER, DUMMY_REVIVER, GetRectCenterX(UR.Sumon), GetRectCenterY(UR.Sumon), 0.) call UnitAddAbility(c, REVIVE_ABIL) call UnitAddAbility(c, 'Aloc') call ShowUnit(c, false) call SetUnitOwner(U.u, DUMMY_PLAYER, true) call SetUnitX(U.u, GetUnitX(c)) call SetUnitY(U.u, GetUnitY(c)) if IssueImmediateOrder(c, REVIVE_ORDER) then call SetUnitPathing(U.u, false) call SetUnitX(U.u, GetLocationX(RECT_POS)) call SetUnitY(U.u, GetLocationY(RECT_POS)) call GroupAddUnit(UR.Dump, U.u) call BJDebugMsg(GetUnitName(U.u) + " is ready for reuse...") endif call PauseTimer(GetExpiredTimer()) call TimerStart(GetExpiredTimer(), 1., false, function UR.ApplyEx) else call ReleaseTimer(GetExpiredTimer()) endif set U.u = c set c = null endmethod private static method onRecycle takes nothing returns nothing local Data U = Data.Set(GetTriggerUnit()) local timer t local real w if IsUnitType(U.u, UNIT_TYPE_HERO) == false then set t = NewTimer() call SetTimerData(t, integer(U)) call BJDebugMsg(GetUnitName(U.u) + " has decaybones. It will be sent to the dump...") set w = BONE_DECAY_TIME - DECAY_DIFF call TimerStart(t, w, false, function UR.Apply) else call BJDebugMsg(GetUnitName(U.u) + " is a hero. Recycling it is not needed...") call U.destroy() endif set t = null endmethod private static method onInit takes nothing returns nothing local trigger t = CreateTrigger() set UR.Dump = CreateGroup() set UR.Sumon = GetRectFromCircleBJ(RECT_POS, RECT_SIDE) call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DECAY) call TriggerAddAction(t, function UR.onRecycle) set t = null endmethod endstruct //*==================== //* User functions... * //*==================== function RecycleUnit takes unit u returns unit return UR.UseRecycled(u) endfunction function CreateUnitEx takes player p, integer id, real x, real y, real f returns unit return RecycleUnit(CreateUnit(p, id, x, y, f)) endfunction endlibrary |
| 08-09-2008, 05:34 PM | #11 |
I was discussing this with Griffen the other day. Unit Recycling isn't too wretchedly difficult, but I think it's entirely impossible to write one generalized and fully global script that would properly allow you to recycle units in any map. I think those sorts of things must be tailored heavily and specifically for the type of map in question. Griffen was saying he wants to write one too, but I'm fairly certain it'll run into similar blockades. |
| 08-09-2008, 08:13 PM | #12 | |
Quote:
I don't think it has to be tailored heavily, but all code would have to be tweaked (eg: with my code, all non-instant unit variables would have to be of type Unit, a value 'struct' holding the index and the life count). All on enter, train, summon, etc. code would have to go through a single system, and it would have to include a unit attachment system (or at least handle one). It could be made generic, but it would require quite a bit of tweaking to other stuff. And certain stuff would be out (not sure about summoned units, but adding expiration timers? Can't be undone, that'd be out, etc.). |
| 08-09-2008, 10:03 PM | #13 | |||
Quote:
The stack is the easy part, and I'm fairly a linked list approach is faster than cache, but it's more limited with units...would depend on the map whether that limit would of 8191 would be a problem, unless you use big arrays, in which case cache starts to look much more attractive. Quote:
Ooo, good point. I hadn't actually got to testing out how to do timer expiration (any my pc has broken, grr). Quote:
Indeed. |
| 08-10-2008, 12:01 AM | #14 | |
Quote:
So, in order to make it as more "universal" as possible, I should keep it as a script that squish as possible the units with corpses (if we think twice, most of the units in a AoS, melee, foties have decay bones), so it would be very useful. Hey.... I was thinking this: other option is not to wait until the unit dies, but "pseudo killing" it if it gets 1 hitpoints or less.... but... it would look a little bit odd and damage detection will be mandatory. |
| 08-10-2008, 02:15 AM | #15 | |||
Quote:
Quote:
Quote:
|
