• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

DDS: Damage Phases and More!

Status
Not open for further replies.
Simply go through the map. There are 9 tutorials and 11 labs that cover everything that can be done with DDS.

This is being posted here to really show the power of multi-stage events and unit specific events.

Naysayers will say that events are just registered in correct order as they require each other. What of dynamic events that are registered/unregistered? They need to still be put into their correct positions. Without multiple stages in an event, they'll go to the end and everything will get screwed up. Without enough stages, they'll still be put into an awkward spot.

I hope people enjoy this new take on damage detection. I certainly worked a very long time on the architecture, API, and tutorials. I hope my work speaks for itself.
 

Attachments

  • Plugin Install Pack.w3x
    176.6 KB · Views: 105
Level 13
Joined
Mar 19, 2010
Messages
870
Hi Nestharus,

just a question. Why you spend so much time in this system in comparison to the current existing wc3 community? I mean, what do you think how many ppl will use this complex system? Are here teams using your DDS in their maps?

Reg.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
"Not to worry, it's also very user-friendly." Lying already

Do you think the API is user-friendly?

Yes?

call DDS.GlobalEvent.ON_DAMAGE_BEFORE.register(Condition(MyFunction)) seems like everything but user friendly.

Also module is nice, but do you think that each struct that wants a callback must implement the big fat boy again and again? this will make the map's script huuuuuge if I do implement DDS in 10 - 15 structs. The script itself has 7866 lines of code(the map), the script with 10 structs with implement DDS is 12087 lines of code.

Consider it
 
"Not to worry, it's also very user-friendly." Lying already

Do you think the API is user-friendly?

Yes?

call DDS.GlobalEvent.ON_DAMAGE_BEFORE.register(Condition(MyFunction)) seems like everything but user friendly.

Also module is nice, but do you think that each struct that wants a callback must implement the big fat boy again and again? this will make the map's script huuuuuge if I do implement DDS in 10 - 15 structs. The script itself has 7866 lines of code(the map), the script with 10 structs with implement DDS is 12087 lines of code.

Consider it

You'd be rt if you weren't so wrong. Once again, static ifs. When you use the module, only a tiny portion of the code is ever included. Rather than counting lines and goin zamg, you need to actually read it so that you understand that what you see for the line count is not what you get.

Remember, I'm a stickler on execution speed and line counts, so do you really think I'd do what you say I did without mentioning it?

@pred
There are a few teams, yes
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
I checked the output with all 3 functions

still 3 functions that do the SAME thing per struct is too much in my opinion

+ the fact that this MUST be used inside struct, while the others can be used inside scope with initializer is also quite big - imo
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
typical nestharus, jumping to assumptions and attacking people when someone opposes his work.

No, To be very specific I made this:

JASS:
library ShowJHOutput
   
    //! textmacro SHOW_JH_CODE
   
        private function a takes code c returns nothing
            call Filter(c)
        endfunction
       
        private function b takes nothing returns nothing
           
        endfunction
       
        private function c takes nothing returns nothing
            call a(function b)
        endfunction
   
    //! endtextmacro
   
    //! textmacro Expand takes string
    struct $string$
        private static method onDamageBefore takes nothing returns nothing
            
        endmethod
        
        private static method onDamage takes nothing returns nothing
            
        endmethod
        
        private static method onDamageAfter takes nothing returns nothing
            
        endmethod
        
        implement DDS
    endstruct
    //! endtextmacro
    
//! runtextmacro SHOW_JH_CODE()

    //! runtextmacro Expand("Struct1")
   
endlibrary

and checked the output, it had around 7 functions, and that is 1 struct, if I was to do //! runtextmacro Expand("Struct2"), the very same functions would be generated from your module. Yes those structs are identical, but lets say I have one for Shielding units, the other one for Printing damage, I will have 6-14 identical functions. That is massive waste of map space.

Imo this wastes a lot of space of the map that could've been used for other stuff, so this in my opinion is inferior to other DDSs,
1.) They dont require anything, yours requires around 20 another libraries, and no this is not C++ where you just write #include <lib.h> and you have it, you have to go and download it(this is even worse cause your faggotry removed everything from hive) whereas with the 2 other DDSs, I just copy the contents of the Jass box and Im good to go.
2.) as I mentioned, this massively wastes map space, because you keep generating the same stuff over and over again for each implementor
 
You're right. Not all of the methods need to be in the module. There is at least one that can be moved to the DDS library. This will properly make it so that only onOnit is in the module when declaring only global events. I kno you hate multiple stages for events and that you especially detest local events. Good news is that if you don't use them, they won't be there. The only extra overhead is local events when units are indexed and deindexed in the core of DDS. The events have to be made after all.

Please don't let these extra optional features ruin the system for you. I ask that you have an open mind. If you do the labs, you'll see where these features can be used.

I kno you hate dynamic code that molds to your needs. You prefer a one size fits all approach. The beauty of dynamic code is that you get exactly what you want.

If you know Trigger, you'll know that empty referenced Triggers add no overhead to execution.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
I think this is really interesting (especially the onDamageAfter functionality, as this has never been implemented reliable so far).

I don't really like the vast amount of code and complexity, but could you provide (in the initial post for example) a short abstract which summarizes the advantages of this system? Because tutorials/labs are nice if someone really wants to go into details of the system, but I think you should outline the characteristics of the system. Otherwise many people won't even look at it...

Essentially: Why should I use this system over other DDSs?
 
The onDamageAfter still runs before damage is applied. They all do. It's just the last event to run. onDamageAfter would be trivial to do the way you picture it, but it wouldn't be useful. This dds does not use timers :).

As i keep saying constantly, you can't count lines as that won't give you actual code. I write dynamic code with lots of static ifs, so to continue to propogate that the module is 700 lines of code is totally false.

An abstract is a good idea, but lots of people don't understand the need for features without working through it themselves. It'd just spawn pointless arguments, which i would summarily ignore since the tuts cover them.

More phases are good because you don't have to unify everything to maintain order. I can plug in various components without breaking anything. It's also all dynamic, so i can put stuff in and take stuff out whenever. This is especially useful with unit specific events. Imagine items, buffs, etc. You can apply them to 1 unit and make them work seamlessly with everything else. Your code will run in the correct order and be dynamic. You can learn more about damage phases by doing the tuts.

Want more? This can work brilliantly with an attack indexing system. Phases are also partitioned in such a way so as to properly support counter damage like thorns.

Even more? Some phases are local to the attacker while others are local to the defender. This lets you properly set your damage up, break it apart, and then apply it. See tutorials for more details.

You get all of this and a highly adaptive module that will give you whatever code you want. It has 8 possible code configurations.

What do other systems offer? Certainly, there is no comparison :)
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
I kno you hate dynamic code that molds to your needs. You prefer a one size fits all approach. The beauty of dynamic code is that you get exactly what you want.

I wonder where you've got this from. I have nothing against static ifs, its actually nice that most of the lines that are generated are blank, but this still doesnt defeat the truth of having exact copies of the same functions over and over again for every struct we implement DDS. You could argue that it is meant to be implemented only by 1 Handler struct, but you can never guarantee such a thing.

I also have no problem with global and local events, but I see no point in them, because I cant imagine someone giving a shit about this.

What do other systems offer? Certainly, there is no comparison :)

  • Ease of installing(neither of the 2 other DDSs(not counting Bribes cause its GUI) require any other library, while your requires around 20 additional libraries, and as I pointed out its even more pain because the resources are not so easily obtainable anymore if someone doesnt know you actually have Github repository)
  • code readablity(you would sacrifice your virginity for the 0.00001 ms difference in performance)
  • ease of use(you must actually go over 20 fucking labs and tutorials for your system to use, whereas I just look at the API of the other 2 DDSs and shoopdawhoop I magically know how to work with it)
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
As i keep saying constantly, you can't count lines as that won't give you actual code. I write dynamic code with lots of static ifs, so to continue to propogate that the module is 700 lines of code is totally false.

Well, even if I remove all the empty lines caused by those static ifs I still count more than 5000 lines of code for the whole system with all requirements. Plus the redundancy that is caused by implementing the module into different structs. If I implement it into 10 structs, it generates 70(?) functions for nothing. That is also one reason why I don't like the module approach as a replacement for function interfaces, for example. The other reason is that it is very hard for the user to get which parts of the code are now finally used and which not.

The onDamageAfter still runs before damage is applied. They all do. It's just the last event to run. onDamageAfter would be trivial to do the way you picture it, but it wouldn't be useful. This dds does not use timers :).

I know you don't use a timer, which in principle is great. Thats why a real after damage event would be possible in the first place (life changed events are fired immediately)... So why no real after damage events if you would have the possibility to do so?


An abstract is a good idea, but lots of people don't understand the need for features without working through it themselves. It'd just spawn pointless arguments, which i would summarily ignore since the tuts cover them.

Then give good examples to show the need for such features. I made the labs but I don't really see the big advantage of several features. In tutorial 3/lab 6 you say:

"The most exciting thing that can be done with DDS is unit specific events. Rather than giving a critical chance to all units, it can be tied with an item that enables critical chance for a specific unit."

But you can certainly do that with other DDS as well. Even worse, the user has to add such cryptic lines like

JASS:
private method onUnitIndex takes nothing returns nothing
	set ON_DAMAGE_OUTGOING = Trigger.create(false)
			
	call DDS(this).Event.ON_DAMAGE.register(Condition(function thistype.onDamage))
endmethod

Why not just filter the desired unit in a general onDamage event?
 
For th last time, local events require like 6 functions. If you use it like any other dds, global only, you don't get those functions. What is your point? Local events have a cost. If you don't like the cost, then don't use them. I don't get it.

Actual after damage events are useless and add overhead.

Your approach of just filtering wad used and scrapped. Imagine 250 events,and only 5 may be used for any given unit. The map would die. Local evwnts maximize performance in a major way.

I can remove 1 to 3 functions from the module. Two more if i make it slightly harder to use, but those are six lines i find to be worth it.
 
Level 10
Joined
Sep 19, 2011
Messages
527
I have nothing against static ifs, its actually nice that most of the lines that are generated are blank, but this still doesnt defeat the truth of having exact copies of the same functions over and over again for every struct we implement DDS.

the problem is that generally, people do the same thing but... manually.

other thing to realize is that number of lines pretty much do not matter since they have to be a lot to add considerable amount of size to the map.

Your approach of just filtering wad used and scrapped. Imagine 250 events,and only 5 may be used for any given unit. The map would die. Local evwnts maximize performance in a major way.

can you perform some benchmarks?
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
other thing to realize is that number of lines pretty much do not matter since they have to be a lot to add considerable amount of size to the map.

Well, its 177 kB (this system) against 37 kB (PDDS). This system has about 7500 compiled, real lines of code in debug mode, PDDS 562. And yes, I didn't just count the lines of the system but counted the final output without comments etc. This is quite bad for both the map size limit of 8Mb and the maximum amount of lines (which is a real problem for big maps, example).

Your approach of just filtering wad used and scrapped. Imagine 250 events,and only 5 may be used for any given unit. The map would die. Local evwnts maximize performance in a major way.

Thats a very unrealistic scenario. Why should I ever have 245 different, unit-specific onDamage events? And even if so, who prevents me from putting those checks into one onDamage handler?

JASS:
private function onSpcificUnitDamage takes nothing returns nothing
    if target == myUnit1 then
        call myUnit1_Damaged()
    elseif target == myUnit2 then
        call myUnit2_Damaged()
    // etc.
    endif
endfunction

Only one event with a few ifs. Performance impact is negligible.


Actual after damage events are useless and add overhead.

What overhead if I don't register a callback on them? And I can still do something like

JASS:
struct ClientStruct extends array
	private static method onDamageAfter takes nothing returns nothing
		set damage = 100 // Damage can still be modified here
	endmethod
    
    implement DDS
endstruct

struct MyStruct extends array
	private static method onDamageAfter takes nothing returns nothing
		call BJDebugMsg(R2S(damage))
	endmethod
    
    implement DDS
endstruct

So whats the point in having different damage phases if I still can't rely on the fact that all damage modifying client code will run before onDamageAfter? In my opinion, real after damage events would be a big pro for using this system.
 
All those checks can result in even more overhead, lol. Here is the real question... Why do you insist in using global events for local events instead of just using local events for local events. It's faster and easier. I don't get it. You don't get any misfires. It's like trying to eat soup with a knife. You can do it, but why would you when you have a spoon?

Many of the required libs, that code ur talking about, are core libs, meaning that they can be used by a lot. At this point you are starting to give a very biased assessment.

And ruke, i'm not going to benchmark 5 evaluated methods vs 250. That is silly.

And for your gurantee to be at the end, use phases and requirements. Client code will typically run after system code beecause client code usually isn't in a lib, and if it is, it usually requires the system code. If neither are true, you have bigger problems, lol. For one, modifying damage in a global phase, except for by a core system, is poor design :)
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
All those checks can result in even more overhead, lol. Here is the real question... Why do you insist in using global events for local events instead of just using local events for local events. It's faster and easier. I don't get it. You don't get any misfires. It's like trying to eat soup with a knife. You can do it, but why would you when you have a spoon?

And ruke, i'm not going to benchmark 5 evaluated methods vs 250. That is silly.

JASS:
scope DamageTest
	globals
		private constant integer UNIT_COUNT_PER_PLAYER = 250
	endglobals

	struct OnDamage1
		private static integer damageCounter = 0

		private method onDamage takes nothing returns nothing
			set damageCounter = damageCounter + 1
			if damageCounter > 1000 then
				set damageCounter = 0
				call BJDebugMsg("1000 Damage Events on units of player 0")
			endif
		endmethod

		private static method registerUnit takes unit u returns boolean
			local thistype this = UnitIndex[u]
			call enableDamageEventLocal()
			return false
		endmethod
	
		private static method onInit takes nothing returns nothing
			local integer i = 0
			local real x = 0.0
			local real y = 0.0
			local unit u
			
			loop
				exitwhen i > UNIT_COUNT_PER_PLAYER
				set u = CreateUnit(Player(0), 'hfoo', x, y, 0.0)
				call registerUnit(u)
				call CreateUnit(Player(1), 'edry', x, y, 0.0)
				if x > y then
					set y = y + 10
				else
					set x = x + 10
				endif
				
				set i = i + 1
			endloop
		endmethod
		
		implement DDS
	endstruct
endscope

JASS:
scope DamageTest
	globals
		private hashtable ht
		private constant integer UNIT_COUNT_PER_PLAYER = 250
	endglobals

	struct OnDamage1
		private static integer damageCounter = 0

		private static method onDamage takes nothing returns nothing
			if LoadUnitHandle(ht, GetHandleId(target), 0) != null then
				set damageCounter = damageCounter + 1
			endif
			if damageCounter > 1000 then
				set damageCounter = 0
				call BJDebugMsg("1000 Damage Events on units of player 0!")
			endif
		endmethod

		private static method onInit takes nothing returns nothing
			local integer i = 0
			local real x = 0.0
			local real y = 0.0
			local unit u
			
			set ht = InitHashtable()
			loop
				exitwhen i >= UNIT_COUNT_PER_PLAYER
				set u = CreateUnit(Player(0), 'hfoo', x, y, 0.0)
				call SaveUnitHandle(ht, GetHandleId(u), 0, u) 
				call CreateUnit(Player(1), 'edry', x, y, 0.0)
				if x > y then
					set y = y + 10
				else
					set x = x + 10
				endif
				
				set i = i + 1
			endloop
			call AddDamageHandler(function thistype.onDamage)
		endmethod
	endstruct
endscope

Result: On my PC both have the exact same framerate (~64 fps). Maybe with more units one can see a difference, but that doesn't matter: Even with such "few" units (250 footies vs 250 dryads) the fps drop heavily (to about 20 fps) if you move the camera to the fight. Even with the DDS completly disabled (it just makes no difference). So maybe its a very tiny bit faster, yes. But in which scenario should it make a difference? Its like making floating point division four times faster in a program where you spend 0.1% of your time doing floating point arithmetics, it just doesn't matter. Also remember that this benchmark example is constructed and very unrealistic.

I don't say its bad to have this, but for me it wouldn't be a reason to use this system.

Many of the required libs, that code ur talking about, are core libs, meaning that they can be used by a lot. At this point you are starting to give a very biased assessment.

I'm not biasing anything, I'm just stating facts: this system with all requirements needs about 7500 lines of code (nothing implemented yet!), which is a lot. If thats a problem depends whether the user wants to use the required libraries also for other things or not. Both possiblities are likely to happen. If he just wants a DDS and nothing else you can't deny its a disadvantage to require all those libraries.


And for your gurantee to be at the end, use phases and requirements. Client code will typically run after system code beecause client code usually isn't in a lib, and if it is, it usually requires the system code. If neither are true, you have bigger problems, lol. For one, modifying damage in a global phase, except for by a core system, is poor design :)

Now you are the one who is eating the soap with a knive. If I want an after damage event, why should I use a before damage event and not an after damage event in the first place? If I have to take care about where and in which order my onDamage handlers are implemented, I don't really see the advantage of the different damage phases. Because I can do the same thing with e.g. PDDS by just calling AddDamageHandler in the correct order.
 
No kidding they were the same speed. Your compared them 1 to 1. What you did was not a true scenario. You compared local events on all units to a global event. In that one rare extremw case that'll likely never happen, they are same. For a true test, put 5 local events on each unit for let's say 75 ranged units. We'll assume that each local event is unique. Next, make ur if statements to partition and do 375 events for your dds. When counter is a certain number, it'll be for a unit. You'd have 375 structs on each if you want a true representation. The is in the other extreme, and dds will win.

A true scenario would have some global events and lots of loc events, some being shared. Dds would atill win, lol.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
Ok, now lets get back to reality. Imagine I have 3 handlers for DDS(lets say 1 - 2 systems like Shield, and then my own for something) and I have bunch of units roaming the map fucking each other to face. Lets say around 50 vs 50 vs 50. Do you think that your overly complicated system is any better than the other, much simpler ones?

Also saying that real scenario will have global and local events is kind of off. To this date I dont really get your imagination or representation of Local vs Global events, I assume Ive never dealt with something like Local or Global events, just events, and Ive never had problem with any DDS
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
No kidding they were the same speed. Your compared them 1 to 1. What you did was not a true scenario. You compared local events on all units to a global event. In that one rare extremw case that'll likely never happen, they are same. For a true test, put 5 local events on each unit for let's say 75 ranged units. We'll assume that each local event is unique. Next, make ur if statements to partition and do 375 events for your dds. When counter is a certain number, it'll be for a unit. You'd have 375 structs on each if you want a true representation. The is in the other extreme, and dds will win.

A true scenario would have some global events and lots of loc events, some being shared. Dds would atill win, lol.

I don't get it... 75 units with 5 events on each? So 375 events in total for your DDS? And PDDS with 75 events (one for each unit) each with 5 ifs? I will test that later :D

The thing is that you still fail to point out the real advantages of this system. Thats why I said you should write an abstract or something, which you don't like to do, so let me do this for you:

This system doesn't use timers!

Why is this great? Because its the only way to avoid health value flickering on damage modification! All DDS that rely on timers can at most avoid health bar flickering, but not the numeric health value above the icon of a unit. Another advantage that comes along with not using timers is, that it is always safe to use the GetWidgetLife native - no need for something like GetUnitLife.

Further you could provide relyable afterDamage events with this technique, which would make a clean seperation between onDamage events (for modification) and afterDamage events (for displaying damage) without requiring the user to take care that his afterDamage events are really fired after all onDamage events.

Those are the two really cool things of this system, not the 5 nanoseconds you safe by using local events. Those are really good things IMO. The bad thing is especially the coding style which makes the code nearly unreadable:

JASS:
private static method registerUnit takes unit u returns boolean
    local thistype this = UnitIndex[u] 
    call enableDamageEventLocal()
    return false
endmethod

Seriously? Calling an instance method like a static method by faking the this pointer?
 
Status
Not open for further replies.
Top