Jump to content
Search In
  • More options...
Find results that contain...
Find results in...
ETTiNGRiNDER

Hexen's Mystic Ambit Incant a bugged item?

Recommended Posts

So, this has been bothering me ever since I looked into the source code to make sure the effects were documented properly on the Doom Wiki, and given that what I posted on the talk page there never got a response, I'll bring it up here.

 

As you might or might not know, the Mystic Ambit Incant is an artifact that appears in co-op and serves to give every member of the party a bonus when it's activated, so long as everyone is close enough to the caster.  It has a differing effect depending on which class is activating the artifact, as follows:

 

Fighter: gain armor (1 point)

Mage: gain blue mana, or green mana if and only if blue mana was already full (random amount 50 to 99)

Cleric: gain health (random amount 50 to 99)

 

Players have sometimes commented on the unbalanced nature of these effects, and particularly that the Fighter's is nearly useless especially when compared to the others, while the Cleric's is often regarded as the best.  Now it wouldn't be the only case where making each class unique has resulted in odd balance between them, but what if it wasn't meant to be that way?

 

Let's look at the code for activating one (I'm using the official Raven release of the code as reference here, where it appears in P_USER.C.)

// Do class specific effect for everyone in radius
boolean P_HealRadius(player_t *player)
{
    mobj_t *mo;
    mobj_t *pmo=player->mo;
    thinker_t *think;
    fixed_t dist;
    int effective=false;
    int amount;

    for(think = thinkercap.next; think != &thinkercap; think = think->next)
    {
        if(think->function != P_MobjThinker)
        { // Not a mobj thinker
            continue;
        }
        mo = (mobj_t *)think;

        if (!mo->player) continue;
        if (mo->health <= 0) continue;
        dist = P_AproxDistance(pmo->x - mo->x, pmo->y - mo->y);
        if(dist > HEAL_RADIUS_DIST)
        { // Out of range
            continue;
        }

        switch(player->class)
        {
            case PCLASS_FIGHTER:        // Radius armor boost
                if ((P_GiveArmor(mo->player, ARMOR_ARMOR, 1)) ||
                    (P_GiveArmor(mo->player, ARMOR_SHIELD, 1)) ||
                    (P_GiveArmor(mo->player, ARMOR_HELMET, 1)) ||
                    (P_GiveArmor(mo->player, ARMOR_AMULET, 1)))
                {
                    effective=true;
                    S_StartSound(mo, SFX_MYSTICINCANT);
                }
                break;
            case PCLASS_CLERIC:            // Radius heal
                amount = 50 + (P_Random()%50);
                if (P_GiveBody(mo->player, amount))
                {
                    effective=true;
                    S_StartSound(mo, SFX_MYSTICINCANT);
                }
                break;
            case PCLASS_MAGE:            // Radius mana boost
                amount = 50 + (P_Random()%50);
                if ((P_GiveMana(mo->player, MANA_1, amount)) ||
                    (P_GiveMana(mo->player, MANA_2, amount)))
                {
                    effective=true;
                    S_StartSound(mo, SFX_MYSTICINCANT);
                }
                break;
            case PCLASS_PIG:
            default:
                break;
        }
    }
    return(effective);
}

P_GiveArmor, P_GiveMana, and P_GiveBody are in P_INTER.C and are mostly pretty straightforward - return true if the effect happened, or false if it was blocked due to the player in question being maxed out.  This serves as a safeguard to prevent burning the artifact when it would have no effect.

 

You might want to look at P_GiveArmor if you're following along with the code, since it has the most things going on, but essentially Incant armor, like Dragonskin Bracers armor, is done as what I'll call "magic armor", in other words while it's done as a plus to the individual armor pieces it only cares about the class maximum, not the piece maximum (so for instance a Fighter could have Mesh Armor with 17 points of armor in it, despite its usual maximum for a Fighter being 5, and as I recall this is exactly what happens if a Fighter casts an Incant on himself enough times).

boolean P_GiveArmor(player_t *player, armortype_t armortype, int amount)
{
	int hits;
	int totalArmor;

	extern int ArmorMax[NUMCLASSES];

	if(amount == -1)
	{
		hits = ArmorIncrement[player->class][armortype];
		if(player->armorpoints[armortype] >= hits)
		{
			return false;
		}
		else
		{
			player->armorpoints[armortype] = hits;
		}
	}
	else
	{
		hits = amount*5*FRACUNIT;
		totalArmor = player->armorpoints[ARMOR_ARMOR]
			+player->armorpoints[ARMOR_SHIELD]
			+player->armorpoints[ARMOR_HELMET]
			+player->armorpoints[ARMOR_AMULET]
			+AutoArmorSave[player->class];
		if(totalArmor < ArmorMax[player->class]*5*FRACUNIT)
		{
			player->armorpoints[armortype] += hits;
		}
		else
		{
			return false;
		}
	}
	return true;
}

 

So what's fishy here?  In the if statements, the Fighter effect is instructed to try P_GiveArmor four times (once for each piece, but see my earlier comment about piece maximums not mattering when giving magic armor), and the Mage effect is instructed to try P_GiveMana twice (once for each type of mana).  But in C, there's a thing called short-circuit evaluation which means that as soon as a result occurs that would render the condition true, the condition is regarded as true without evaluating the rest of the conditions.  So when the Fighter's effect succeeds in adding a point to Mesh Armor, or the Mage's effect succeeds in adding to blue mana, as far as the code is concerned, it's done.  Those other P_GiveArmor and P_GiveMana attempts only happen if the previous attempts failed - and while this might make sense for the Mage effect in so far as I could imagine giving one or the other type of mana but not both being possibly intended, it seems to make no sense for the Fighter's - if magic armor is maxed out, it's maxed out, and trying to put it on a different piece of armor isn't going to change that.

 

So, in my opinion, this seems suspiciously like a case of one of those gotchas in C causing a result that wasn't what the programmer actually intended, and the real effect of the Mystic Ambit Incant was meant to have been like this:

 

Fighter: gain armor (4 points) - 4x as effective as the Fighter's "useless" vanilla effect and essentially a Dragonskin Bracers for everyone

Mage: gain equal amounts of blue and green mana (random amount 50 to 99) - like a mini Krater of Might for everyone

Cleric: gain health (random amount 50 to 99) - unchanged because only one condition is checked here

 

But, I'm just someone who occasionally hacks around with C code on an amateur level, so I'd like the experts to weigh in on whether the vanilla effects could in any way be argued as intentional or if it's a clear-cut bug that should be documented as such.

Share this post


Link to post

I think you are correct. This is the typical kind of badly written checks that do not really do what's intended. Boom has a similar effect with its 'split door' special that only moves the ceiling if it can be moved.

 

In this case it is likely that it was never noticed because it's a multiplayer only item.

 

Share this post


Link to post

Seems likely. Great find! Hexen multiplayer is a fairly mythical enough experience to understand how this could be missed. I know I've never set up a Hexen net game.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×