[ZScript] Hitbox actor

Handy guides on how to do things, written by users for users.

Moderators: GZDoom Developers, Raze Developers

Forum rules
Please don't start threads here asking for help. This forum is not for requesting guides, only for posting them. If you need help, the Editing forum is for you.
Post Reply
User avatar
Matt
Posts: 9696
Joined: Sun Jan 04, 2004 5:37 pm
Preferred Pronouns: They/Them
Operating System Version (Optional): Debian Bullseye
Location: Gotham City SAR, Wyld-Lands of the Lotus People, Dominionist PetroConfederacy of Saudi Canadia
Contact:

[ZScript] Hitbox actor

Post by Matt »

Every so often someone wants either:
  1. An additional "part" of an actor that can be hit for extra damage; or
  2. A shield that can be "worn" or "held" by an actor to block incoming attacks.
Both of these would rely on one or more actors that always position themselves relative to the main actor such that their hitboxes would represent the appropriate part on what the main actor is supposed to represent (head, shield, etc.).

Here's how to set one up in ZScript, using Vec3Angle to find the correct position and overriding Tick to skip all the useless physics checks that would just screw up what we're trying to do.

To try this example: type "give ExampleShieldGiver" in the console. There's no way to get rid of the shield once you have it except to die and respawn.

Not included in this tutorial:
  • Non-damaging collisions that affect the wearer's movement. This can get real messy, real fast, and would detract from the main point of this tutorial.
  • How to totally keep the actor from blocking the main actor's own attacks. Frankly, I don't know how. This shield is conveniently off to the wearer's side, though, so only big projectiles are stopped and this would only be a problem for hitscans that veer off very far to the left (which some zombieman shots might be able to do).
  • Actually making this thing breakable. Just give it health and a death state and you're good to go.
  • Aesthetic stuff like how to keep the actor from clipping through walls and whatnot. It might be better to stick with invisible actors.

Code: Select all

class ExampleShield:Actor{
    default{
        +shootable // the whole point of this exercise
        +nodamage // remove and set health to taste
        health 1; // you might want more in an actual breakable shield
        height 13; radius 18; // you can make it bigger but where's the fun in that?
        scale 0.6; // BEXPB0 is 23x31
        painchance 256; // just to give us some feedback
    }
    states{
    pain:
        BEXP C 4;
        // intentionally falls through to spawn
    spawn:
        BEXP B -1; // set this to a finite duration for a temporary shield
        stop;
    }
    // here's where the magic happens
    // place the actor at the appropriate shield position
    // regardless of state
    override void Tick(){
        // optional: in case you want a death animation of it falling off
        if(health<1){
            super.Tick();
            return;
        }

        // terminate if no applicable, living master
        if(!master||master.health<1){
            destroy();
            return;
        }

        // calculate the new position relative to master
        double reach=master.radius*2; //or whatever you want
        vector3 newpos=master.Vec3Angle(
                reach,
                master.angle+20, // skew it so many degrees to the left
                master.height*0.3-sin(master.pitch)*reach,
                absolute:true // not sure if this does anything
            );

        // place at new position
        SetOrigin(newpos,true);

        // advance to the next frame
        if(!CheckNoDelay()) return;
        if(tics!=-1){
            if(tics>0)tics--;  
            while(!tics){
                if(!SetState(CurState.NextState)){
                    return;
                }
            }
        }
    }
    override int DamageMobj(
        actor inflictor,
        actor source,
        int damage,
        name mod,
        int flags=0,
        double angle=0
    ){
        // if damage is over 20, 10% gets through
        // obviously the same principle applies for a headshot, etc.,
        // just make it *10 or whatever instead of /10.
        if(
            master
            && damage>20
        ){
            double mult=0.1; // setting this number apart for readability
            master.DamageMobj(inflictor,source,damage*mult,mod,flags,angle);

            // optional: additional effects
            // here's a simple knockback/flinch
            master.vel+=(master.pos-pos)*0.001*damage;
        }

        // in case you want a breakble shield
        return Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
    }
}
// dummy actor to get a shield to attach
class ExampleShieldGiver:CustomInventory{
    states{
    pickup:
        TNT1 A 0{
            actor es=spawn("ExampleShield",pos);
            es.master=self;
        }fail;
    }
} 
Post Reply

Return to “Tutorials”