Jump to content

A Cyberdemon Hack Question


ApprihensivSoul

Recommended Posts

I've seen a couple different hacks for the Cyberdemon boss fight floating around, but one I haven't seen that I think would be a good fit is regenerating health.

I.e. the Cyberdemon is killable with normal weapons, but starts with a higher health pool, and has a decent regeneration speed forcing you to rely on your heavier weapons to outrace him, meaning the Soul Cube is still relevant. I was wondering if anyone knew how to make this happen and how I would go about doing it?

(Alternatively, his health is lower, but he has a halo-style overshield, and you need to break it with a Soul Cube hit and then deal normal weapon damage to kill him? Unsure if that's even possible, but I figured I'd ask?)

Share this post


Link to post

That would be ai_monster_boss_cyberdemon.script, located in pak000.pak, in your Doom3/base folder. Give me a few minutes, I'll scrap together a quick hack for healing.

Share this post


Link to post

Okay, this is what I came up with:

 

// CFW Healing Start

#define CYBER_HEAL_RATE            10

    float healtime;
    float nexthealtime;
    float currenthealth;


    healtime = 0;
    currenthealth = getHealth();

    if ( sys.getTime() > nexthealtime ) {
        healtime = sys.getTime();

        if ( healtime >= nexthealtime ) {
            if ( currenthealth < getFloatKey("health") ) {
                setHealth( hp + 50 );
            }
            if ( getHealth() > getFloatKey("health") ) {
                setHealth (getFloatKey("health") );
            }
        }
    }
    // Healing delay
    nexthealtime = DelayTime( CYBER_HEAL_RATE );

// CFW Healing End

 


This should probably be integrated in monster_boss_cyberdemon::check_attacks, so that the cyberdemon heals every time it's deciding when to attack. Keep in mind I'm not a full time programmer so I'm probably missing something in the code I posted above. It's early morning here and I didn't sleep for 30+ hours. You probably want to double-check that code. Or if you can't, wait for someone more intelligent than me to confirm that this will do what I think it will.

The way I hope this will work is as follows:

1. Cyberdemon registers it's health everytime it checks if it can attack
2. If last healing was longer ago than CYBER_HEAL_RATE (in this instance 10), then, if the cyberdemon isn't at full health, it will regenerate 50 HP.
3. If the cyberdemon results in more HP than it's max HP, it will return to it's max HP, so it can't stack infinite health
4. Once the healing is done, the timer is reset

This is as much as I can do for you right now man. The rest is in your hands. I can't test it myself right now as I'm extremely exhausted and don't have doom 3 ready to go anyway. Again, keep in mind, I'm NOT a formally trained programmer. I didn't master any programming language, nor do I actively know Doom Script. I slapped this together out of bits and pieces of mods and already present Doom 3 script. As far as I understand the logic, this should work. But again, double check, take it with a grain of salt and preferably get someone more intelligent than me to verify this. Make sure to backup any files you change. And better yet, have a separate mod file and put the modified .script in there instead of messing with the base files. Good luck.


EDIT: For your other ideas ...

I'm not sure how the soul-cube only thing works. I know Sikkmod implements it, but I don't remember how exactly. I think it's a simple bool toggle (on/off).

Overshield ... I mean, I don't think it's impossible, but it's going to take much more effort than it's worth it and than anyone is going to be willing to spend on it. I have a vague idea how to do this, as I can just rip out the shield system from XLDoom and try and attach it to the cyberdemon somehow, but I'll need to really sit down and work for a few days to figure out how to make that shield envelop the cyberdemon (or at least stay in front of it). And then, I have absolutely zero experience with 3D modeling, so you'll have to figure out someone else to get you visual effects of such a shield. I can't really write GUI either so I can't really give you a health bar for that shield either. TL;DR: Probably doable. But way beyond my skill. And unlikely to be picked up by anybody else due to the sheer amount of work it'll take.

Edited by CFWMagic

Share this post


Link to post

Hah, I'm genuinely impressed with the work you put in so far. I'll tinker when I can, thank you. Totally understand your limitations, get some sleep. I have a couple other experiments to play with, but the overshield might not even need vfx as the boss was a puzzle boss (albeit a weak one) originally. It's just one of the five or six things that make the original game more fleshed out overall. Maybe just do the regen, but the Soul Cube hits pause it to force the weapon switch?

Just spitballing though,  even in this form I'm super interested in experimenting.

Share this post


Link to post

So, about interrupting the healing. Here's the structure of the cyberdemon script:


/***********************************************************************

ai_monster_boss_cyberdemon.script

monster_boss_cyberdemon

***********************************************************************/

#define CYBER_ATTACK_RATE            5
#define    CYBER_PAIN_DELAY            0.25
#define CYBER_NOFOVTIME                4

// anim blend times
#define    CYBER_PAIN_TO_IDLE            2
#define CYBER_PAIN_TO_PAIN            0
#define    CYBER_MELEE_TO_IDLE            4
#define CYBER_RANGE_TO_IDLE            4
#define CYBER_IDLE_TO_PAIN            0
#define CYBER_IDLE_TO_WALK            4
#define CYBER_WALK_TO_IDLE            4
#define CYBER_WALK_TO_RANGEATTACK    4
#define CYBER_WALK_TO_MELEE            4

object monster_boss_cyberdemon : monster_base {
    float        nextAttack;
    string        range_attack_anim;
    entity        kick_entity;
    float        nextNoFOVAttack;

    // ai state
    void        init();
    void        state_Begin();
    void        state_Idle();

    // attacks
    float        check_attacks();
    void        do_attack( float attack_flags );
    void        combat_range();
    void        combat_melee();
    
    // torso anim states
    void        Torso_Idle();
    void        Torso_Pain();
    void        Torso_MeleeAttack();
    void        Torso_RangeAttack();
    
    // legs anim states
    void        Legs_Idle();
    void        Legs_Walk();
};

/***********************************************************************

    Torso animation control

***********************************************************************/

void monster_boss_cyberdemon::Torso_Idle() {
    idleAnim( ANIMCHANNEL_TORSO, "idle" );
    
    while( !AI_SPECIAL_DAMAGE ) {
        waitFrame();
    }
    
    animState( ANIMCHANNEL_TORSO, "Torso_Pain", CYBER_IDLE_TO_PAIN );
}

void monster_boss_cyberdemon::Torso_Pain() {
    string animname;
    float nextpain;
    float currenttime;
    
    animname = getPainAnim();
    playAnim( ANIMCHANNEL_TORSO, animname );

    nextpain = sys.getTime() + CYBER_PAIN_DELAY;
    
    while( !animDone( ANIMCHANNEL_TORSO, CYBER_PAIN_TO_IDLE ) ) {
        if ( AI_SPECIAL_DAMAGE ) {
            currenttime = sys.getTime();
            if ( currenttime > nextpain ) {
                animState( ANIMCHANNEL_TORSO, "Torso_Pain", CYBER_PAIN_TO_PAIN );
            }
        }
        waitFrame();
    }

    finishAction( "pain" );
    animState( ANIMCHANNEL_TORSO, "Torso_Idle", CYBER_PAIN_TO_IDLE );
}

void monster_boss_cyberdemon::Torso_MeleeAttack() {
    playAnim( ANIMCHANNEL_TORSO, "melee_attack" );

    while( !AI_SPECIAL_DAMAGE && !animDone( ANIMCHANNEL_TORSO, CYBER_MELEE_TO_IDLE ) ) {
        lookAtEnemy( 0.1 );
        waitFrame();
    }

    finishAction( "melee_attack" );

    if ( AI_SPECIAL_DAMAGE ) {
        animState( ANIMCHANNEL_TORSO, "Torso_Pain", CYBER_IDLE_TO_PAIN );
    }
    animState( ANIMCHANNEL_TORSO, "Torso_Idle", CYBER_MELEE_TO_IDLE );
}

void monster_boss_cyberdemon::Torso_RangeAttack() {
    disablePain();

    playAnim( ANIMCHANNEL_TORSO, range_attack_anim );
    while( !AI_SPECIAL_DAMAGE && !animDone( ANIMCHANNEL_TORSO, CYBER_RANGE_TO_IDLE ) ) {
        lookAtEnemy( 1 );    
        waitFrame();
    }

    allowMovement( true );
    finishAction( "range_attack" );

    if ( AI_SPECIAL_DAMAGE ) {
        animState( ANIMCHANNEL_TORSO, "Torso_Pain", CYBER_IDLE_TO_PAIN );
    }
    animState( ANIMCHANNEL_TORSO, "Torso_Idle", CYBER_RANGE_TO_IDLE );
}

/***********************************************************************

    Legs animation control

***********************************************************************/

void monster_boss_cyberdemon::Legs_Idle() {
    idleAnim( ANIMCHANNEL_LEGS, "idle" );

    while( !AI_FORWARD ) {
        waitFrame();
    }
    
    animState( ANIMCHANNEL_LEGS, "Legs_Walk", CYBER_IDLE_TO_WALK );
}

void monster_boss_cyberdemon::Legs_Walk() {
    playCycle( ANIMCHANNEL_LEGS, "walk" );
    
    while( AI_FORWARD )    {
        waitFrame();
    }
    
    animState( ANIMCHANNEL_LEGS, "Legs_Idle", CYBER_WALK_TO_IDLE );
}

/***********************************************************************

    AI

***********************************************************************/

/*
=====================
monster_boss_cyberdemon::init
=====================
*/
void monster_boss_cyberdemon::init() {
    nextNoFOVAttack = 0;
    setState( "state_Begin" );
}

/***********************************************************************

    States

***********************************************************************/

/*
=====================
monster_boss_cyberdemon::state_Begin
=====================
*/
void monster_boss_cyberdemon::state_Begin() {
    animState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 );
    animState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 );
    monster_begin();
    setMoveType( MOVETYPE_ANIM );
    setState( "state_Idle" );
}

/*
=====================
monster_boss_cyberdemon::state_Idle
=====================
*/
void monster_boss_cyberdemon::state_Idle() {
    wait_for_enemy();

    nextAttack    = 0;

    setState( "state_Combat" );
}

/***********************************************************************

    attacks

***********************************************************************/

/*
=====================
monster_boss_cyberdemon::do_attack
=====================
*/
void monster_boss_cyberdemon::do_attack( float attack_flags ) {
    nextNoFOVAttack = sys.getTime() + CYBER_NOFOVTIME;
    if ( attack_flags & ATTACK_MELEE ) {
        combat_melee();
    } else if ( attack_flags & ATTACK_MISSILE ) {
        combat_range();
    }
}

/*
=====================
monster_boss_cyberdemon::check_attacks
=====================
*/
float monster_boss_cyberdemon::check_attacks() {
    float currentTime;
    float attack_flags;
    vector org;
    
    attack_flags = 0;

    if ( AI_ENEMY_VISIBLE ) {
        org = getOrigin();
        kick_entity = findActorsInBounds( org + '-65 -65 0', org + '65 65 300' );
        if ( kick_entity ) {
            attack_flags |= ATTACK_MELEE;
        }
    }

    if ( ( ( sys.getTime() > nextNoFOVAttack ) && AI_ENEMY_VISIBLE ) || AI_ENEMY_IN_FOV ) {
        currentTime = sys.getTime();
        if ( !canReachEnemy() || ( currentTime >= nextAttack ) ) {
            range_attack_anim = chooseAnim( ANIMCHANNEL_LEGS, "range_attack" );
            if ( canHitEnemyFromAnim( range_attack_anim ) ) {
                attack_flags |= ATTACK_MISSILE;
            }
        }
    }
    
    return attack_flags;
}

/*
=====================
monster_boss_cyberdemon::combat_range
=====================
*/
void monster_boss_cyberdemon::combat_range() {
    faceEnemy();
    stopMove();
    animState( ANIMCHANNEL_TORSO, "Torso_RangeAttack", CYBER_WALK_TO_RANGEATTACK );
    waitAction( "range_attack" );

    // don't attack for a bit
    nextAttack = DelayTime( CYBER_ATTACK_RATE );
    nextNoFOVAttack = sys.getTime() + CYBER_NOFOVTIME;
}

/*
=====================
monster_boss_cyberdemon::combat_melee
=====================
*/
void monster_boss_cyberdemon::combat_melee() {
    kickObstacles( kick_entity, 200 );
    sys.wait( 0.5 );
    directDamage( kick_entity, "melee_cyberdemon_kick" );
}



So, if you ever did DECORATE, you'll notice some similarities to it. If you did not, no trouble, I'll do my best to explain.

As you can see, the cyberdemon has "states". A state, is basically a specific set of actions that the monster will perform at any given time.

So, state_Begin for example, is the state the cyberdemon is in by default. I.e.: when it is freshly spawned. What this state does is essentially begin the Idle animation, and then put the cyberdemon in state_Idle.

This matters, because you'll notice that once the cyberdemon exits state_Begin, he can't go back to it. Why does this matter? Because you can put the healing script into a specific state. For example, if you put the healing part into the combat state, in the monster_boss_cyberdemon::check_attacks function, then that healing script will ONLY run when that function runs. I.e.: The cyberdemon needs to be checking for attacks in order for it to heal. So, if for instance, you use the soulcube on it and force the cyberdemon to play it's pain animation, then it simply can NOT heal, because the pain state doesn't have the healing script.

What does all of this mean? It means that you can condition the healing script to run at a specific time, and at that time alone. You can even write a completely new state, let's say state_healing, where the cyberdemon will only heal when in this state. You can then give it it's own animation, and have a condition in check_attacks, which, if the cyberdemon is wounded, will call state_healing and put the cyberdemon into it's healing state while playing the animations you gave it. You should then add conditions for the cyberdemon to exit out of that healing state and go back to normal combat state.

And the kicker is, nothing says that state_healing can't contain it's own attacks. For example, you can have the cyberdemon react to pain by giving him a state which will make him static, more aggressive with firing rockets, and give him healing. Of course, this is much harder done than said. I won't be able to accomplish this as animation is REALLY not my strongest suit. But yeah, I hope this helps you with your experimentation.

Good luck! Keep me posted! I'm curious what you'll come up with!

Share this post


Link to post

Out of curiousity, what is the simplest way to adjust the rate at which monsters can move in this game? I imagine it's tied to the animations, but I can't figure out in what way. I'd love for the monsters to have some faster movement if possible, especially Hell Knights and Imps.

was able to implement the regeneration script without crashing the game, I haven't figured out how to check if it's working besides just fighting him, but it's not a good process. 

Share this post


Link to post
  • 3 weeks later...
On 10/26/2022 at 4:33 PM, ApprihensivSoul said:

Out of curiousity, what is the simplest way to adjust the rate at which monsters can move in this game? I imagine it's tied to the animations, but I can't figure out in what way. I'd love for the monsters to have some faster movement if possible, especially Hell Knights and Imps.

was able to implement the regeneration script without crashing the game, I haven't figured out how to check if it's working besides just fighting him, but it's not a good process. 


Can't tell you much about animations, as 3D graphics and physics are the bane of me. I'd rather not say something stupid and incorrect than attempt an uneducated guess on this one. Closest I can tell you is take a look at how sikkmod's custom demons are written. If my memory serves me well, the Baron of Hell moves somewhat slower than a Hellknight while reusing HK's animations. Your best bet is there.

As for testing the regeneration script, slap it on top of an imp and console yourself to map test_test_box. Should make things much easier.

I lost my computer and can't really do any work right now so that'll have to do. But if you're not in a hurry, I'll get back to you after buying a new motherboard and such.

Share this post


Link to post
On 10/26/2022 at 4:33 PM, ApprihensivSoul said:

Out of curiousity, what is the simplest way to adjust the rate at which monsters can move in this game? I imagine it's tied to the animations, but I can't figure out in what way.

You can adjust the framerate in the monster's md5anim file. To make the hell knight walk faster, you can first check its definition file to see which animation file(s) it uses for walking and then look in "base\pak002.pk4\models\md5\monsters\hellknight\" to find the corresponding file, and increase the "frameRate" from its default of 24.

Share this post


Link to post

Is increasing the frame rate not working? I imagine it's far simpler than creating new animations, and I don't know what would happen if you start deleting frames, but if you know how to do all that, then you already know what you're doing, and the sky's the limit I suppose. Increasing the frame rate in the md5anim files for walking involves changing one number and will result in the monster moving as fast as the number you put in. Increase the frame rate of an attack animation and the monster will attack more quickly, etc.

Share this post


Link to post

I actually don't know how to do that, I'm applying external knowledge to the concept. I haven't had time to work on it yet. 

 

The framerate thing makes sense. I'm just trying to brainstorm other approaches that use the same logic, such as frame removal or the like. Sorry to confuse you!

Share this post


Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...