- Joined
- Nov 11, 2006
- Messages
- 7,591
Damage Control
This library allows you to manipulate the damage caused and received by units. It works by setting the target's life to the appropriate value before they receive the damage, which effectively produces the same output as dealing that amount of damage.
Of course, there are complications when you hit someone for more than their health or if you want to reduce the damage taken, and that is why this system was created. It eases the process.
This is meant for rapid spell creation, and spell ingenuity. There are some spell ideas that people discard because it is way too much work. For example, turning damage received into damage gained, or increasing the amount of damage a unit deals for their next 3 attacks.. things along those lines.
Before anyone says it, yes this is exactly like DamageModifiers. However, there are some key differences in both the features and the way each is used. I do give due credit to DamageModifiers for the basis, but I felt that I personally wanted a few extra features.
Requirements:
- JassHelper 0.A.2.B.
- UnitIndexer hiveworkshop.com/forums/jass-functions-413/system-unit-indexer-172090/
- Event hiveworkshop.com/forums/jass-functions-413/snippet-event-186555/
- DamageEvent hiveworkshop.com/forums/jass-functions-413/snippet-damageevent-186829/
This system is still kind of a work in progress (some features still can be added), but it works and works well.
JASS:
library DamageControl /* v2.1.0.0
*************************************************************************************
*
* This library allows you to modify the damage output or intake of a unit
* for any attack, and provides a very neat interface.
*
*************************************************************************************
*
* */uses/*
*
* */ UnitIndexer /* hiveworkshop.com/forums/jass-functions-413/system-unit-indexer-172090/
* */ DamageEvent /* hiveworkshop.com/forums/jass-functions-413/snippet-damageevent-186829/
* */ Event /* hiveworkshop.com/forums/jass-functions-413/snippet-event-186555/
*
************************************************************************************
*
* Configuration
*/
globals
private constant integer ABILITY_LIFE_BONUS = 'DcLB' // rawcode of the life bonus ability
boolean NEGATIVE_DAMAGE_TO_HEAL = false // causes negative damage to heal the unit
// for the next damaging attack
integer DAMAGE_CONTROL_PRIORITY_LAST = 0xaa289 // this will automatically force the damage control
// struct to be issued last, after the other ones
endglobals
/*
************************************************************************************
*
* struct DamageControl extends array
*
* - This struct handles the execution of all Damage Control structs,
* - and has a few functions and methods for you to work with.
*
* static Event MOD
* - Fired before the system processes damage modifications.
* static Event FINAL
* - Fired after the system completes all damage modifications.
* static real result
* - The resulting damage of an attack after modifications. Use with
* - the FINAL event.
* static unit source
* - The source of the damage. You can use this or DamageEvent.source
* static unit target
* - The target of the damage. You can use this or DamageEvent.target
* static integer sourceId
* - The index of the source unit. You can use this or DamageEvent.sourceId
* static integer targetId
* - The index of the target unit. You can use this or DamageEvent.targetId
* static boolean enabled
* - Tells whether or not the system is enabled in general.
* static boolean enabledNext
* - Tells whether or not the system is enabled for the next attack.
*
* static method enable takes nothing returns nothing
* - Enables the DamageControl system completely.
* static method disable takes nothing returns nothing
* - Disables the DamageControl system completely.
* static method enableNext takes nothing returns nothing
* - Enables the DamageControl system for the next damaging attack. This
* - function is mostly useless, except in rare cases when you want to
* - counteract a disableNext() call.
* static method disableNext takes nothing returns nothing
* - Disables the DamageControl system for the next damaging attack.
* - It will automatically reset the system to be enabled afterward.
*
********************************************************************
*
* module DamageControlModule
*
* - To access the damage control features, you must implement this module
* - at the bottom of your struct. It unlocks two methods for you to define:
* static method onDamageDealt takes unit source, unit target, real amount returns real
* static method onDamageTaken takes unit source, unit target, real amount returns real
* - onDamageDealt is executed whenever a unit, source, deals damage.
* - onDamageTaken is executed whenever a unit, target, takes damage.
* - Both functions should return the resulting amount of damage.
*
* static boolean array enabled
* - To enable your damage control struct for a unit, you simply will set
* - this boolean to true, corresponding to the user data of the unit:
* set enabled[GetUnitUserData(unit)] = boolean
* - All damage control structs are initially disabled for each unit.
*
* static integer array dealtCount
* static integer array takenCount
* - This keeps track of how many times each unit has had their damage
* - modified by this system. To retrieve each value, simply input the
* - user data of the unit as the array value. See the demo spells for
* - clear examples on how to use this. Note that these are automatically
* - incremented for you every time that struct is used by the unit.
*
********************************************************************
*
* module DamageControlPriorityModule (added in v.2.0.0.0)
*
* - Everything is exactly the same as DamageControlModule, however,
* - this module allows you to specify a priority which determines
* - when the damage control functions of your struct will be fired
* - in relation to the other damage control structs.
*
* - You MUST define an integer variable named PRIORITY in your struct
* - for this module. For example:
* static integer PRIORITY = 1
* - The number determines what "slot" it will be fit into in terms
* - of evaluation. The lower values are always the first ones to be
* - evaluated. For example, if there are 3 structs about to be evaluated,
* - and one has a priority of 0, the second a priority of 50, and the
* - third a priority of 2, then it will fire the structs in order of
* - priority: 0, then 2, then 50. By default, all priority structs will
* - be fired BEFORE non-priority structs, with one case as an exception:
*
* - This will also allow you to evalute structs AFTER all of the other
* - structs. To do so, simply use this as the value for PRIORITY:
* static integer PRIORITY = DAMAGE_CONTROL_PRIORITY_LAST
* - This will fire at the end, after all of the integer priority structs
* - and after the non-priority structs.
*
********************************************************************
*
* struct SampleStruct extends array
*
* static method onDamageDealt takes unit source, unit target, real amount returns real
* return amount + 100.
* endmethod
* - This increases the damage dealt by the unit by 100.
*
* static method onDamageTaken takes unit source, unit target, real amount returns real
* if takenCount[DamageEvent.targetId] == 3 then
* set takenCount[DamageEvent.targetId] = 0
* set enabled[DamageEvent.targetId] = false
* endif
* return amount - 30.
* endmethod
* - This reduces the damage taken by the unit by 30 for the next 3
* - damaging attacks he receives. Once it reaches 3, it will disable
* - the unit from receiving the damage control of this struct.
*
* implement DamageControlModule
*
* endstruct
*
********************************************************************
*
* Credits
*
* - Anitarf: DamageModifiers (ideas and basis)
* - Nestharus: AdvDamageEvent (ideas and basis)
* - Nestharus: UnitIndexer, DamageEvent, Event
* - Bribe: For his ingenious method
*
************************************************************************************/
globals
private constant boolean DEFAULT = NEGATIVE_DAMAGE_TO_HEAL
private trigger damageTrigger = CreateTrigger()
private timer lifeBonusTimer = CreateTimer()
endglobals
private module DamageControlSystemInit
private static method onInit takes nothing returns nothing
set thistype.FINAL = CreateEvent()
set thistype.MOD = CreateEvent()
call DamageEvent.ANY.register(Condition(function thistype.onDamage))
endmethod
endmodule
private struct PriorityList
static integer LAST = DAMAGE_CONTROL_PRIORITY_LAST // random number
static thistype head = 0
static thistype last = 0
private thistype next
private integer prio
private trigger curr
static method insert takes integer priority, trigger evaluate returns nothing
local thistype node = thistype.allocate()
local thistype this = head.next
set node.prio = priority
set node.curr = evaluate
if this == 0 then
set head.next = node
set node.next = 0
set last = node
return
elseif priority == LAST then
set last.next = node
set node.next = 0
set last = node
return
endif
loop
exitwhen this == 0
if this.prio == priority or this.prio == LAST then
set node.next = this.next
set this.next = node
return
endif
set this = this.next
endloop
endmethod
static method evaluate takes nothing returns nothing
local thistype this = head.next
if this != 0 then
loop
exitwhen this.prio == LAST // evaluate up until the ones
call TriggerEvaluate(this.curr) // that should be last
set this = this.next
endloop
endif
call TriggerEvaluate(damageTrigger) // evaluate the non-priority ones
loop
exitwhen this == 0 // evaluate the ones that
call TriggerEvaluate(this.curr) // should be evaluated last
set this = this.next
endloop
endmethod
endstruct
struct DamageControl extends array
static Event FINAL = 0
static Event MOD = 0
static real result = 0
private static integer size = 0
private static unit array lifeUnits
private static integer array unitSize
static boolean enabled = true
static boolean enabledNext = true
static method operator source takes nothing returns unit
return DamageEvent.source
endmethod
static method operator target takes nothing returns unit
return DamageEvent.target
endmethod
static method operator sourceId takes nothing returns integer
return DamageEvent.sourceId
endmethod
static method operator targetId takes nothing returns integer
return DamageEvent.targetId
endmethod
static method enable takes nothing returns nothing
set enabled = true
endmethod
static method disable takes nothing returns nothing
set enabled = false
endmethod
static method enableNext takes nothing returns nothing
set enabledNext = true
endmethod
static method disableNext takes nothing returns nothing
set enabledNext = false
endmethod
private static method manageLife takes nothing returns nothing
local real life
local integer id
loop
exitwhen size == 0
set id = GetUnitUserData(lifeUnits[size])
if unitSize[id] == 1 then
set life = GetWidgetLife(lifeUnits[size])
call UnitRemoveAbility(lifeUnits[size], ABILITY_LIFE_BONUS)
call SetWidgetLife(lifeUnits[size], life)
endif
set unitSize[id] = unitSize[id] - 1
set size = size - 1
endloop
endmethod
private static method fireEvent takes nothing returns nothing
if unitSize[DamageEvent.targetId] > 1 then
call UnitRemoveAbility(DamageEvent.target, ABILITY_LIFE_BONUS)
endif
call FINAL.fire()
if unitSize[DamageEvent.targetId] > 0 then
call UnitAddAbility(DamageEvent.target, ABILITY_LIFE_BONUS)
endif
endmethod
private static method onDamage takes nothing returns boolean
local unit target = DamageEvent.target
local real damage = DamageEvent.amount
local real life = GetWidgetLife(target)
local real temp = result
set result = damage
if damage <= 0 or not enabled then
call thistype.fireEvent()
set result = temp
set target = null
return false
endif
if not enabledNext then // this is separate from the above block
set enabledNext = true // because it is meant to prevent the
call thistype.fireEvent() // modification of the next damaging attack,
set result = temp // so it should not be used up on 0 damage
set target = null // events such as debuffing.
return false
endif
call MOD.fire()
call PriorityList.evaluate()
if (damage - 0.1) <= result and result <= (damage + 0.1) then
call thistype.fireEvent()
set result = temp
set target = null
return false
endif
set life = life + damage - result
if result < damage then
set size = size + 1
set lifeUnits[size] = target
set unitSize[DamageEvent.targetId] = unitSize[DamageEvent.targetId] + 1
if result < 0 and not NEGATIVE_DAMAGE_TO_HEAL then
set result = 0
set life = life + result
endif
call TimerStart(lifeBonusTimer, 0., false, function thistype.manageLife)
elseif life < 0.405 then
set life = 0.5
endif
call thistype.fireEvent()
call SetWidgetLife(target, life)
set result = temp
set target = null
set NEGATIVE_DAMAGE_TO_HEAL = DEFAULT
return false
endmethod
implement DamageControlSystemInit
endstruct
module DamageControlModule
static boolean array enabled
static if thistype.onDamageDealt.exists() then
static integer array dealtCount
endif
static if thistype.onDamageTaken.exists() then
static integer array takenCount
endif
private static method onDamage takes nothing returns boolean
local real damage = DamageControl.result
static if thistype.onDamageDealt.exists() then
if thistype.enabled[DamageEvent.sourceId] then
set dealtCount[DamageEvent.sourceId] = dealtCount[DamageEvent.sourceId] + 1
set DamageControl.result = thistype.onDamageDealt(DamageEvent.source, DamageEvent.target, damage)
endif
endif
static if thistype.onDamageTaken.exists() then
if thistype.enabled[DamageEvent.targetId] then
set takenCount[DamageEvent.targetId] = takenCount[DamageEvent.targetId] + 1
set DamageControl.result = thistype.onDamageTaken(DamageEvent.source, DamageEvent.target, damage)
endif
endif
return false
endmethod
private static method onInit takes nothing returns nothing
call TriggerAddCondition(damageTrigger, Condition(function thistype.onDamage))
endmethod
endmodule
module DamageControlPriorityModule
static boolean array enabled
static if thistype.onDamageDealt.exists() then
static integer array dealtCount
endif
static if thistype.onDamageTaken.exists() then
static integer array takenCount
endif
private static method onDamage takes nothing returns boolean
local real damage = DamageControl.result
static if thistype.onDamageDealt.exists() then
if thistype.enabled[DamageEvent.sourceId] then
set dealtCount[DamageEvent.sourceId] = dealtCount[DamageEvent.sourceId] + 1
set DamageControl.result = thistype.onDamageDealt(DamageEvent.source, DamageEvent.target, damage)
endif
endif
static if thistype.onDamageTaken.exists() then
if thistype.enabled[DamageEvent.targetId] then
set takenCount[DamageEvent.targetId] = takenCount[DamageEvent.targetId] + 1
set DamageControl.result = thistype.onDamageTaken(DamageEvent.source, DamageEvent.target, damage)
endif
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerAddCondition(t, Condition(function thistype.onDamage))
call PriorityList.insert(thistype.PRIORITY, t)
set t = null
endmethod
endmodule
endlibrary
As of v.2.0.0.0, priorities have been added. This feature allows you to tell the system in what order you want your damage control structs to be run. For certain ones, you may want it to run first, and for others, you may want it to run last. Now this system has the ability to do that. Read the documentation for more info.
Why are priorities important? Well, resulting damage varies depending on the order in which damage controls are applied. It is sometimes crucial to have certain things run before or after others, else it will lead to the wrong results. With the previous method, there was no way of having that occur without simple luck or extra work. For example, struct A reduces damage taken by 50% and struct B increases damage taken by 25. A unit damages a unit, who has both those structs enabled, for an unmodified 100 damage. If A executes before B, the resulting damage will be 100 * 0.5 + 25 = 75. If B executes before A, the resulting damage will be (100 + 25) * 0.5 = 62.5. See the difference?
Why would someone choose not to use priorities? Well, yes, uniformity is good and I could make it so that all structs require priorities. (and then just add a WHO_KNOWS priority) However, the priority structs have one extra trigger, and they are entered into a list. I decided that if someone doesn't care about priorities, then they can just ignore it and use a normal damage control module. Sometimes, it doesn't really matter, especially when you are not modifying damages by percentages. (flat increases/reductions don't need priorities) That way, the implementation is slightly easier (you don't have to type an extra integer variable, lol) and it is slightly more efficient. Although, I might change this in the future if people feel that uniformity would be the better choice.
In addition, I made a very detailed demo map. It features 4 basic spells:
- Enrage - Causes the unit to deal 50 extra damage. Lasts for the next 4 attacks.
- Mutated Blood - Causes any attacks the caster receives to heal the caster instead, for twice as much damage as it would have done. Lasts for the next 5 attacks received.
- Phoenix Blast - Deals 125 damage to a target, but causes target's next 2 attacks to deal 30 extra damage.
- Lightning Armor - Creates a protective barrier around the caster, reducing the damage he takes by 50%. In addition, all attackers are struck back for 50 damage.
All of those spells are made relatively easily, and require only a fraction of the code it would normally require. They are heavily commented and each demonstrate interesting ways to take advantage of the system's features.
Attached below is the demo map. Enjoy!
2.1.0.0
- Added better multiple-instance support.
- Added operators to read the event data to avoid unnecessary mixing.
- The event now fires in the correct order, allowing GetWidgetLife() to read the appropriate life before the actual modifications. Please update to 2.1.0.0 if you are using any earlier version.
- Added priorities and reworked the system using Bribe's method to eliminate the need for custom SetUnitLife/GetUnitLife functions.
- Code Optimizations and redocumented.
- Big bug fix. Please update to 1.1.1.1 if you are using 1.1.01.
- Added a GetUnitMaxLife function to receive a unit's maximum hp without interference from the system.
- Minor Code Optimizations
- Bug Fix
- Initial Release
Attachments
Last edited by a moderator: