| 05-25-2008, 07:22 AM | #1 |
Background: So, I would first and foremost like to thank the Tides of Blood team for inspiring me to make a (releasable) damage detection system in the first place. Past that, the background of the system is simple: I wanted to create a damage detection system that allows easy detection of attacks and spells and infinitely many damage types without changing the way people think about triggers. I wanted to create a world where damage detection is your friend and where you can easily check damage types in triggers, use waits, whatever you want, just like if Blizzard itself had made it for us. Well, I achieved that and am damned proud to present to you all the IDDS, intuitive and simple, for your use. Requirements:
Library:library IntuitiveDamageSystem initializer Init requires Table //****************************************************************************** //* BY: Rising_Dusk //* (Intuitive) Damage Detection System 1.14 //* //* This library is the core for what has come to be known as the Intuitive //* Damage Detection System, or IDDS for short. Simply by copying this library //* into your map somewhere, you will have access to all of its features and //* options. Below this documentation are some global variables that can be //* edited to make the system more useful for your map, whatever it might be. //* Please note that you should only change those globals listed under //* configuration constants and damage type constants. //* //* An important note for the system is that all non-attack damage in your map //* MUST BE TRIGGERED using the special function call included in this system, //* UnitDamageTargetEx. This is how the system works to detect attacks, because //* if the only non-triggered damage in your map originates from attacks, you //* clearly know which damage packets are attacks. This allows users to use //* orb abilities in their maps for whatever they want. //* //* function UnitDamageTargetEx takes unit source, unit target, real damage, ... //* ...attacktype attackType, integer damageType, boolean ConsiderArmor returns boolean //* //* This is the function with which you will deal all triggered damage in your //* map. The damageType argument is one of the predefined integer constants that //* you can edit or add below. Default values for this with the system are //* DAMAGE_TYPE_ATTACK, DAMAGE_TYPE_SPELL, and DAMAGE_TYPE_EXTRA. You can //* trigger the system to treat triggered damage like it is an attack if you //* want to by using DAMAGE_TYPE_ATTACK. It is very easy to make new damage //* types in the system, just follow the instructions in the configuration //* constants area. In addition, be sure that the constants you define do not //* conflict with the predefined Blizzard constants. (ie. DAMAGE_TYPE_FIRE) If //* these conflicts do exist, you will encounter multiply defined syntax errors. //* The AttackType argument is the same as in the regular UnitDamageTarget //* native. Also, the system allows you to consider armor when dealing damage or //* not. Set the ConsiderArmor boolean argument to false if you want to ignore //* armor for that damage, or true if you want to factor it in. //* //* function TriggerUnregisterDamageEvent takes trigger trg returns boolean //* function TriggerRegisterDamageEvent takes trigger trg, integer priority returns boolean //* //* The TriggerRegisterDamageEvent function is used when initializing a damage //* detection response trigger. By using this, it allows you to use a syntax //* structure nigh-identical to the standard JASS2. It returns a boolean for //* your convenience that is false if you pass it a null trigger. The system //* also allows you to pass a positive, zero-inclusive integer to it as that //* trigger's priority. The higher the number you pass, the later on in the //* trigger executions it will fire. This is useful if you want shield //* abilities, as you will want their priorities low so that they can block the //* damage before it gets to other things. You are also allowed to unregister //* a trigger from the system at any time if you want; this will likely never //* have to be done for most maps. //* //* function SetDamage takes real dmg returns nothing //* function SetDamageType takes integer dmgtype returns boolean //* //* With this function, you can modify the damage the system interprets for its //* triggers. This function DOES NOT ACTUALLY CHANGE THE DAMAGE BEING DEALT, it //* is merely a tool for users to use to change the internal variables. The user //* will need to modify the damage himself by some other means. Similarly, the //* SetDamageType function internally changes the damage type of a given packet //* of damage. This can be useful if you want to convert DAMAGE_TYPE_ATTACK into //* something else or if you want dynamic damagetypes in-game. //* //* function SetTriggerPriority takes trigger trg, integer priority returns boolean //* function GetTriggerPriority takes trigger trg returns integer //* //* These functions let you set or get a given trigger's priority at will. These //* functions both require that the trigger being passed to it is registered to //* the IDDS system. If you pass an unregistered trigger to GetTriggerPriority, //* it will return -1. If you pass a similar trigger to SetTriggerPriority, it //* will return false. //* //* function IgnoreHigherPriority takes nothing returns boolean //* //* This function is one of the most important reasons for priorities to exist. //* With it, you can tell the system to ignore higher priority triggers. This //* is useful, for instance, if you have a triggered evasion ability and don't //* want anything else to be done with that damage because it was dodged. Other, //* similar damage-preventing routines will also find this function useful. //* //* function RegisterDamageType takes nothing returns integer //* //* This function is useful for declaring your own DAMAGE_TYPE_ETC constants //* external to the system. By declaring your global variable and then calling //* this on it as follows, you can register new damage types on the fly. This //* is very useful if you want other systems or spells to introduce new damage //* types that are either ignored or do special things for that application. //* //* function GetTriggerDamageType takes nothing returns integer //* function GetTriggerDamageSource takes nothing returns unit //* function GetTriggerDamageTarget takes nothing returns unit //* function GetTriggerDamageBase takes nothing returns real //* function GetTriggerDamage takes nothing returns real //* //* Like normal WC3 damage detection, the system has event responses for the //* damage source, the target of the damage, the amount of damage dealt, and //* other things. It also permits the detection of damage type, which is //* something standard WC3 does not have. This lets you create on-attack spells //* very easily whereas without the system it would be very difficult and //* computationally costly. GetTriggerDamageBase returns the amount of damage //* the unit was dealt at the beginning of a given trigger series, whereas //* GetTriggerDamage returns whatever damage the unit has left to receive, if //* it has been modified in any way with the SetDamage function mentioned //* earlier. //* //* Once you understand all of the aforementioned aspects of the system, you're //* ready to put it to use. I know it can be tricky to require all spells be //* triggered, but this is the way of many great maps anyways, so such a //* requirement is not so unreasonable. If you have any questions regarding the //* system, please go to [url]www.wc3c.net[/url] and send a private message to the account //* Rising_Dusk and I will respond as soon as I can. This system may only be //* released at [url]www.wc3c.net[/url] and its existence on any other website is against //* the author's will. //* //* Enjoy! //* globals //* Configuration constants private integer DamageTypeCount = 4 //* These are the damagetype constant globals for ease of use constant integer DAMAGE_TYPE_ATTACK = 0 constant integer DAMAGE_TYPE_IGNORED = 1 constant integer DAMAGE_TYPE_SPELL = 2 constant integer DAMAGE_TYPE_EXTRA = 3 //* To add new constants, simply follow the naming convention and increment //* the number. You shouldn't change or remove DAMAGE_TYPE_ATTACK or //* DAMAGE_TYPE_IGNORED, though, since they have special properties in the //* system. //* These are static constants used by the system and shouldn't be changed private trigger RunTrigger = CreateTrigger() private trigger AddTrigger = CreateTrigger() private integer Count = 0 private Table TrigTable = 0 private Table RegiTable = 0 private boolean IgnPrior = false private integer array NewDamageType private real array NewDamage private trigger array Trg private integer array Priority //* Temporary variables used by the system private unit DamageSource = null private unit DamageTarget = null private integer DamageType = 0 private integer DamageId = 0 private real DamageBase = 0. private real Damage = 0. endglobals //****************************************************************************** //****************************************************************************** //* Use an insertion sort algorithm to sort the trigger stack based on priority private function TriggerSort takes nothing returns boolean local integer i = 1 local integer j = 0 local integer p = 0 local trigger t = null loop exitwhen i >= Count set t = Trg[i] set p = Priority[i] set j = i-1 loop exitwhen j < 0 or Priority[j] <= p set Priority[j+1] = Priority[j] set Trg[j+1] = Trg[j] set TrigTable[GetHandleId(Trg[j])] = j+1 set j = j - 1 endloop set Priority[j+1] = p set Trg[j+1] = t set TrigTable[GetHandleId(t)] = j+1 set i = i + 1 endloop set t = null return true endfunction //****************************************************************************** //****************************************************************************** //* The function to call when you want to end a damage's trigger series function IgnoreHigherPriority takes nothing returns boolean if DamageSource != null then //Make sure it was called in the right place set IgnPrior = true endif return IgnPrior endfunction //* Changes the base damage for a trigger series on the fly function SetDamage takes real dmg returns boolean if DamageSource != null and dmg >= 0 then //Make sure it was called in the right place set NewDamage[DamageId] = dmg set Damage = dmg return true endif return false endfunction //* Changes the base damage type of the series function SetDamageType takes integer dmgtype returns boolean if DamageSource != null and dmgtype >= 0 then //Make sure it was called in the right place set NewDamageType[DamageId] = dmgtype set DamageType = dmgtype return true endif return false endfunction //* Returns the given trigger's priority if it's loaded to the system function GetTriggerPriority takes trigger trg returns integer if RegiTable[GetHandleId(trg)] == 0 then return -1 endif return Priority[TrigTable[GetHandleId(trg)]] endfunction //* Sets the given trigger's priority if it's loaded to the system function SetTriggerPriority takes trigger trg, integer priority returns boolean if RegiTable[GetHandleId(trg)] == 0 or priority < 0 then return false endif set Priority[TrigTable[GetHandleId(trg)]] = priority return TriggerSort() endfunction //****************************************************************************** //****************************************************************************** //* The new damage function used by the system function UnitDamageTargetEx takes unit source, unit target, real damage, attacktype attackType, integer damageType, boolean ConsiderArmor returns boolean local boolean b = false set DamageType = damageType set DamageSource = source if ConsiderArmor then set b = UnitDamageTarget(source, target, damage, false, false, attackType, DAMAGE_TYPE_NORMAL, null) else set b = UnitDamageTarget(source, target, damage, false, false, attackType, DAMAGE_TYPE_UNIVERSAL, null) endif if not b or damageType == DAMAGE_TYPE_IGNORED then set DamageType = DAMAGE_TYPE_ATTACK set DamageSource = null endif return b endfunction //* The method by which one registers a trigger with the system function TriggerRegisterDamageEvent takes trigger trg, integer priority returns boolean if trg == null or priority < 0 then return false endif if RegiTable[GetHandleId(trg)] == 0 then set RegiTable[GetHandleId(trg)] = 1 endif set Trg[Count] = trg set Priority[Count] = priority set TrigTable[GetHandleId(trg)] = Count set Count = Count + 1 return TriggerSort() endfunction //* The method by which one unregisters a trigger from the system function TriggerUnregisterDamageEvent takes trigger trg returns boolean local integer i = 0 if trg == null then return false endif set i = TrigTable[GetHandleId(trg)] if trg != Trg[i] then return false endif set Trg[i] = Trg[Count] set Priority[i] = Priority[Count] set TrigTable[GetHandleId(Trg[i])] = i set RegiTable[GetHandleId(trg)] = 0 set Count = Count - 1 return TriggerSort() endfunction //* Initialization shorthand to register a new damage type externally function RegisterDamageType takes nothing returns integer local integer i = DamageTypeCount set DamageTypeCount = DamageTypeCount + 1 return i endfunction //****************************************************************************** //****************************************************************************** //* Wrappers for the system that can get inlined anyways function GetTriggerDamageType takes nothing returns integer return DamageType endfunction function GetTriggerDamageSource takes nothing returns unit return DamageSource endfunction function GetTriggerDamageTarget takes nothing returns unit return DamageTarget endfunction function GetTriggerDamageBase takes nothing returns real return DamageBase endfunction function GetTriggerDamage takes nothing returns real return Damage endfunction //****************************************************************************** //****************************************************************************** private function RunConditions takes nothing returns boolean //* The conditions for what must be true for damage detection to run return GetEventDamage() >= 0.0001 and DamageType != DAMAGE_TYPE_IGNORED endfunction private function AddConditions takes nothing returns boolean //* The conditions for registering a unit with the damage system return true endfunction private function PreloadConditions takes unit u returns boolean //* The conditions for preloading a unit to the damage system return true endfunction //****************************************************************************** //****************************************************************************** globals private integer array IDStack private integer IDC = 0 private integer IDN = 0 endglobals private function Run takes nothing returns nothing local unit u = GetEventDamageSource() local unit s = DamageSource local unit t = GetTriggerUnit() local integer i = 0 local integer id = 0 local integer d = DamageType local real r = GetEventDamage() local real b = r //Allocate an id for this damage packet if IDN > 0 then set id = IDStack[IDN] set IDN = IDN - 1 else set id = IDC set IDC = IDC + 1 endif if DamageSource == null then //Damage is of type attack set d = DAMAGE_TYPE_ATTACK set s = u endif loop exitwhen i > Count or IgnPrior //Ensure all variables are correct for nesting set Damage = r set DamageBase = b set DamageTarget = t set DamageSource = s set DamageType = d set DamageId = id set NewDamage[id] = 0. set NewDamageType[id] = -1 if IsTriggerEnabled(Trg[i]) and TriggerEvaluate(Trg[i]) then call TriggerExecute(Trg[i]) endif if NewDamage[id] > 0. then //Update damage if it was changed set r = NewDamage[id] endif if NewDamageType[id] >= 0 then //Update damagetype if it was changed set d = NewDamageType[id] endif set i = i + 1 endloop set Damage = 0. set DamageBase = 0. set DamageTarget = null set DamageSource = null set DamageType = DAMAGE_TYPE_ATTACK set DamageId = 0 set IgnPrior = false set NewDamage[id] = 0. //Return id to the stack set IDN = IDN + 1 set IDStack[IDN] = id set u = null set s = null set t = null endfunction private function Load takes nothing returns nothing call TriggerRegisterUnitEvent(RunTrigger, GetEnteringUnit(), EVENT_UNIT_DAMAGED) endfunction //****************************************************************************** //****************************************************************************** private function PreloadUnits takes nothing returns boolean if PreloadConditions(GetFilterUnit()) then call TriggerRegisterUnitEvent(RunTrigger, GetFilterUnit(), EVENT_UNIT_DAMAGED) endif return false endfunction private function Init takes nothing returns nothing local rect r = GetWorldBounds() local region re = CreateRegion() local boolexpr b = Condition(function PreloadUnits) local group g = CreateGroup() //* Create the tables for use with the system set TrigTable = Table.create() set RegiTable = Table.create() call TriggerAddAction(RunTrigger, function Run) call TriggerAddCondition(RunTrigger, Condition(function RunConditions)) call GroupEnumUnitsInRect(g, r, b) call RegionAddRect(re, r) call TriggerRegisterEnterRegion(AddTrigger, re, null) call TriggerAddAction(AddTrigger, function Load) call TriggerAddCondition(AddTrigger, Condition(function AddConditions)) call RemoveRect(r) call DestroyGroup(g) call DestroyBoolExpr(b) set re = null set g = null set b = null set r = null endfunction endlibrary Attached is a demo map that shows many of the facets of the system being used. There are also sufficiently many examples inside the test map to show anyone how to use it. Enjoy! |
| 05-25-2008, 07:29 AM | #2 | |
Jesus Christ Dusk, you're my new hero. I can go batshit with this thing for my AoS, since all spell damage is done via triggers. Quote:
EDIT: Looked at the code, and I can make a constant integer that does that. Like, DAMAGE_TYPE_AWESOME or whatever. EDITEDIT: I'm assuming this works generally the same way as the others; units get registered when they enter the map, and then they can be picked up by the event which catches units being damaged. |
| 05-25-2008, 07:43 AM | #3 | |
Quote:
|
| 05-25-2008, 08:30 AM | #4 |
EDIT: Woops, tested wrong one, will edit with feedback. |
| 05-25-2008, 08:38 AM | #5 | |||
Quote:
Quote:
Quote:
|
| 05-25-2008, 08:47 AM | #6 |
Bah you replied to fast above ^ == This system looks very interesting. So basically if the damage wasn't done by the system it has to be an attack, haha neat. For the testmap, I'd recommend also adding in 1-2 ability examples that trigger on attack, such as a - Critical Strike, Evasion, Backstab etc. On that note, say I want a critical strike, to add on 200% damage or whatever. What damage type should I have it dealt as, since its attack damage, but I don't want it to register another attack? == This should allow a few interesting possiblities such as my favourite d2 abilities: - Double Swing - Strafe Where you can make secondary attacks/shots actually still count as physical attacks. |
| 05-25-2008, 02:52 PM | #7 |
Ah yes Dusk, if this system can allow for custom evasions/critical strikes/cleaving strikes, then that would be awesome. A question: Can it find the amount of damage that was dealt when a unit is damaged? I'm assuming it can, but I wanted to be sure. |
| 05-25-2008, 03:08 PM | #8 | |
Quote:
JASS:set Damage = GetEventDamage() |
| 05-25-2008, 03:39 PM | #9 | ||
Quote:
Critical Strike:scope ExampleCriticalStrike private function Conditions takes nothing returns boolean return GetTriggerDamageType() == DAMAGE_TYPE_ATTACK and GetRandomInt(1,100) <= 15 endfunction private function Actions takes nothing returns nothing local unit u = GetTriggerDamageSource() local unit t = GetTriggerDamageTarget() local integer lvl = GetUnitAbilityLevel(u, 'A000') //Whatever ability local real d = GetTriggerDamage() //Deals +100%/200%/300%/etc damage call UnitDamageTargetEx(u, t, d*(lvl), ATTACK_TYPE_HERO, DAMAGE_TYPE_EXTRA, true) //Use a different damage type that considers armor identically to an attack, but so the //trigger doesn't fire itself //Do a call for fading text set u = null set t = null endfunction public function InitTrig takes nothing returns nothing local trigger trg = CreateTrigger() call TriggerRegisterDamageEvent(trg, function Actions, Condition(function Conditions)) set trg = null endfunction endscope Backstab is equally easy, heck, everything is; seriously, one example explains all! Dude, if you wanted the system to allow you to trigger all attacks for all units on the map, all you'd have to do is add a Hardened Skin ability to every unit on the map in a disabled spellbook and make its minimum damage 0.1, that way it'd still register an attack with the system but not hurt the enemy. Voillah, now you can make your own custom attack system with virtually no effort using this Damage Detection system as a base. Quote:
|
| 05-25-2008, 04:06 PM | #10 |
Dusk, I think you deserve some cookies. *cookies* I think we need to gather the best systems from Wc3c and make a huge map. It'd be the epitome of vJASS and JASS systems coming together in one huge map. |
| 05-25-2008, 07:51 PM | #11 |
A fine system for those who prefer to work with triggers and avoid structs. |
| 05-25-2008, 08:18 PM | #12 | |
Quote:
JASS:private function LoadEnumConditions takes unit EnumUnit returns boolean //* The conditions for registering a unit with the damage system upon map initialization return true endfunction private function InitLoadEnum takes nothing returns boolean local unit u = GetFilter() if LoadEnumConditions(u) then call TriggerRegisterUnitEvent(RunTrigger, u, EVENT_UNIT_DAMAGED) endif set u = null return false endfunction private function DamageSystemInit takes nothing returns nothing local rect r = GetWorldBounds() local region re = CreateRegion() local integer i = 0 local group g = CreateGroup() loop exitwhen i > 11 set AlternateSource[i] = CreateUnit(Player(i), DummyUnitID, 0, 0, 0) call UnitAddAbility(AlternateSource[i], 'Aloc') call PauseUnit(AlternateSource[i], true) set i = i + 1 endloop set RunTrigger = CreateTrigger() call TriggerAddAction(RunTrigger, function Run) call TriggerAddCondition(RunTrigger, Condition(function RunConditions)) set AddTrigger = CreateTrigger() call RegionAddRect(re, r) call TriggerRegisterEnterRegion(AddTrigger, re, null) call TriggerAddAction(AddTrigger, function Load) call TriggerAddCondition(AddTrigger, Condition(function AddConditions)) call GroupEnumUnitsInRect(g, r, Condition(function InitLoadEnum)) set i = 0 loop exitwhen i > TrgCount set Trg[i] = null set Cond[i] = null set Action[i] = null set i = i + 1 endloop call RemoveRect(r) call DestroyGroup(g) set re = null set r = null set g = null endfunction |
| 05-25-2008, 08:23 PM | #13 |
Yeah, but that would be a lame solution. The more logical solution is to harass Vex to make me able to move those initializations before the pre-placed unit creation. If you couldn't tell, I wanted to avoid such brute force crap; most mapmakers that want to trigger all damage will create their units at runtime anyways. Your method wouldn't compile anyways; it's GetFilterUnit(). :P EDIT: I suppose I could do that if you think it's necessary, though I hate the method you approached it with. |
| 05-25-2008, 08:30 PM | #14 |
Shaddup, so I forgot a word >_< And what says it's a lame solution? That's the only way there is to do it, and wouldn't it be better to have that functionality than not to have it because you want Vexorian to add something to vJASS so you don't have to write it yourself? For Pete's sake, I even wrote it for you already; all you have to do is copy/paste :P |
| 05-25-2008, 08:34 PM | #15 | |
Quote:
Actually, I want Vex to add it to vJass because it's a logical thing to do. Having this system as collateral to help in persuading him increases my chances of getting him to do it. I'll go ahead and write in my own way for getting the pre-placed units anyways, though. |
