HeXen update

Post a reply

Smilies
:D :) :( :o :shock: :? 8-) :lol: :x :P :oops: :cry: :evil: :twisted: :roll: :wink: :geek: :ugeek: :!: :?: :idea: :arrow: :| :mrgreen: :3: :wub: >:( :blergh:
View more smilies

BBCode is ON
[img] is OFF
[url] is ON
Smilies are ON

Topic review
   

Expand view Topic review: HeXen update

Re: HeXen update

by MartinHowe » Sat Aug 16, 2025 7:02 am

Cool. And thanks :mrgreen:

Re: HeXen update

by Rachael » Sat Aug 16, 2025 5:13 am

Native morphing can be done with GZDoom and is already possible and has actually been done previously.

And I am not talking about the type that locks you to one weapon. You can completely become the target class, so much so that the change persists even across levels, and get access to that class's full suite of weapons. At the end of the day, the playerinfo struct simply points to a playerpawn to indicate that player's primary actor, and when one actor is spawned in on the fly and all pointers get substituted properly (and stats are properly changed if needed such as health), a full morph has already taken effect and the old playerpawn object can be destroyed.

In fact, at this point, the morph powerup is actually more complex than this task is. So there's that.

This is yet to become a full fledged official feature, but it is already planned.

So there is nothing to worry about as far as class-changing goes.

Re: HeXen update

by MartinHowe » Sat Aug 16, 2025 12:14 am

Discussion redirected from the Doomworld thread, so as not to spam NightDive's thread with ZDoom-specific stuff.

Regarding the matter of implementing class changing as morphing; experiments seem to suggest that it can be done in ZScript. Indeed, most of the morph code nowadays seems to be in ZScript. However, while a script is possible that saves weapons across morphs, one cannot wield more than one weapon when morphed. You can give a morphed player another weapon, it goes in their inventory, they get the mana in it (if applicable), and they can can drop it; but they can't wield it if they already have another weapon.

Sadly, the weapon code is so convoluted, that it's nowhere near obvious why one cannot use, say, a cleric staff while morphed as a cleric. The item is given, in the inventory, but cannot be switched to. This it really bizarre. What on earth, deep in the code, is stopping it? :shrug:

So it seems that maybe there should be a clean method of switching player classes that isn't based on morphing but is a straight-up thing in itself; maybe start with the morph code and strip out everything not essential to it. Of course, to support it might require changes under the hood. It'd be interesting to know what the devs think.

I have always considered it kinda sad that morphing wasn't in from the start; theoretically one should have always been able to attach a player avatar to any actor, even a barrel, on the understanding that actions are limited to those of the actor and that, for monsters, PSprites would need adding. The standard attacks and actions common to most monsters would be tagged as such so that player control can be automatically mapped to them by the engine. For example, a barrel would ignore movement commands and only have one attack state: Melee, which would be to self-destruct, essentially making it a suicide bomber. However, this would need to have been set up from the start, so I can understand why it was never a thing. Of course it could now be done in the form of some huge mod; but as always, there'd never be the time to do it :(

Experimental morph code:

Code: Select all

class MorphCycler : ArtiEgg
{
    bool giveFWeapFist;
    bool giveCWeapMace;
    bool giveMWeapWand;

    bool morphed;

    bool hasFWeapFist;
    bool hasFWeapAxe;
    bool hasFWeapHammer;
    bool hasFWeapQuietus;

    bool hasCWeapMace;
    bool hasCWeapStaff;
    bool hasCWeapFlame;
    bool hasCWeapWraithverge;

    bool hasMWeapWand;
    bool hasMWeapFrost;
    bool hasMWeapLightning;
    bool hasMWeapBloodscourge;

    class<Weapon> fighterWielding;
    class<Weapon> clericWielding;
    class<Weapon> mageWielding;

    Default
    {
        Inventory.PickupMessage "Morph Cycler";
        Tag "Morph Cycler";
    }

	override bool TryPickup (in out Actor toucher)
	{
		bool result = super.TryPickup(toucher);

        if (result)
        {
            console.printf("PICKUP!");
            // When first morphed we must
            // give them the starting weapon;
            // this is pickup so not morphed,
            // at least not by us.
            giveFWeapFist = !(toucher is "FighterPlayer");
            giveCWeapMace = !(toucher is "ClericPlayer");
            giveMWeapWand = !(toucher is "MagePlayer");
        }

        return result;
	}

    override bool Use(bool pickup)
    {
        if (!owner)
        {
            console.printf("No owner!");
            return false;
        }

        let currentClass = owner.GetClass();

        if (!(currentClass is "PlayerPawn"))
        {
            console.printf("Not player class!");
            return false;
        }

        if (currentClass is "FighterPlayer")
        {
            MorphToNewClass("ClericPlayer");
        }
        else if (currentClass is "ClericPlayer")
        {
            MorphToNewClass("MagePlayer");
        }
        else if (currentClass is "MagePlayer")
        {
            MorphToNewClass("FighterPlayer");
        }

        return false;
    }

    void MorphToNewClass(class <Actor> newClass)
    {
        string oldClassName = owner.GetClassName();
        string newClassName = newClass.GetClassName();
        int flags = MRF_ADDSTAMINA|
                    MRF_FULLHEALTH|
                    MRF_WHENINVULNERABLE|
                    MRF_LOSEACTUALWEAPON|
                    MRF_NEWTIDBEHAVIOUR|
                    MRF_UNDOBYDEATHFORCED|
                    MRF_UNDOALWAYS|
                    MRF_TRANSFERTRANSLATION;

        console.printf("Currently a %s, requesting %s.", oldClassName, newClassName);

        if (owner is newClass)
        {
            console.printf("Already a %s, no action required.", newClassName);
            return;
        }

        bool wasMorphed = false;
        if (morphed)
        {
            console.printf("Attempting unmorph.");
            SaveWeaponState(owner.GetClass());
            owner.Unmorph(owner, true);
            wasMorphed = true;
            morphed = false;
        }

        if (owner is newClass)
        {
            RestoreWeaponState(newClass);
            return;
        }

        // If execution reaches here and we were previously morphed then:
        //  * The state of the old class has already been saved.
        //  * Because we were not already the current class, its state was not in
        //    use and thus has not been modified since it was last morphed away from.
        //  * Because we are going to immediately morph away from it again, we
        //    we do not need to restore its state.

        // If execution reaches here and we were NOT previously morphed then:
        //  * Because we are the current class, its state is in
        //    use and thus must be saved before the morph.
        //  * Because we are not already the new class, its state was not in
        //    use and thus must be restored after the morph.

        console.printf("Attempting morph to %s.", newClassName);

        if (!wasMorphed) SaveWeaponState(owner.GetClass());
        if (owner.A_Morph(newClass, 10500, flags))
        {
            console.printf("Now a %s\n", newClass.GetClassName());
            RestoreWeaponState(newClass);
            morphed = true;
        }
        else
        {
            console.printf("Morph to %s failed!", newClassName);
            console.printf("Remaining at %s\n", owner.GetClassName());
        }
    }

    void SaveWeaponState(class <Actor> currentClass)
    {
        let readyWeapon = owner.Player.ReadyWeapon;

        if (currentClass is "FighterPlayer")
        {
            FighterSaveWeapons();
            fighterWielding = null;
            if (readyWeapon) fighterWielding = readyWeapon.GetClass();
        }
        else if (currentClass is "ClericPlayer")
        {
            ClericSaveWeapons();
            clericWielding = null;
            if (readyWeapon) clericWielding = readyWeapon.GetClass();
        }
        else if (currentClass is "MagePlayer")
        {
            MageSaveWeapons();
            mageWielding = null;
            if (readyWeapon) mageWielding = readyWeapon.GetClass();
        }
    }

    void RestoreWeaponState(class <Actor> newClass)
    {
        if (newClass is "FighterPlayer")
        {
            if (giveFWeapFist) // first time as a fighter
            {
                if (!(owner.FindInventory("FWeapFist")))
                {
                    owner.GiveInventory("FWeapFist", 1);
                }
                owner.A_SelectWeapon("FWeapFist");
                giveFWeapFist = false;
            }
            else
            {
                FighterRestoreWeapons();
                if (fighterWielding) owner.A_SelectWeapon(fighterWielding, SWF_SELECTPRIORITY);
            }
        }
        else if (newClass is "ClericPlayer")
        {
            if (giveCWeapMace) // first time as a cleric
            {
                if (!(owner.FindInventory("CWeapMace")))
                {
                    owner.GiveInventory("CWeapMace", 1);
                }
                owner.A_SelectWeapon("CWeapMace");
                giveCWeapMace = false;
            }
            else
            {
                ClericRestoreWeapons();
                if (clericWielding) owner.A_SelectWeapon(clericWielding, SWF_SELECTPRIORITY);
            }
        }
        else if (newClass is "MagePlayer")
        {
            if (giveMWeapWand) // first time as a mage
            {
                if (!(owner.FindInventory("MWeapWand")))
                {
                    owner.GiveInventory("MWeapWand", 1);
                }
                owner.A_SelectWeapon("MWeapWand");
                giveMWeapWand = false;
            }
            else
            {
                MageRestoreWeapons();
                if (mageWielding) owner.A_SelectWeapon(mageWielding, SWF_SELECTPRIORITY);
            }
        }
    }

    void FighterSaveWeapons()
    {
        hasFWeapFist = !!(FindInventory("FWeapFist"));
        hasFWeapAxe = !!(FindInventory("FWeapAxe"));
        hasFWeapHammer = !!(FindInventory("FWeapHammer"));
        hasFWeapQuietus = !!(FindInventory("FWeapQuietus"));
    }

    void FighterRestoreWeapons()
    {
        if (hasFWeapFist) owner.GiveInventory("FWeapFist", 1);
        if (hasFWeapAxe) owner.GiveInventory("FWeapAxe", 1);
        if (hasFWeapHammer) owner.GiveInventory("FWeapHammer", 1);
        if (hasFWeapQuietus) owner.GiveInventory("FWeapQuietus", 1);
    }

    void ClericSaveWeapons()
    {
        hasCWeapMace = !!(FindInventory("CWeapMace"));
        hasCWeapStaff = !!(FindInventory("CWeapStaff"));
        hasCWeapFlame = !!(FindInventory("CWeapFlame"));
        hasCWeapWraithverge = !!(FindInventory("CWeapWraithverge"));
    }

    void ClericRestoreWeapons()
    {
        if (hasCWeapMace) owner.GiveInventory("CWeapMace", 1);
        if (hasCWeapStaff) owner.GiveInventory("CWeapStaff", 1);
        if (hasCWeapFlame) owner.GiveInventory("CWeapFlame", 1);
        if (hasCWeapWraithverge) owner.GiveInventory("CWeapWraithverge", 1);
    }

    void MageSaveWeapons()
    {
        hasMWeapWand = !!(FindInventory("MWeapWand"));
        hasMWeapFrost = !!(FindInventory("MWeapFrost"));
        hasMWeapLightning = !!(FindInventory("MWeapLightning"));
        hasMWeapBloodscourge = !!(FindInventory("MWeapBloodscourge"));
    }

    void MageRestoreWeapons()
    {
        if (hasMWeapWand) owner.GiveInventory("MWeapWand", 1);
        if (hasMWeapFrost) owner.GiveInventory("MWeapFrost", 1);
        if (hasMWeapLightning) owner.GiveInventory("MWeapLightning", 1);
        if (hasMWeapBloodscourge) owner.GiveInventory("MWeapBloodscourge", 1);
    }
}

Re: HeXen update

by Enjay » Mon Aug 11, 2025 8:07 am

I suspected that they might be a bit more controlling with it. I do like the DF remaster a lot though. As well as all the coding side of things, I was really impressed with the upgraded videos. I don't know if Lucas Arts provided original high-res versions that they had, or if things were upscaled, or if sections had to be re-made and re-rendered, (or maybe a combination of all of those things) but they are very nicely done.
Thanks for the insight.

Re: HeXen update

by edward850 » Mon Aug 11, 2025 6:54 am

I was adjacent to it but not personally involved, I was working on Quake2 and then Doom+DoomII at the time, and it didn't have multiplayer.
Though indeed it was different as Lucasfilm were providing guidelines for art and lore, amongst others. Even the achievement names.

Re: HeXen update

by Enjay » Mon Aug 11, 2025 6:00 am

That's cool, thanks. It seems like a very reasonable approach and sounds like you do have quite a bit of agency with it.
Were you also involved in the Dark Forces Remaster release too? Given that's a Lucas Arts (or Disney?) product, I imagine that might have been a bit different?

Re: HeXen update

by edward850 » Sun Aug 10, 2025 8:36 pm

It rather depends, for something like Quake 1 & 2 everything we did went through approvals by Kevin Cloud, given he was the director. For Doom, Heretic and Hexen we basically proposed our expectations for the project to id Software ahead of time, but the technical aspects of how to implement them were up to us and Mike Rubits (sponge) at id Software.

Re: HeXen update

by Enjay » Sun Aug 10, 2025 7:27 pm

Thanks for the explanation.
As a matter of interest, if you are able to say, how much free reign do you get for new features etc? Like, adding the shield mechanics or new items, or editing the maps. Are you guys just trusted to make sensible decisions or do you get any direction about the kind of things to change or have to go through a process where any proposed changes you want to make are authorised by someone who is not part of NightDive?

Re: HeXen update

by edward850 » Sun Aug 10, 2025 6:25 pm

Enjay wrote: Fri Aug 08, 2025 5:43 am Much as I appreciate what NightDive et al does, I do wonder why they have a tendency to re-invent the wheel and introduce new systems to do the work of well-established standards with (as far as I can see it) little or no advantage, that simply increase the work for port authors and serve to confuse modders about which system they "should" be using, and make mods incompatible with certain ports.

For a while things were becoming more and more unified in the approach to Doom engine modding but, recently, there has been a proliferation of standards for, again - as I see it, little or no advantage.
Logistics, mainly. We aren't building from any established community port but rather the internal Doom branch that's been used since the OG Xbox, which while it isn't functionally different from the original engine, it is structurally different due to how it implemented splitscreen. Anything we implemented has to be done with a limited timeframe, serve our purposes immediately for the QA team to be able to test, and also actually just work in the code base we even have which is still expecting a dehacked structured actor table. In addition to there being no real Heretic/Hexen modding standard in the first place, we have to use something that already works with the tools we have within that timeframe. The JSON system that was created is actually just an alternate way to define the internal dehacked tables, just without having to make new dehacked extensions in heretic or hexen.

Re: HeXen update

by Rachael » Sun Aug 10, 2025 4:24 am

ThatKidBobo wrote: Sat Aug 09, 2025 2:11 pm If these changes get implemented, will they be toggleable? Although I really have no idea how that would work with the maps. Where are the new maps and assets stored anyway? I'm guessing they are in wad files too, but if they are not, how would that be implemented?
Two precedents protect user choice and is expected to do so in this scenario as well: First, GZDoom has always been a "user-first" port, meaning a lot of things like this are toggleable precisely for that reason. And secondly, they are toggleable in the Kex port anyhow (at least when I saw it on the Gmanlives video), so it just wouldn't make any sense to not include switches for it.

Whether you have to restart GZDoom or the maps in question remains to be seen, but that is something that will be hashed out with the logistics of the implementation when it does eventually happen.

Re: HeXen update

by salahmander2 » Sat Aug 09, 2025 4:17 pm

ThatKidBobo wrote: Sat Aug 09, 2025 2:11 pm If these changes get implemented, will they be toggleable? Although I really have no idea how that would work with the maps. Where are the new maps and assets stored anyway? I'm guessing they are in wad files too, but if they are not, how would that be implemented?
Having some experience with that part of ZScript, once the EXDEFs are translated to ZScript, it's actually pretty easy to switch to using the enhanced weapons and the monsters without a need for creating a new actor specifically for the enhanced variants, and given that the toggles can work pretty well with ZScript courtesy of CVARINFO and adding on an option specifically for switching them.

Re: HeXen update

by Enjay » Sat Aug 09, 2025 3:02 pm

ThatKidBobo wrote: Sat Aug 09, 2025 2:11 pm If these changes get implemented, will they be toggleable? Although I really have no idea how that would work with the maps. Where are the new maps and assets stored anyway? I'm guessing they are in wad files too, but if they are not, how would that be implemented?
There are certainly additional WAD files with altered maps and additional resources.

Re: HeXen update

by ThatKidBobo » Sat Aug 09, 2025 2:11 pm

If these changes get implemented, will they be toggleable? Although I really have no idea how that would work with the maps. Where are the new maps and assets stored anyway? I'm guessing they are in wad files too, but if they are not, how would that be implemented?

Re: HeXen update

by Enjay » Sat Aug 09, 2025 4:55 am

I asked for a balanced opinion, and I got one. :)
Thank you.

Re: HeXen update

by Rachael » Sat Aug 09, 2025 4:04 am

GPL is more of an end-user-friendly license than it is an author-friendly license.

The GPL license over the DSL, allows for commercial use, ensures that forks always remain source-available, and ensures that you, as a user, have full permission to do whatever you want to the software and that you can make whatever changes you please with the software in your own personal use. It also gives contributors legal recourse should the license ever not be followed properly, such as a person taking your work and republishing it without making the source available.

It is also the most widely recognized license in use for open-source projects, and for all of its faults is probably one of the leading reasons why lots of free software remains open and accessible to everyone (including entire operating systems such as Linux), including those who might not be able to otherwise afford it if GPL alternatives not available to them.

The GPL is a perfect example of how nothing is ever 100% good or 100% bad.

Top