WC3 never stops to surprise me

In order to support Octarine Orb's spell lifesteal (and overall increase accuracy attack detecting system) I had to move all hardcoded spells into wrappers. Dummies throwing spells being perfect way to detect if damage is magical — just look for Aloc.

Everything was fine — simple triggers, nothing complex. Every hardcoded shockwave being copied and used as dummy-wave: original wave deals 0 damage and no animation, dummy wave provides damage and visuals. These changes affects Lina, Banshee and Magnus in first place.

There were few reports about failed shockwave casters. Only few — and that caused me to think it's minor bug, which cannot be reproduced in any set I've tried. I did know that shockwave cannot be casted on cliffs, so my best guess was — somehow people break my system, casting waves right into cliifs. But once map hit RGC, things went crazy — amount of reports about broken waves rised as Spartacus in da best times. Even more — waves didn't work even on flat terrain. That what I can call catastrophic issue.

One more thing — there were quite few reports of SF's Requiem of Souls being broken as well. If you remember, it's based on Shockwaves too. But let's dig things out.

Trying to start
Reported bug cannot be reproduced in single player — no matter how i tried, nothing could break my dummies. Literally, system covers 99.99% cases, and the only reason I left 0.01% — it's because of potential hardcoded issues.

Very soon my mate Keller found out, that every reported game with broken waves had Techies inside. Fast tests gave us wonderful resulst:
  • if Techies being picked before any wave ability being casted, dummy will never cast the wave;
  • if wave was casted at least once before Techies pick, everything was fine.

Meanwhile let me present you my dummy wave system, it's very simple:

function DummyWave takes unit d, string order, real x, real y returns nothing
	local real cx=GetUnitX(d)
	local real cy=GetUnitY(d)
	local real a=AngleBetweenXY(cx,cy,x,y)*bj_DEGTORAD
	local real cosa=20*Cos(a)
	local real sina=20*Sin(a)
	local integer i=0
	call echo("dummy wave")
	loop
		exitwhen IssuePointOrder(d,order,x,y) or i>20
		call SetUnitX(d,GetUnitX(d)+cosa)
		call SetUnitY(d,GetUnitY(d)+sina)
		call echo("dummy wave #"+I2S(i)+" failed at x="+R2S(x)+" y="+R2S(y)+" from x="+R2S(GetUnitX(d))+" y="+R2S(GetUnitY(d)))
		call SaveReal(TempHash,'wave','000x',x)
		call SaveReal(TempHash,'wave','000y',y)
		set x=x+cosa
		set y=y+sina
		set i=i+1
	endloop
	if i>20 then
		call UnitRemoveAbility(d,'Avul')
		call IssueTargetOrder(d,order,d)
	endif
	//call echo("fired at "+I2S(i))
endfunction


So target point (given x/y) always being the point player ordered to cast spell. Order for all dummy waves if "carrionswarm" — no ground deformation, perfect choice. If spell casting fails anyhow it starts to move dummy. And guess what — all 20 steps reported «failed». I've added different selectable dummy to check where is the problem. Wonderful — dummy being unable to cast spell. But if I am select him and order exactly the same things manually he succesfully casts it!

I've tried everything, and even added last lines — after 20 failed steps system desperately asks dummy to cast wave at least on himself. And guess why — it actually helped, ability being casted on himself successfully. WTF?

Ok, let's return to Techies. What can be broken here? Disabling his init code one by one, I found the reason — Suicide trigger. There were enabling and disabling mechanic, which helps to prevent possibility of suicide while Techies is in Wraith Form (Leoric's Aghanim). It's based on requirements for Suicide ability, where required unit is dummy, which being created on Techies enter on the map. I've selected 'e00K' — old KotL's Wisp. It was completely blind select, nothing special.

When I changed unit's ID for another one just to see if it's about dummy's type, waves started to work again. So, 'e00K' break all dummy units on the map now and forever, preventing them from using «carrionswarm». How's that?

There no mention of 'e00K' left in the code. Either this unit cannot enter the map anyhow else. So let's see his ability pack — I didn't clean it up. One by one i found, that only if ability 'A10V' equipped, dummies become retarded. Even simplier — if 'A10V' enters the map, regardless units, and no waves were casted before, dummy won't be able to cast any «carrionswarm»-wave.

'A10V', Blinding Light, SLK data:

C;X2;K"ANcl"
C;X10;K1
C;X11;K1
C;X19;K20
C;X20;K50
C;X22;K500
C;X25;K1
C;X28;K"carrionswarm"


Nothing special, typical ANcl with pretty default settings:
  • X24 = 0 -> immediate (no target);
  • X25 = 1 -> visible on panel;
  • X28 = order = «carrionswarm».

So, only thing which looks bad here is order. Let's see. IssuePointOrder reported false in return — dummies couldn't use wave. But if we reverse order of entering on the map (any wave and then A10V) everything works, even A10V.

What could it be? Im already aware of WC3 caching unit's data on spell learning. This feature being used to alter unit's cast point or backswing just for single spell instead of adding cast time to them. So it's pretty clean that Blizz also used some cache technique for abilities as well. And there are strange issue with working on ANcl — for some reason it may take a placeholder in Game.dll, becoming the standard for all abilities with this order. Huh? So game thinking, that «carrionswarm» is immediate spell and refuse to cast it on point. 'Cause this ability said so. But what about selfcast I mentioned before? Will discuss that later, now let's check out our theory.

Theorycraft III
Let's see. Abilities being cached. Let's check out who else, beside Techies, could break waves in my map right now:
  • two more Blinding Light-es from two other KotL's wisps;
  • one unused old Shadowraze spell;
  • Storm Spirit's ultimate.

What? Storm being able to shutdown all waves and SF's ultimate? Cannot be, I've seen Storm vs SF dozen times, no way SF will get chance to cast his dummy waves before Storm learning first level of ability. And only when I checked dump of 83c, I remembered — Storm's ulti changed in my map due to it's base ability.

Notice: Ball Lightning based on Carrion Swarm in Frogo doto. It means you can't cast it on cliifs and only allowed targets are valid target — casting on youself won't work, f.i. So I had to change it, making Storm completely free to chose where to go, changing base ability to ANcl and keeping old order just in case.

So, back to topic: why dummy was able to cast spell on himself?

Making Lotus Orb, which have to reflect all targeted spells back onto caster, I found that almost every non-target (immediate) spell is using caster as target in game's engine. So GetTriggerUnit == GetSpellTargetUnit. It's true for Fan of knives, and for every Channel-based immediate ability. Can't say more about hardcoded immediate abilities, but I remember some of them didn't follow this mechanic, so it depends. And, basing on this idea, immediate orders can be successfully ordered on target == caster, and engine won't see any issues with it.



Conclusions
  • WC3 using some kind of caching for abilities when they enters the map, which helps to handle Issue*Order JASS;
  • caching conditions are bad and allows ANcl to preset order's behaviour;
  • do never use random dummy. Or at least remove his spells -_-

But we can go deeper!
Is it only that specific problem with carrionswarm? Is only this order being fucked in conditions or there are more?

Let's try to change targeting type of 'A10V' from immediate (0) to land or unit (3), just like default carrion swarm. Result:
  • no wave being casted by dummy;
  • IssuePointOrder returned true.

Well fuck. Engine believes he fulfilled the ability's requirements, but in fact he didn't.

Keep rolling. Back to immediate type, let's check 'bloodlust' order. It's being used on Rabid and Multicasted Bloodlust on non-dummy. So it may be broken too? Nope: dummies successfully used bloodlust on targets, no issues at all.

So TargetOrder kinda immune to the spoofing.
Notice: when I was translating this article I've got hit about possiblity of that Bloodlust came out earlier ingame so my test was failed from the start. So you may check it out on your custom maps just to see if am I right or not.

Let's back to Point orders, here we go with "rainoffire", which is used for Pit Lord's ability. Result: no waves, "false" returned.

We almost done. I wanted to check what would happen if we spoof «move», but it obviously being preloaded much earlier than I can add this ANcl to anything, so no luck here.

Last one — is order "dreadlordinferno", used for Warlock's Aghanim' second Infernal. As we expected, «false» returned, no secondary Golem.

Now change target type of our spoofer to "unit only". Nothing changd, still no Golem. But on target type = ground or "ground or unit" order went succesfull — Infernal spawned just as normal. So caching is up to targeting type as well?

Back to «rainoffire» — succesfully casts on «ground»/«ground or unit» targets for 'A10V'.

Overall: ANcl with X24 (target) being equal to «no target» or «unit only» may be cached by engine as default behaviour, breaking every dummy using the same order.



Discovering this bug and related things helped out not only to fix my dota, but LoD as well. It was commong bug for 2 abiliites here — March of Machines and Midnight pulse. Now they can play in peace.

Из России с любовью

0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.