[CODE] Build-Style Toggleable Powerups

Post your example zscripts/ACS scripts/etc here.

[CODE] Build-Style Toggleable Powerups

Postby Mikk- » Wed Jan 13, 2021 10:40 am

I found the need for a not-so-hacky implementation of Build Style toggleable powerups, so I wrote a clean, flexible, ZScript based toggleable powerup system. I felt it would be a nifty resource for those that may want to create their own powerups that work this way.

To create your own actor inherit from the ToggleableInventory class, set up your properties (type, depletion period & regeneration period, icons etc.) and voila! You can also inherit from the ToggleableInventory class with DECORATE, should you need to!
Code: Select allExpand view
class ToggleableInventory : customInventory
    
{
    // whether the item is toggled "on" or "off"
    bool active;
    
    
// the internal ticker (as opposed to performing expensive modulo operations).
    int ticker;
    
    
// the type of powerup to toggle.
    property Type: Type; class<powerup> type;    

    
// the time in tics (or negative values for seconds) it takes to deplete 1 unit
    property RegenPeriod: RegenTics; int RegenTics;         
    
    
// The sounds to play when [De]Activating the powerup...
    property ActivateSound: ActivateSound; sound ActivateSound;
    property DeactivateSound: DeactivateSound; sound DeactivateSound;
    // ...Or to play when fully depleted. 
    property DepletedSound: DepletedSound; sound DepletedSound;
    
    
// the time in tics (or negative values for seconds) it takes to regenerate 1 unit, 0 implies no regeneration.
    property DepletePeriod: ActiveTics; int ActiveTics;    
    
    
// define the custom flags.
    private int ti_Flags;
    
    flagdef CheckFloorRegen
: ti_Flags, 0;
    
    default
        
{
        // the default depletion period is 1 second.
        ToggleableInventory.DepletePeriod -1;        
        
// the default regeneration period is 3 seconds.
        ToggleableInventory.RegenPeriod -3;      
        
        
// the sounds to play when certain things happen
        ToggleableInventory.ActivateSound     "";
        ToggleableInventory.DeactivateSound "";
        ToggleableInventory.DepletedSound     "";
        
        inventory
.Amount 100;                            
        inventory
.MaxAmount 100;            
        inventory
.interhubamount 100;
        +INVENTORY.INVBAR;
        // clear this flag on inheriting classes should you wish for the item to be
        // destroyed when depleted.
        +INVENTORY.KEEPDEPLETED;
        }
    
    override void AttachToOwner
(actor other)
        {
        super.AttachToOwner(other);
        
        
// if the period is negative, make positive & multiply by ticrate (35 in most cases).
        if(ActiveTics < 0)                                
            ActiveTics 
= (-ActiveTics) * TICRATE;
        
        if
(RegenTics < 0)
            RegenTics = (-RegenTics) * TICRATE;
        }
        
    
    override void DoEffect
()
        {
        if(active && amount < 1)                                    
            
{
            // if the amount is 0, then remove the powerup.
            owner.TakeInventory(type, 1);    
            
            
// turn the active state to false.      
            active = false;        
            
            
// play the "Fully Depleted" sound.
            owner.A_StartSound(DepletedSound, CHAN_BODY);
            
            
// if the item is not to be kept when reaching 0 units, remove it.   
            if(!bKEEPDEPLETED)                            
                DepleteOrDestroy
();
                    return;
            }
            
        
// if for some reason the powerup is no longer in the player's inventory
        // while active, deactivate the ToggleableInventory class.        
        if(active && !owner.CountInv(type))
            active = false;
        
        
// deplete/regenerate the amount if ticker is equal to the DepletionPeriod/RegenPeriod.
        if(active && ++ticker == activeTics)    
                
{
                ticker = 0;
                amount = max(amount - 1, 0);
                    return;
                }
            
        if
(!active && amount < maxAmount && RegenTics && ++ticker == RegenTics)
            {
            ticker = 0;
            
            
// If the +ToggleAbleInventory.CheckFloorRegen flag is enabled, don't perform regen.
            // This is to prevent regeneration while falling (good for jetpacks and stuff)
            if(ti_Flags & bCheckFloorRegen && !owner.player.OnGround)
                return;
                
            amount 
= min(amount + 1, maxAmount);
                return;
            }
        }
        
    
// this function simply substiutes GiveInventory to modify the given powerup's duration.    
    void GiveToggleablePowerup()                        
        
{
        owner.GiveInventory(type, 1);        
        
        
// find the powerup in the owner's inventory
        let i = PowerUp(owner.FindInventory(type));        
        
        
// set the duration to around 2 years of real time, (might as well be infinite)
        // note: it is set to '0x7FFFFFFD' instead of '0x7FFFFFFF' to work with PowerTimeFreezer properly.
        if(i)
            i.EffectTics = 0x7FFFFFFD;                    
        
}                                                
    
    
// these virtual functions can be overridden in inheriting classes
    // to add things such as particle effects, sounds and other stuff
    // when the powerup is activated or deactivated.
    // don't forget to call the super. otherwise it will not play any sounds.
    virtual void ActivatePowerup()
        {
        owner.A_StartSound(ActivateSound, CHAN_BODY);
        }
        
    virtual void DeActivatePowerup
()
        {
        owner.A_StartSound(DeactivateSound, CHAN_BODY);
        }
    
     states
        
{
        Use:
            TNT1 A 0
                
{
                // if there are no units left to consume 
                // don't activate the powerup.
                if(invoker.amount < 1)            
                    return
;
                
                if
(invoker.active)                        
                    
{
                    // remove the powerup if active... 
                    // these blocks are where you can also play sounds, visual flashes etc.
                    invoker.owner.TakeInventory(invoker.type, 1);
                    // Perform audio & visual stuff in this function
                    // (can be overridden)
                    invoker.DeactivatePowerup();
                    }
                else    
                    
{
                    // Give the powerup as defined in the function.
                    invoker.GiveToggleablePowerup();    
                    
// Perform audio & visual stuff in this function, too.
                    invoker.ActivatePowerup();
                    }
                
                
// set the ticker to 0 when toggled.
                invoker.ticker = 0;
                // toggle the state of the powerup.
                invoker.active = !invoker.active;        
                
}
            fail;                                        
        
} 
    
}


and here are a couple of examples of togglable powerups:
Code: Select allExpand view
// in zscript...
class ToggleableWings : ToggleableInventory
    
{
    default
        
{
        ToggleableInventory.Type "PowerFlight";
        
        ToggleableInventory
.DepletePeriod 10;    
        ToggleableInventory
.RegenPeriod -2;        
        
        
// cannot regenerate power when in the air.
        +ToggleableInventory.CHECKFLOORREGEN;
        }
    
    
// causes an orange flash when activated.
    override void ActivatePowerup()
        {
        super.ActivatePowerup();
        
        owner
.A_SetBlend("ff9900", 0.5, 25);
        }
    }
 
// in DECORATE   
actor ToggleableRadsuit : ToggleableInventory
    
{
    ToggleableInventory.Type "PowerIronFeet"
    
    
// the period here is 2 seconds.
    ToggleableInventory.DepletePeriod -1

    
// 0 implies that there is no regeneration.        
    ToggleableInventory.RegenPeriod -1        

    ToggleableInventory
.ActivateSound     "switches/normbutn"
    ToggleableInventory.DeactivateSound "switches/exitbutn"
    ToggleableInventory.DepletedSound     "brain/spit"

    // clear the INVENTORY.KEEPDEPLETED flag, should you want the powerup to be removed upon depletion.
    -INVENTORY.KEEPDEPLETED                        
    
}                            
    
Last edited by Mikk- on Sat Mar 27, 2021 10:56 am, edited 4 times in total.
User avatar
Mikk-
yooooooooooo
 
Joined: 30 Jun 2009
Location: Somewhere off Kanagawa
Discord: Mikk0451#3922

Re: [CODE] Build-Style Toggleable Powerups

Postby Ac!d » Mon Jan 25, 2021 9:05 am

I don't know if we can call this an issue but, If I hit an exit map with a ToggleableInventory still activated, the ToggleableInventory will still deplete itself and the powerup won't be activated for the next level.

Edit : You must add the flags +INVENTORY.HUBPOWER and / or +INVENTORY.PERSISTENTPOWER to the powerups used for ToggleableInventory.Type
User avatar
Ac!d
Perfection is a way, not an end. - Korean Proverb
 
Joined: 02 Apr 2019
Location: France
Discord: Ac!d#2732

Re: [CODE] Build-Style Toggleable Powerups

Postby Mikk- » Tue Mar 02, 2021 11:09 am

Updated the code with a few changes. It addresses the issue that Ac!d raised, albeit in a different way. It checks whether the player has the powerup when moving between maps/hubs, if the powerup isn't found, the main item is deactivated.

You may still want to add the +INVENTORY.PERSISTENTPOWER / +INVENTORY.HUBPOWER flags, if that's the sort of behaviour you require.
User avatar
Mikk-
yooooooooooo
 
Joined: 30 Jun 2009
Location: Somewhere off Kanagawa
Discord: Mikk0451#3922

Re: [CODE] Build-Style Toggleable Powerups

Postby Xim » Sat Mar 27, 2021 9:21 am

This is an excellent script. Although I had to come up with a little hack for the item to play a sound when used, because it doesn't seem to perform the use sound assigned (at least in decorate), so I added a "Use:" state like this:
Code: Select allExpand view
Use:
  TNT1 A 0 A_StartSound("misc/gasmask", CHAN_BODY)
  Goto Super::Use


Probably a better way to do it, I'm not much of a coder. But this seems to work for now.

PS: Might be cool to have a version that uses an ammo type, but that'd probably be a whole other project. :P
User avatar
Xim
 
Joined: 20 Feb 2009
Location: Somewhere with trees

Re: [CODE] Build-Style Toggleable Powerups

Postby Mikk- » Sat Mar 27, 2021 10:53 am

Neat idea - I've tweaked the code a little in the main post. I've added three new properties, two virtual functions and a custom flag & modified the timers to work with an internal timer instead of performing a modulo operation on the item's age.
The new properties are all related to sound: (De)ActivateSound & DepletedSound, the (De)Activated sounds are obviously played when the item is activated or deactivated. The Depleted sound is played once the item's amount is reduced to 0.

The virtual functions ActivatePowerup & DeactivatePowerup have been implemented to help with incorporating custom special effects. With an override of these functions you can spawn actors or particles or call A_SetBlend. Really handy to use when you want to add a little more flair when activating or deactivating your powerups. Just make sure to call the super. of the function you're overriding otherwise it may not play the sounds!

Finally the custom flag is +ToggleableInventory.CHECKFLOORREGEN, this flag disables powerup regeneration when the player is not currently standing on the floor - This is useful in the case of a flight powerup, say for example your flight powerup expires mid air, this prevents cheesing of fall damage by letting it regenerate one point and being able to cancel your momentum.

On the idea of using ammo as a counter: it's an interesting idea, however it may be better as a separate item to prevent clutter with the main code. I'll take a look.
User avatar
Mikk-
yooooooooooo
 
Joined: 30 Jun 2009
Location: Somewhere off Kanagawa
Discord: Mikk0451#3922


Return to Script Library

Who is online

Users browsing this forum: No registered users and 1 guest