We just reached 30,000 articles on this wiki! 🥳
If you appreciate the work done within the wiki, please consider supporting The Cutting Room Floor on Patreon. Thanks for all your support!

Hexen II

From The Cutting Room Floor
Jump to navigation Jump to search

Title Screen

Hexen II

Developer: Raven Software[1]
Publisher: id Software[1]
Platform: Windows
Released internationally: March 27, 1998[1]


AnimationsIcon.png This game has unused animations.
AreasIcon.png This game has unused areas.
CodeIcon.png This game has unused code.


ProtoIcon.png This game has a prototype article
PrereleaseIcon.png This game has a prerelease article

Careful, you'll lose an eye.
This page or section needs more images.
There's a whole lotta words here, but not enough pictures. Please fix this.

Hexen II drags the Heretic/Hexen series into the world of proper polygon-based 3D, kicking and screaming. D'Sparil and Korax may have been destroyed, but the last Serpent Rider, Eidolon, continues to rule the world of Thyrion, with the Four Horsement of the Apocalypse doing his bidding. It's up to a quartet of heroes to get hopelessly lost and curse out switch puzzles save the day!

Sub-Page

Read about prototype versions of this game that have been released or dumped.
Prototype Info
Read about prerelease information and/or media for this game.
Prerelease Info

Unused Code

pr_cmds.c - Random number functions

PF_random, PF_randomrange, PF_randomvalue, PF_randomvrange and PF_randomvvalue are builtin functions to allow returning a random number or vector based on the number of parameters. Of these functions, only PF_random is available to the hcode interpreter.

Even though PF_random is accessible, the hcc utility has random as an intrinsic function that takes 0, 1 or 2 parameters overriding the definition found in builtin.hc, and replacing it with a single opcode that is interpreted for the same effect. As such, PF_random is also unused, and also explains why the random function doesn't behave in the way implied from its definition.

bldrain.hc - Blood Fire

There's a commented out particle effect in bloodmissile_impact_effect. If enabled (and the explosion sprite is hidden), it shows red/yellow particles that quickly turn black. This is the only time PARTICLETYPE_DARKEN would otherwise been referenced.

Combat.hc - FireMelee

Combat.hc has two functions, where FireMelee is the important one.

if(trace_ent.flags2&FL_ALIVE&&!infront_of_ent(self,trace_ent)&&self.playerclass==CLASS_ASSASSIN&&
	self.weapon==IT_WEAPON1&&random(1,10)<self.level)
{
	CreateRedFlash(trace_endpos);
	damage_base*=random(2.5,4);
	backstab=TRUE;
}

damg = random(damage_mod+damage_base,damage_base);
SpawnPuff (org, '0 0 0', damg,trace_ent);
T_Damage (trace_ent, self, self, damg);
if(!trace_ent.flags2&FL_ALIVE&&backstab)
{
	centerprint(self,"Critical Hit Backstab!\n");
	AwardExperience(self,trace_ent,10);
}

The two if statements surrounding the used code handle the assassin. The assassin gets a random chance of performing a critical hit (10% at level 2, +10% per level), which does *2.5-4 damage. If the target is killed, it also awards a minor experinece boost. This code will never get executed because the punch dagger doesn't use the FireMelee function, instead applying the damage directly within fire_punchdagger found in punchdgr.hc. The version in the used function only activates at level 6 or higher, increases the base damage before the damage multiplier, and awards experience per backstab.

	// Necromancer stands a chance of vampirically stealing health points
	if (self.playerclass == CLASS_NECROMANCER) 
	{
		if  ((trace_ent.flags & FL_MONSTER) || (trace_ent.flags & FL_CLIENT))	
		{
			chance = self.level * .05;
				
			if (chance > random())
			{
				point_chance = self.level;
				point_chance *= random();
				if (point_chance < 1)
					point_chance = 1;

				sound (self, CHAN_BODY, "weapons/drain.wav", 1, ATTN_NORM);

				self.health += point_chance;
				if (self.health>self.max_health)
					self.health = self.max_health;
			}
		}
	}

The Necromancer can steal health in the same function. Attacking with melee gives 5% per level chance of gaining a random amount of health. However, the health theft is handled through sickle_fire from sickle.hc. The used version instead has an adjusted chance formula (4% per level starting at level 6, cap at 20%), and heals a fixed amount (2 per level starting at level 6, cap at 10).

Icemace.hc - Crusader Ice Mace

Commented out:

/*
void shard_hit (void)
{
	if(other.classname=="blizzard shard")
		return;

	if(other.takedamage&&other.health&&other!=self.owner)
		T_Damage(other,self,self.owner,50*self.scale);
	sound(self,CHAN_AUTO,"crusader/icewall.wav",0.1,ATTN_NORM);
	particleexplosion(self.origin,14,20,5);
//	particle2(self.origin,'-10 -10 -10','10 10 10',145,14,5);
	remove(self);
}
*/

/*
void FireShard (void)
{
local vector org,dir;
	
		newmis=spawn();
		newmis.movetype=MOVETYPE_BOUNCE;
		newmis.solid=SOLID_TRIGGER;
		newmis.owner=self.owner;

		dir_x=random(50,100);
		dir_y=random(50,100);
		dir_z=random(-250,-180);

		org_x= random(-84,-8);
		org_y= random(-84,-8);
		if(org_x<64)
			org_z=64+org_x;
		else if(org_y<64)
			org_z=64+org_y;
		org_x+=self.origin_x;
		org_y+=self.origin_y;
		org_z+=self.origin_z+64;
		traceline(self.origin,org,TRUE,self);
		org=trace_endpos;

		newmis.velocity=dir;
		newmis.angles=vectoangles(newmis.velocity)+'90 0 0';
		newmis.scale=random(0.05,0.55);
		newmis.skin=0;
		newmis.frame=0;
		newmis.touch=shard_hit;

		setmodel(newmis,"models/shard.mdl");
		setsize(newmis,'0 0 0','0 0 0');
		setorigin(newmis,org);
}
*/	

These would fire shards that would do between 2.5 to 27.5 damage and would ricochet off walls, compared to the 10 damage per shot from the current weapon which don't ricochet (aside from hitting already frozen targets).

Newimp.hc - Imps

Found in the source code, but not compiled into the progs.dat file. The file's header implies that it would be for the ice imp, and contains monster AI code that was moved to a different file.

Vorpal.hc - Vorpal sword

Commented out is a rudimentary implementation of reflecting projectiles, now handled by the Ring of Turning. This version simply causes projectiles to be reflected, while the ring of turning also includes support for other projectile types and partial handling for deathmatches.

/*
============
Deflect missiles
============
*//*
void vorpal_downmissile (void)
{
	vector  source,dir;
	entity  victim;
	float chance;
	entity hold;

	if (!self.artifact_active & ART_TOMEOFPOWER)
		return;

	victim = findradius( self.origin, 150);
	while(victim)
	{
		if ((victim.movetype == MOVETYPE_FLYMISSILE) && (victim.owner != self))
		{
			victim.owner = self;
			chance = random();
			dir = victim.origin + (v_forward * -1);
			CreateLittleWhiteFlash(dir);
			sound (self, CHAN_WEAPON, "weapons/vorpturn.wav", 1, ATTN_NORM);
			if (chance < 0.9)  // Deflect it
			{
				victim.v_angle = self.v_angle + randomv('-180 -180 -180', '180 180 180'); 

				makevectors (victim.v_angle);
				victim.velocity = v_forward * 1000;
			}
			else  // reflect missile
				victim.velocity = '0 0 0' - victim.velocity;
		}
		victim = victim.chain;
	}
}
*/
(Source: Hexen II vorpal.hc:465)

Function vorpal_normal_fire has an unused variable damage_flg, which alters the expected mana cost of the weapon. This variable is set if the vorpal sword inflicts damage from its area attack, to ensure that 2 blue mana is only spent if it damages something. However, the actual check to determine mana cost is checked against trace_end.flags & FL_MONSTER, which means the paladin expends 2 mana if the last entity checked was a monster, rather than if any monster was hit. Also of note is damage_flg activates on any damageable rather than just monsters.

(Source: Hexen II vorpal.hc:512)

Function launch_vorpal_missile is only called from vorpal_tome_fire if the player has at least 4 blue mana. Because of this, launch_vorpal_missile will not create a half-damage vorpal missile due to the player always having more than 4 blue mana. This cascades into not being able to create a half-damage shockwave.

World.hc - world setup

Hexen II has a low gravity level similar to Quake's secret level, but it wasn't included in the game. Also of note is r_ambient, which isn't present in the GL port and isn't reset when the map is changed even when it's available.

	if (self.model == "maps/mgtowers.bsp")
	{
		cvar_set ("sv_gravity", "100");
		cvar_set ("r_ambient", "36");
	}
	else
		cvar_set ("sv_gravity", "800");

Unused Maps

Thomas - Intro

Launching this map goes through the intro for the expansion pack, but the scripting doesn't work that well if you play the map directly.

Instead, it's better to view this by typing playdemo t9 in the console, a demo that otherwise doesn't play automatically.

Monsters - "THE PIT"

Found in the expansion pack, Portal of Praevus. This is a ten room map, which appears to test how certain monsters behave in different terrain types. Such terrain includes mazes, either with sloped or slanted walls, pits with an exit ramp, a tower with a ramp, and so on.

Of these rooms, three of them are slightly interesting. One room has a switch, when pressed, causes three enemies to fight among themselves. To perform this infighting, there's hardcoding in ai.qc that allows the monsters to target creatures with a specific designation, complete with comments referring to this level as "THE PIT!"

The room with a lava separator has a small sheep, known as "Nate 9000", which prints out received damage to the console (requires developer 1 in console).

The final room is preceded by the full set of player weapons. When entered, it releases three batches of monsters all at once, before closing the player in the arena. There's no obvious way to escape this arena, as there doesn't seem to be a chaos device that allows for escape.

Unused Animation

Gauntlet

The Paladin's Gauntlet has two unused frames, $7thGnt1 and $7thGnt2, which appear to be rotated versions of $7thGnt3 to wind up to an attack.

Axe

The Paladin's Axe has one unused frame, $1stAxe27. This frame is part of the series where the axe is about to return to position, but this frame in particular seems to have the axe go further to the right from the default rest position, where as frame $1stAxe25 appears to be a normal transition.

Warhammer

When the crusader swings the warhammer, it's chosen by rint(random(1,3)) to handle three choices. These are two mistakes in a row - the first is the random function returns a value above 1.0 and below 3.0, and thus would normally handle only two functions. The function rint would save the day to round to the nearest integer, but that function has a flaw where it only rounds up if the fractional part is 0.9 or higher. As such, the Left-to-Right swing is used but much more rare than what should be expected.

What isn't used in the Left-to-right swing are three wind up frames.

Frame LotR4 Frame LotR5 Frame LotR6
HexenII Warhammer lotr4.png HexenII Warhammer lotr5.png HexenII Warhammer lotr6.png

Two frames at the end of the right-to-left swing are present. They're disjoint from adjacent animation frames, where the swing is completed, and where the hammer returns to standard position.

Frame RtoL12 Frame RtoL13
HexenII Warhammer rtol12.png HexenII Warhammer rtol13.png

There is an unused vertical swing animation, consisting of 10 frames. Only eight of these frames have visible content on-screen, the last two has the weapon tilted too far down to be visible.

Hack 1 Hack 2 Hack 3 Hack 4
HexenII Warhammer hack1.png HexenII Warhammer hack2.png HexenII Warhammer hack3.png HexenII Warhammer hack4.png
Hack 5 Hack 6 Hack 7 Hack 8
HexenII Warhammer hack5.png HexenII Warhammer hack6.png HexenII Warhammer hack7.png HexenII Warhammer hack8.png

References