buddha_die() cleaned up and fixed by Thomas Freundt:

From: "Ozkan Sezer" <sezeroz@gmail.com>
[...]
While browsing the forums, I ran into this bug report:
http://quakeone.com/forums/quake-talk/other-games/5783-hexen-ii-community-work-discussion-59.html
... and I actually reproduced it.  On the console, do

playerclass 2  (to make it crusader)
map tibet10
impulse 43  (to get everything)
god

... then go and face praevus. Always use the ice mace in tome
of power mode and always keep a summoned imp around,
and then spam praevus with a lot of blizzard.

Result: the finale screen doesn't trigger.

[...]

From: "Thomas Freundt" <thf_ebay@yahoo.de>
To: "Ozkan Sezer" <sezeroz@gmail.com>
Subject: Re: praevus bug??
Date: Mon, 18 Apr 2011 09:07:02 +0200

Hi Ozkan

I think I found the reason for the bug. The following entity is assigned
to Praevus:

{
"classname" "monster_buddha"
"target" "t22"
"targetname" "t21"
"netname" "t51"
"angle" "0"
"origin" "-2720 1248 1456"
}

In 'buddha.hc' there is a function called 'buddha_run':

 if (self.health < self.max_health * (1 / 5) && self.dmgtime == 3)
 {
  self.dmgtime+=1;
  SUB_UseTargets();
  self.target = self.netname;
  self.think = buddha_recharge;
  thinktime self : 0.1;
  return;
 }
 else if (self.health < self.max_health * (2 / 5) && self.dmgtime == 2)
 {
  self.dmgtime+=1;
  SUB_UseTargets();
  self.target = self.targetname;
  self.think = buddha_recharge;
  thinktime self : 0.1;
  return;
 }

When Praevus' health drops to less than 40% SUB_UseTargets() fires
the standard "target" being "t22" which leads to the destruction of
the first pillar; then self.target is latched to self.targetname
which resolves to "target" "t21". Hence, when health drops to less
than 20% the second pillar is destroyed and self.target is latched
to self.netname which is equivalent to "target" "t51". When Praevus
dies 'monster_death_use' issues another call to SUB_UseTargets()
which activates the trigger_once entity with "targetname" "51" and
thereby initiates the final sequence with the flying skulls:

{
"classname" "trigger_once"
"target" "t52"
"targetname" "t51"
"delay" "6.5"
"model" "*13"
}

This is how it should work. However, only one function of Praevus'
behavioral repertoire can be active at a time and inserting debug
messages into 'buddha_run' reveals that this function is only active
during certain time slots; in the meantime Praevus can take considerable
damage or even die. This is nicely demonstrated when one quickly drains
his health level to near-death, then ceases fire, and only watches the
scenery. The lag causes the first pillar to fall asunder, invoke a
recharge sequence, followed by the second pillar being destroyed and
yet another recharge sequence.

If Praevus dies without execution of the <40% health if clause, the
default "target" "t22" is triggered by 'monster_death_use'; in other
words, the first pillar is always destroyed. The assignment
'self.target = self.netname' is also found in 'buddha_die', but this
function is called *after* 'monster_death_use' so the flying skulls
sequence never triggers.

To fix this bug one only has to make another call to SUB_UseTargets()
before Praevus' entity removes itself in 'buddha_die':

doWhiteFlash();
SUB_UseTargets();
remove(self);

Better still we remove 'self.target = self.targetname' from 'buddha_run'
not only to ascertain the function is called only once (calling it twice
shouldn't display any ill side effects, though; then again, I prefer not
to rely on assumptions :-), but also to have more accurate control of
timing. The default delay of 6.5s is now too long. To remedy this issue
we can either reduce delay to <1s by altering tibet10.ent or call
SUB_UseTargets about 6s before Praevus is removed:

if (self.count >= 4 && self.count < 4.1)
    SUB_UseTargets();

 if (self.count >= 10) 
 { ...

(There seems to be some rounding up with floating point, so
'if (self.count == 4)' is never executed)

~
ThF

[...]

After having completed the MP by defeating by far the toughest
adversary, waiting more than 6s without anything happening seems
somewhat irritating to me. For obvious reasons altering the entity
file isn't really an option.

As soon as 'buddha_die' sets self.count to 0, the function is called
another 100 times with 0.1s intervals. So the perfect point in time
to trigger the final sequence with its inherent 6.5s delay would be
when self.count is 3.5. I now use a prog.dat that has both original
'self.target = self.netname' statements commented out and instead
inserted

 if (self.count >= 3.5 && self.target != self.netname)
 {
   self.target = self.netname;
   SUB_UseTargets();
 }

before the statement originally at line 1087

if (self.count >= 10)
{ ...

This gets rid of floating point uncertainty and ensures that it is
only activated once with the right timing. I have tested this many
times, it always worked.

[...]

> I see. To confirm, it also is fine if one plays "decently", ie.
> without the Crusader's ice spam, yes?

The only error that can still occur with this amendment is that the
second pillar never breaks; but I find this rather unimportant.

> And do you mean that if you do the above changes, the SUB_UseTargets()
> call just after doWhiteFlash() is not needed?

Exactly.

> BTW, the if (self.count >= 10) {...}  construct contains some weird
> unnecessary if checks, such as the if (self.count == 3) check, as
> well as if (self.count == 0) check after the self.count = 0
> assignment.  I guess they can use a good cleanup while we are there.

I think so, too. It looks like a duplicate pasted mistakenly.


--- buddha.hc.orig
+++ buddha.hc
@@ -958,7 +958,7 @@
 	{
 		self.dmgtime+=1;
 		SUB_UseTargets();
-		self.target = self.netname;
+	//	self.target = self.netname; // see notes by Thomas Freundt below
 		self.think = buddha_recharge;
 		thinktime self : 0.1;
 		return;
@@ -1050,7 +1223,7 @@
 	entity found;
 	vector new_origin;
 
-	self.target = self.netname;
+//	self.target = self.netname;
 
 	if (self.think != buddha_die)
 	{
@@ -1065,7 +1238,7 @@
 
 	if (self.cnt == 0)
 	{
-		sound(self, CHAN_VOICE, "buddha/die.wav", 1, ATTN_NONE);		
+		sound(self, CHAN_VOICE, "buddha/die.wav", 1, ATTN_NONE);
 		found=find(world,classname,"player");
 		while(found)
 		{//Give them all the exp
@@ -1081,59 +1254,37 @@
 		self.velocity='0 0 0';
 	}
 
+	if (self.count >= 3.5 && self.target != self.netname)
+	{
+		self.target = self.netname;
+		SUB_UseTargets();
+	}
+
 	//thinktime self : self.rider_death_speed;
 	self.rider_death_speed += 0.1;
 
-	if (self.count >= 10) 
+	if (self.count >= 10)
 	{
-		if (self.count == 3)
-		{
-			beam = spawn();
+		self.count = 1;
+		self.effects (-) EF_BRIGHTLIGHT;
+		self.effects (+) EF_NODRAW;
+		if (self.movechain != world)
+			self.movechain.effects (+) EF_NODRAW;
 
-			new_origin = self.origin + '0 0 50';
+		//thinktime self : 0.05;
 
-			setmodel(beam,"models/boss/circle.mdl");
-			setorigin(beam,new_origin);
+		entity search;
 
-			setsize (beam, '0 0 0', '0 0 0');		
-			beam.owner = self;
-			beam.movetype = MOVETYPE_FLYMISSILE;
-			beam.solid = SOLID_NOT;
-			beam.drawflags = SCALE_TYPE_UNIFORM;
-			beam.scale = .1;
-			beam.skin = 0;
-			beam.avelocity = '0 0 300';
-			beam.think = circle_think;
-			thinktime beam : HX_FRAME_TIME;
-			//self.count = 13;
-		}
-
-		self.count = 0;
-		
-		if (self.count == 0)
+		search = find(world, classname, "rider_temp");
+		while (search != world)
 		{
-			self.count = 1;
-			self.effects (-) EF_BRIGHTLIGHT;
-			self.effects (+) EF_NODRAW;
-			if(self.movechain!=world)
-				self.movechain.effects (+) EF_NODRAW;
-			
-			//thinktime self : 0.05;
-
-			entity search;
-
-			search = find(world, classname, "rider_temp");
-			while (search != world)
-			{
-				remove(search);
-				search = find(search, classname, "rider_temp");
-			}
+			remove(search);
+			search = find(search, classname, "rider_temp");
+		}
 
-			doWhiteFlash();
-			remove(self);
+		doWhiteFlash();
+		remove(self);
 
-			return;
-		}
 		return;
 	}
 	else
@@ -1152,7 +1303,7 @@
 			setmodel(beam,"models/boss/circle.mdl");
 			setorigin(beam,new_origin);
 
-			setsize (beam, '0 0 0', '0 0 0');		
+			setsize (beam, '0 0 0', '0 0 0');
 			beam.owner = self;
 			beam.movetype = MOVETYPE_FLYMISSILE;
 			beam.solid = SOLID_NOT;
@@ -1174,7 +1325,7 @@
 		setmodel(beam,"models/boss/shaft.mdl");
 		setorigin(beam,new_origin);
 
-		setsize (beam, '0 0 0', '0 0 0');		
+		setsize (beam, '0 0 0', '0 0 0');
 		beam.owner = self;
 		beam.drawflags = SCALE_ORIGIN_BOTTOM | SCALE_TYPE_XYONLY;
 		beam.movetype = MOVETYPE_NOCLIP;

