| 01-25-2011, 11:10 AM | #1 | |
A library to detect physical attacks on units using buff-placing attack modifiers. This library uses vJass, AutoIndex, DamageModifiers and LinkedList. Additionally, you need a library that supports DamageModifiers, and DamageEvent is the only one known to me that does that.
JASS:/******************************************************************************* * PhysicalAttack -- Version 1.0.0 * by Deaod * * A library that detects physical attacks of units using attack modifiers. * * KNOWN BUGS: * This library cant detect usage of Attack Ground. * * API: * You have to register any buff thats placed by an attack on the attacked unit * using * * function RegisterAttackBuff takes integer buffid, integer priority returns nothing * * The priority determines in which order these buffs will be checked for, so * its probably a good idea to give more frequently occurring buffs a higher * priority. * * To actually respond to physical attacks, you have to create a struct that * extends PhysicalAttack and implement methods onAttack and/or onAttacked. * * EXAMPLE: * * // simple attack modifier that makes a unit deal 50% more damage 50% of the * // time on the first level, 75% more damage 75% of the time on the second * // level and 100% more damage 100% of the time on the third level. * struct Demonstration extends PhysicalAttack * integer level * * // the source of this attack is the unit you created this struct for * // the target is passed to this method, as well as the damage * // and so is the ID of the buff that was detected * method onAttack takes unit target, real damage, integer buffid returns real * if GetRandomReal(0,1)<=(0.25+(level*0.25)) then * // positive return value increases damage dealt * return damage*(0.25+(level*0.25)) * else * // negative return value decreases damage dealt * return -damage*(0.25+(level*0.25)) * endif * endmethod * * // no onAttacked method, so this struct only responds to when a unit * // attacks another unit. * * // you are free to implement a create method in your struct, and if * // you do, you have to pass a unit and an integer to the allocator. * static method create takes unit u, integer level returns thistype * // dont forget to pass the unit you want attack detection for to the * // allocator along with the priority * // the priority determines in which order the structs are processed. * // Higher priority means earlier execution. If there are multiple * // structs with the same priority, the ones of the attacking unit get * // evaluated first and the first one of those to be evaluated is the * // one created first. * local thistype s=allocate(u, 0) * set s.level=level * return s * endmethod * endstruct * * CREDITS: * Anitarf (DamageModifiers) * Ammorth (LinkedList) * grim001 (AutoIndex) * Vexorian (JassHelper) * MindWorX (JassNewGenPack) * PitzerMike (JassNewGenPack) * Pipedream (Grimoire) * SFilip (TESH) * ******************************************************************************/ library PhysicalAttack requires DamageModifiers, AutoIndex, LinkedList globals // this is the priority of this library as a damage modifier. // damage modifiers with a higher priority will get evaluated before this one. // damage modifiers with a lower priority will get evaluated after this one. // its currently the highest possible value. // see also: DamageModifiers library private constant integer PRIORITY = 0x7FFFFFFF endglobals // filter out units you dont want to detect physical attacks for. private function UnitFilter takes unit u returns boolean return true endfunction // Don't touch anything below. private keyword UnitData private keyword unitData private keyword priority globals private List AttackBuffs=0 endglobals private struct AttackBuff integer abilityID integer priority endstruct function RegisterAttackBuff takes integer buffid, integer priority returns nothing local Link l local AttackBuff s=AttackBuff.create() set s.abilityID=buffid set s.priority=priority if AttackBuffs==0 then set AttackBuffs=List.create() endif loop exitwhen l==0 or AttackBuff(l.data).priority<priority or l.next==0 set l=l.next endloop if l==0 then call Link.create(AttackBuffs, s) elseif l.next==0 then call Link.createLast(AttackBuffs, s) else call l.insertBefore(s) endif endfunction struct PhysicalAttack integer priority UnitData unitData stub method onAttack takes unit target, real damage, integer buffid returns real return 0. endmethod stub method onAttacked takes unit source, real damage, integer buffid returns real return 0. endmethod method destroy takes nothing returns nothing call unitData.removeCallback(this) call deallocate() endmethod static method create takes unit whichUnit, integer priority returns thistype local thistype s if UnitFilter(whichUnit) then set s=allocate() set s.priority=priority set s.unitData=UnitData[whichUnit] call s.unitData.addCallback(s) return s debug else debug call BJDebugMsg("PhysicalAttack: Can't create struct for "+GetUnitName(whichUnit)+"! Unit is filtered.") endif return 0 endmethod endstruct private struct UnitData extends DamageModifier private List structs private static method createFilter takes unit u returns boolean return UnitFilter(u) endmethod private static method create takes unit u returns thistype local thistype s=allocate(u, PRIORITY) set s.structs=List.create() return s endmethod private method onDestroy takes nothing returns nothing call structs.destroy() endmethod private method onDamageDealt takes unit target, real damage returns real local Link sl local Link tl local Link b local integer buffid local real res=0 if AttackBuffs!=0 then set b=AttackBuffs.first loop exitwhen b==0 if GetUnitAbilityLevel(target, AttackBuff(b.data).abilityID)>0 then set buffid=AttackBuff(b.data).abilityID set sl=structs.first if UnitFilter(target) then set tl=UnitData[target].structs.first else set tl=0 endif loop exitwhen sl==0 and tl==0 if sl!=0 and (tl==0 or PhysicalAttack(sl.data).priority>=PhysicalAttack(tl.data).priority) then set res=res+PhysicalAttack(sl.data).onAttack(target, damage+res, buffid) set sl=sl.next else set res=res+PhysicalAttack(tl.data).onAttacked(this.me, damage+res, buffid) set tl=tl.next endif endloop call UnitRemoveAbility(target, buffid) // return now return res endif set b=b.next endloop endif return 0. endmethod method addCallback takes PhysicalAttack which returns nothing local Link l=structs.first loop exitwhen l==0 or PhysicalAttack(l.data).priority<which.priority or l.next==0 set l=l.next endloop if l==0 then call Link.create(structs, which) elseif l.next==0 then call Link.createLast(structs, which) else call l.insertBefore(which) endif endmethod method removeCallback takes PhysicalAttack which returns nothing call structs.search(which).destroy() endmethod implement AutoCreate implement AutoDestroy endstruct endlibrary |
| 01-25-2011, 11:29 AM | #2 |
Yay, new submissions! No idea what that does, I am no coder, but the fact someone still cares enough to submit stuff to this site is very nice. :P |
| 01-25-2011, 05:55 PM | #3 |
Just use Frost Breath buff as that works for everything. Registering new buffs is also actually a good idea o-o, but Frost Breath for general attacks. I can see why this couldn't catch attack ground, but I solved that problem in AttackIndexer by creating a dummy unit at the order point and making the w/e attack that instead. This can be further modularized by making a simple DamageEvent sort of deal that can detect physical attacks and another thing that can index those physical attacks (indexing makes onAttack (onIndex), onResolve, and onDeindex useful as that provides a unique index). You can also go farther with this and detect JASS and spell attacks (done in AdvDamageEvent at THW). One thing I've learned is that buffs aren't placed on a unit until after damage is dealt. Just making sure you accounted for that with a timer ;P. I'm sure you tested it and made sure it's all working, but just double checking here ;D. Guess wc3c needs the systems being done at THW too : D. AdvDamageEvent And also, you should use a likely variable or w/e to cut down on iteration. For example, the last buff placed would be the most likely next buff and its next would be the second most likely. The closest buff ids to it would actually be the most likely buffs (ones that haven't resolved yet). AdvDamageEvent at THW support damage modifiers too btw, so that's two that you know of now that does it. I think Damage by j4l supports it as well at TH. 3 forums with 3 different standards of coding and 3 different sets of systems that are all incompatible with each other, haha : D. |
| 01-25-2011, 08:18 PM | #4 | |||||
Quote:
Quote:
Quote:
Quote:
Quote:
|
| 01-26-2011, 12:03 AM | #5 | |||
Quote:
Only efficient solution I could think of, and they wouldn't be able to select the dummy unit : ). Quote:
When I was working on AdvDamageEvent, the level of the buff returned 0 every time. When I added in a timer, it returned the expected 1 ; P. Quote:
Was just thinking of damage modification in general ;D. So yea, wasn't talked about DamageModifiers ;p. |
| 01-26-2011, 08:48 AM | #6 | ||
After some test and writing of code, im back. Quote:
Quote:
I'm still not convinced i can prevent users from killing the dummy unit with spells (if they kill it with physical attacks, their fault). |
| 01-26-2011, 04:17 PM | #7 |
->Shouldve thought of this earlier, but using an attack modifier that slows by default is a really bad idea. Too many attacks using that attack modifier on a unit and that unit will be frozen. Go and ask Karawasa for confirmation. But you remove the buff immediately no? It'd only have it on for a split instant, so it shouldn't even be noticeable. Also, you should be able to tell JASS and spell attacks too with a small addition ; p. I did it using hooks. If the thing had a buff, it was physical. If the thing was hooked, it was JASS. If none of the above, it was a spell ;P. Worked brilliantly. |
| 01-26-2011, 04:38 PM | #8 | |
Quote:
|
| 01-26-2011, 05:10 PM | #9 |
And Corruption works on splash and etc like Frost Breath? |
| 01-26-2011, 05:21 PM | #10 |
No. But what difference does it make? You can trigger splash anyway. |
| 01-26-2011, 09:39 PM | #11 |
->No. But what difference does it make? You can trigger splash anyway. A major difference in overhead >: p, but w/e, do what you think is best ;D. Only abilities that seem to apply splash properly are the frost abilities, and Frozen Breath appeared to be the best. |
| 01-26-2011, 10:38 PM | #12 |
Personally, I prefer to trigger all my spell damage using xedamage, then I can easily identify any non-triggered damage as attack damage. However, I can see the benefits of a buff-based attack detection method as well. There is no reason that an attack detecting library would have to only support one of these methods, though. With some optional requirements, a calibration constant and some static if magic, it could easily support both. For the buff detection method, it would be useful if the system automatically created a hidden buff-placing on-attack passive ability using the objectmerger and automatically added that ability to all units when they are indexed. This would save the users some effort when installing the library since most users will need it for generic attack damage detection where every unit needs some form of buff-placing ability. I'm wondering if there is a way to resolve conflicts with triggered on-attack abilities that don't use this system, but instead do their own buff detection and removal. If the ability's buff is not added to the system, then attacks made with that ability active will not register as physical attacks. If the buff is added, then it will be removed and the custom ability's own code won't work any more. It should not be too difficult to modify any such abilities to use this system instead of doing the detection themselves, so this isn't such a big problem. It certainly isn't worth the overhead of only removing the buffs after a 0.0 timer expires to allow other code to detect them. However, speaking of generic attack damage detection, there should be a way to have non-unit-specific ways to respond to attack damage events. I never aded this functionality to DamageModifiers because I already had DamageEvent, but DamageEvent didn't support priorities or modifying damage, so in hindsight I could have used non-unit-specific damage modifiers. However, I now remembered that AutoIndex allows us to easily automate the creation and destruction of unit-specific structs for all units with the AutoCreate and AutoDestroy modules, so this is kind of a moot point as well. I guess this leaves me with only two points: one, the resource should support various methods for identifying attack damage. First, the attack buff detection. Second, damage native and BJ hooks and assuming all non-attack damage is dealt through triggers. Third, using xedamage.isInUse() and assuming all non-attack damage is dealt with xedamage. The second point is that for the buff-based attack damage detection method, an invisible base buff-applying ability should be automatically generated and given to all units to save the users the trouble of doing that. By the way, I think PhysicalAttack is a bit awkward as a name. This is meant to detect unit attacks, whether they be physical or magical. Considering what this library expands upon, a name like AttackDamageModifier or something shorter like AttackDamage feels more appropriate. |
| 03-14-2011, 04:46 AM | #13 |
Damage isn't applied until after the damage event... just tested it : |. First the damage event runs, then the damage is applied. Regular Damage- 1 damage event fires, buff applied before damage event Missile Splash Damage 2 damage events fire for target unit first damage event runs w/ dmg and w/o buff second damage event runs w/ 0 damage and buff 2 damage event fires for rest of units damage event runs w/ dmg and w/o buff damage event runs w/ 0 damage and w/0 buff (wc3 bug??) buff applied on rest of units on next period Artillery Splash Damage 1 damage event fires Buff applied to all units on next period So this raises a few problems... you have to start a 0 second timer to catch all of the buffs for all possible attacks, but then you run into a problem with artillery attacks. Artillery attacks explode units, and to catch the buffs, you have to save the unit from dying before you know whether the unit will live or not. This means that artillery attacks will no longer be able to explode units as there is no good way to detect whether an attack was an artillery attack or not. Some work was done with attackground and ordering a unit to attack a tree, but that requires the unit's orders be interrupted, so they're not good solutions. Now we run into another problem... if a unit has a non splash detection ability, the splash detection ability won't run. edit Solved in AdvDamageEvent >: ). There is also an ability problem. You have to use 2 abilities to get it working, and the two that appear to work are Frost Breath and Item Frost Damage Bonus. I have tried many combos and many abilities =). edit Here is the solution, cut straight out of the latest AdvDamageEvent- JASS:
if (UnitAlive(GetUnitById(targetId))) then
if (GetUnitAbilityLevel(GetUnitById(targetId), DUMMY_BUFF) > 0) then
//was a physical attack
if (ec[eventCount]) then
//initial splash (runs through w/ only splash abils on splash attack)
loop
set eventType[i] = PHYSICAL
set es[i] = 0
set i = i - 1
exitwhen not ec[i] or es[i] != sourceId
set ec[i] = false
endloop
else
//not splash (doesn't run w/ splash abil no non splash attack)
set eventCount = eventCount + 1
set eventType[eventCount] = PHYSICAL
set update = true
endif
elseif (ls == sourceId and eventType[eventCount] == PHYSICAL) then
//this will only run with non-splash abil and splash abil on splash attack
//first the 0 dmg runs, then the actual damage runs, alternates
if (ec[eventCount]) then
set ec[eventCount] = false
else
set eventCount = eventCount + 1
set eventType[eventCount] = PHYSICAL
set ec[eventCount] = true
set update = true
endif
elseif (hooked[sourceId] > 0) then
//JASS attack
set hooked[sourceId] = hooked[sourceId] - 1
set eventCount = eventCount + 1
set eventType[eventCount] = JASS
set update = true
elseif (overType != 0) then
//known attack
set eventType[eventCount] = overType
set overType = 0
set update = true
else
//unknown attack
//will initially run for all splash attacks
//artillery attacks won't be known until after timer
set eventCount = eventCount + 1
set eventType[eventCount] = 0
set update = true
set ec[eventCount] = true
set es[eventCount] = sourceId
endif
//for chaining attacks together, last source = current source
set ls = sourceId
//if update (didn't skip), then add to timer stack
if (update) then
set eventSourceId[eventCount] = sourceId
set eventTargetId[eventCount] = targetId
set eventDamage[eventCount] = amount
//if haven't saved the unit, add life abil to save it
if (saved[targetId] == 0) then
set life = GetWidgetLife(GetUnitById(targetId))
call UnitAddAbility(GetUnitById(targetId), ADV_DAMAGE_EVENT_SAVE_UNIT_ABILITY)
call SetWidgetLife(GetUnitById(targetId), life+500000)
endif
//always increase the save count so life ability is removed at right time
set saved[targetId] = saved[targetId] + 1
//start the timer to actually handle (only needed because of artillery attacks)
//artillery attacks are *never* known until after timer
call TimerStart(eventTimer, 0, false, function thistype.handleEvent)
endif
endif
All units have Item Attack Frost Bonus and Frost Breath added to them at index. Here is the final step inside of the timer method for retrieving type JASS:
if (type == 0) then
if (GetUnitAbilityLevel(target, DUMMY_BUFF) > 0) then
set type = PHYSICAL
set explode = true
else
set type = SPELL
set explode = false
endif
else
set explode = false
endif
if (type == PHYSICAL) then
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "PHYSICAL")
call UnitRemoveAbility(target, DUMMY_BUFF)
endif
call ANY.fire()
call Event(type).fire()
And killing JASS:
set life = GetWidgetLife(target)-500000
set saved[targetId] = saved[targetId] - 1
if (life < .405) then
call SetUnitExploded(target, explode)
set saved[targetId] = 0
call SetWidgetLife(target, .5)
call disable()
call UnitDamageTarget(GetUnitById(sourceId), GetUnitById(targetId), 100, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
call enable()
elseif (saved[targetId] == 0) then
call UnitRemoveAbility(GetUnitById(targetId), ADV_DAMAGE_EVENT_SAVE_UNIT_ABILITY)
call SetWidgetLife(target, life)
endif
Have fun =). Giving this up so that you guys can get the wc3c variant of PhysicalAttack approved ^)^. THW will have a version, wc3c will have a version, and TH will not =). With these, j4l's Damage at TH is kind of obsolete =D. Eventually, they'll catch up ;P. And you are welcome for the complete solution. You can also expand slightly to infer between physical, spell, and JASS attacks by hooking all of the BJs and natives. Also, with my solution, you can tell between artillery, splash, and non-splash attacks, lol. There is no accurate way to detect multishot : \ It took me an incredibly long time to come up with this solution... a lot of thinking. While I was testing different abilities to see if any would work with both splash and non splash, I came upon the solution when seeing how the buffs were added and what order the damage was done in ;p. |
| 03-16-2011, 06:43 AM | #14 | |
Quote:
Can you elaborate more? I'm still having trouble understanding why I want something that only detects buff-placers. So... it only works with units that do a normal attack? Normal attack as in, the attack command? |
| 03-17-2011, 03:14 AM | #15 | |
Quote:
It actually doesn't work at the moment, but that's the general gist yea. See my post for information on why it doesn't work ; P I even posted the solution for how to fix ;O |
