No idea where to begin. A "smart" item.

Ask about ACS, DECORATE, ZScript, or any other scripting questions here!

Moderator: GZDoom Developers

Forum rules
Before asking on how to use a ZDoom feature, read the ZDoom wiki first. If you still don't understand how to use a feature, then ask here.

Please bear in mind that the people helping you do not automatically know how much you know. You may be asked to upload your project file to look at. Don't be afraid to ask questions about what things mean, but also please be patient with the people trying to help you. (And helpers, please be patient with the person you're trying to help!)
Post Reply
User avatar
NeoTerraNova
Posts: 153
Joined: Tue Mar 14, 2017 5:18 pm
Location: Western North Southlandia (East Side)

No idea where to begin. A "smart" item.

Post by NeoTerraNova »

Greetings, all! Thanks for stopping by and having a look at my problem/question.

I'm still trying to learn ZScript, and given that my only prior "programming" knowledge is HTML and DECORATE, it's.. not something I fully understand, still. What I'm looking to do, today, is create a Pickup Item that has a counter of 50 rounds of ammunition in it. Upon pickup, those rounds should be distributed into the magazines of weapons that the character holds, going from one to the next, filling the magazines in the guns, and once full, moving on to the next one in line until all are full, or the Pickup runs out of bullets to give. It should not distribute ammunition to magazines for weapons the Player does not have.

I've looked at the code for Intelligent Supplies, Thrifty Ammo, and Smart Scavenger.. and I'm totally lost in the code. I don't need to "smart" or "thrifty" anything else but these boxes of ammo (there would be one for each caliber of ammo, say 6 total) because of how I've worked the non-ammo supplies, so trying to patch one of those to work doesn't seem like a realistic solution. Yes, Thrifty Ammo does only focus on Ammo, but I don't understand how the code works at all, never mind how to modify it to work with how I have weapons and ammo set up.

Any help or pointers would be greatly appreciated.

I had a thought of somehow using a Counter in ZScript to count down the number of bullets in the box as they're being distributed among the Player's weapons, but I have no idea how to implement that, I've never seen a counter before, let alone coded one, and the page in the Wiki that covers it, doesn't provide an example that I understand.

I don't care if this is done via DECORATE or ZSCRIPT, or if the code ends up being clunky, so long as I can get this to work, somehow.

Thank you to anyone that stops by to help.
User avatar
Virathas
Posts: 254
Joined: Thu Aug 10, 2017 9:38 am

Re: No idea where to begin. A "smart" item.

Post by Virathas »

This shouldn't be very hard, although i am still unsure how this "pickup item" would work. If it is simply an ammo box, that you touch and then it affects you (no carrying in inventory) you could do something like this in the TryPickup:

(This is pseudocode, not actual zscript)

Code: Select all

// Use "Amount" as the ammo left in the box

While (Amount) // Loop for each "bullet"
{
--> Check ammo for the current/main weapon, if it is not full, add one bullet and reduce amount by 1. Use Continue, to restart the loop

--> If current/main weapon is full, we check the other weapon, the same way as above. Repeat this up to all weapon slots.

--> If there was no ammo to give into weapon, we need to break from the while
}
--> And here you need to decide, whether remove the box anyway, or keep it with reduced ammo.
I honestly have no idea how you save the data for weapons in the player, so i cannot help you with actual code.
User avatar
NeoTerraNova
Posts: 153
Joined: Tue Mar 14, 2017 5:18 pm
Location: Western North Southlandia (East Side)

Re: No idea where to begin. A "smart" item.

Post by NeoTerraNova »

Sorry for not getting back to you sooner. I'm going through some preoccupying real life events right now.

Let me set you up with an example, here, for a single bullet, that I'm actually using right now.

Code: Select all

Class Loose9mm1 : CustomInventory
{
Default
{
	Scale 0.1;
	inventory.pickupsound "pickup/9mm";
	inventory.pickupmessage "Loose Ammunition: 9mm bullet";
	-COUNTITEM
    }
    states
	{
Pickup:
  TNT1 A 0
  {
    let Z21M = FindInventory("Z21AM");
    let K23M = FindInventory("K23AM");
    let G44M = FindInventory("G44AM");
    let MP4M = FindInventory("MP4AM");
    let Z211 = FindInventory("Z21");
    let Z212 = FindInventory("Z21SH");
    let Z213 = FindInventory("Z21HH");
    let K231 = FindInventory("K23");
    let K232 = FindInventory("K23SH");
    let K233 = FindInventory("K23HH");
    let G441 = FindInventory("G44");
    let G442 = FindInventory("G44SH");
    let MP41 = FindInventory("MP4");
    let MP42 = FindInventory("MP4SD");
    if(Z211 != null || Z212 != null || Z213 != null && Z21M.Amount + 1 <= Z21M.MaxAmount)
     {
        A_GiveInventory("Z21AM",1);
       }
    else if(K231 != null || K232 != null || K233 != null && K23M.Amount + 1 <= K23M.MaxAmount)
       {
        A_GiveInventory("K23AM",1);
       } 
    else if(G441 != null || G442 != null && G44M.Amount + 1 <= G44M.MaxAmount)
       {
        A_GiveInventory("G44AM",1);
       } 
    else if(MP41 != null || MP42 != null && MP4M.Amount + 1 <= MP4M.MaxAmount)
       {
        A_GiveInventory("MP4AM",1);
       } 
    else
       { 
        return invoker.FindState("Nothing");
       }
  return invoker.FindState(null); 
     }
  TNT1 A 0 A_RailWait; 
  Stop;
  
    Nothing:
        TNT1 A 0 A_PRINT("All my 9mm weapons are full.");
    Fail;
	Spawn:
       9MMB A -1 Bright;
	Stop;
	}
}

Now, I THINK i know where you're going with your psuedo-example, and the above code could be adapted by... adding an Amount counter to the top part of the code (say, 50), then.. I think I can use the rest of my check code as-is? I mean, it should continue the check downwards through the rest of the weapons listed, while preventing it from trying to fill weapons the Player does not have.

I will give this a try and see if I can figure out how to add an Amount thing in there, and I suppose I need to change the standard Pickup to a TryPickup state?

Thank you for the pointers. If you have a better idea, though, given this bit of code, I'd love to hear it. Honestly, I thought I'd have to write something entirely different.
User avatar
Player701
 
 
Posts: 1710
Joined: Wed May 13, 2009 3:15 am
Graphics Processor: nVidia with Vulkan support
Contact:

Re: No idea where to begin. A "smart" item.

Post by Player701 »

Let me say this again: please stop using CustomInventory in ZScript, its sole purpose when it was created was to overcome DECORATE's limited scripting capabilities. Everything that was only doable with CustomInventory in DECORATE can be done in a much better fashion with normal Inventory in ZScript.

The following code implements a pickup that scans for select ammo types (as defined in the AmmoTypes array) by searching the player's inventory for any weapons that use them. Provided that a matching weapon is found, ammo is refilled by at most Amount rounds (but not above the MaxAmount of the corresponding ammo type). Note that the distribution is not even and will "greedily" refill each ammo type in the order they are defined in the array.

Code: Select all

class Loose9mm1 : Inventory
{
    Default
    {
        Inventory.Amount 1;
        Inventory.PickupSound "pickup/9mm";
        Inventory.PickupMessage "Loose Ammunition: 9mm bullet";
    }

    States
    {
        Spawn:
            9MMB A -1 Bright;
            Stop;
    }

    // These are the ammo types we'll be scanning for
    static const class<Ammo> AmmoTypes[] = { 'Z21AM', 'K23AM', 'G44AM', 'MP4AM' };

    override bool TryPickup(in out Actor toucher)
    {
        bool success = false;
    
        // Try to give ammo of each type, as long as we have anything to give at all
        for (int i = 0; i < AmmoTypes.Size() && Amount > 0; i++)
        {
            let at = AmmoTypes[i];
        
            // Check if we have any weapons that use this ammo type
            for (let ii = toucher.Inv; ii != null; ii = ii.Inv)
            {
                let weap = Weapon(ii);
                
                if (weap == null)
                {
                    // Not a weapon
                    continue;
                }

                Ammo aa;

                if (weap.Ammo1 is at)
                {
                    // Correct ammo1
                    aa = weap.Ammo1;
                }
                else if (weap.Ammo2 is at)
                {
                    // Correct ammo2
                    aa = weap.Ammo2;
                }
                else
                {
                    // Wrong ammo types
                    continue;
                }

                // Correct ammo. See how much we can give
                int toGive = Min(aa.MaxAmount - aa.Amount, Amount);
                
                aa.Amount += toGive;
                Amount -= toGive;
                
                // Pickup will succeed if at least 1 ammo has been given
                success = success || toGive > 0;
            }
        }

        if (success)
        {
            // OK, register pickup
            GoAwayAndDie();
        }
        else
        {
            toucher.A_Print("All my 9mm weapons are full.");
        }

        return success;
    }
}

class Loose9mm50 : Loose9mm1
{
    Default
    {
        Inventory.Amount 50;
        Inventory.PickupMessage "Loose Ammunition: 9mm bullets (x50)";
    }
}
User avatar
NeoTerraNova
Posts: 153
Joined: Tue Mar 14, 2017 5:18 pm
Location: Western North Southlandia (East Side)

Re: No idea where to begin. A "smart" item.

Post by NeoTerraNova »

I get on just to reference the post here and start trying to code something.. lo' and behold! Player701, thank you SO MUCH for this. I can't express my appreciation enough.


It does simplify my original single bullet pickup, and the "Loose9mm50" class does work to fill up all the weapons. Which makes things more elegant, and I do understand... some of this. Enough, anyway, to make this repeatable.

Now, the (to me!) harder part - making the "Loose9mm50" not vanish after taking just 1 bullet from it, and preserve it. I got it to work half way, but I can't seem to get it to work quite the way I was hoping. This is the Code I came up with:

Code: Select all

class Loose9mm1 : Inventory
{
    Default
    {
        Inventory.Amount 1;
        Inventory.PickupSound "pickup/9mm";
        Inventory.PickupMessage "Loose Ammunition: 9mm Bullet";
    }

    States
    {
        Spawn:
            9MMB A -1 Bright;
            Stop;
    }

    // These are the ammo types we'll be scanning for
    static const class<Ammo> AmmoTypes[] = { 'Z21AM', 'K23AM', 'G44AM', 'MP4AM' };

    override bool TryPickup(in out Actor toucher)
    {
        bool success = false;
        bool remains = false;
   
        // Try to give ammo of each type, as long as we have anything to give at all
        for (int i = 0; i < AmmoTypes.Size() && Amount > 0; i++)
        {
            let at = AmmoTypes[i];
       
            // Check if we have any weapons that use this ammo type
            for (let ii = toucher.Inv; ii != null; ii = ii.Inv)
            {
                let weap = Weapon(ii);
               
                if (weap == null)
                {
                    // Not a weapon
                    continue;
                }

                Ammo aa;

                if (weap.Ammo1 is at)
                {
                    // Correct ammo1
                    aa = weap.Ammo1;
                }
                else if (weap.Ammo2 is at)
                {
                    // Correct ammo2
                    aa = weap.Ammo2;
                }
                else
                {
                    // Wrong ammo types
                    continue;
                }

                // Correct ammo. See how much we can give
                int toGive = Min(aa.MaxAmount - aa.Amount, Amount);
                int leftov = Amount - toGive;
               
                aa.Amount += toGive;
                Amount -= toGive;
               
                // Pickup will succeed if at least 1 ammo has been given
                remains = remains && leftov < 0; //It doesn't seem to read this part
                success = success || leftov == 0;
            }
        }
        if (remains)
        {
            toucher.Spawn("Loose9mm1",toucher.pos,NO_REPLACE); //I have no idea if I did the Vector3 part correctly, I really don't know how to reference that right. It doesn't seem to be activating, anyway. I'd like the Loose9mm1 left over to simply be dropped on the ground at the player's feet, if I can.
        }
        if (success)
        {
            // OK, register pickup
            GoAwayAndDie();
        }
        else
        {
            toucher.A_Print("All my 9mm weapons are full.");
        }

        return success;
    }
}

class Loose9mm50 : Loose9mm1
{
    Default
    {
        Inventory.Amount 50;
        Inventory.PickupMessage "Loose Ammunition: 9mm bullets (x50)";
    }
}

Now, the engine does eat the code, and it does so properly. When "Loose9mm50" is Summoned, it does take only "part" of it to fill the weapons, and leaves the rest behind. It does NOT seem to "complete" the whole function, though, and play the appropriate sound or cause a screen flash when touched to indicate it's being picked up. I don't know where to slip in a Log or Print, force a Pickup Flash (entirely optional), and PlaySound (or appropriate other function) for the pickup, if it only picks up part of the "Loose9mm50."

I specified that it should "toucher.Spawn" the "Loose9mm1" in hopes of having the "Loose9mm50" change to a different graphic to indicate that it's not a full box (preferable), or, just have it dump loose bullets on the ground (not ideal, but I can work with it), if there's ammo left over after the Player does not take every bullet in "Amount."

From my initial work with my Ammo code (thanks again Virathas!), I found that if I didn't specify things in a specific way, the "dropped" ammunition, whatever it is, will be one object with all the remaining Amount inside of it - this was the effect I was hoping to get (and to be fair, this does kinda do that, sorta).

Again, any further help is greatly appreciated. A HUGE THANK YOU to Player701 and Virathas for taking your time to help me.
User avatar
Player701
 
 
Posts: 1710
Joined: Wed May 13, 2009 3:15 am
Graphics Processor: nVidia with Vulkan support
Contact:

Re: No idea where to begin. A "smart" item.

Post by Player701 »

You are overthinking it. At the end, simply check if there is still some ammo left with Amount > 0. I've also added a check to disable creation of a "residual" item if item respawning is enabled (otherwise it'd lead to multiple items reappearing in the same spot). I do recommend maintaining multiplayer compatibility as a good practice.

The only part I don't like is A_Print, since it will be triggered as soon as the residual item is created, and it will be triggered many times when approaching the pickup range of the item while fully stocked. I'm not sure if there's a clean way to work around it, maybe best to remove it altogether.

Code: Select all

class Loose9mm1 : Inventory
{
    Default
    {
        Inventory.Amount 1;
        Inventory.PickupSound "pickup/9mm";
        Inventory.PickupMessage "Loose Ammunition: 9mm bullet";
    }

    States
    {
        Spawn:
            9MMB A -1 Bright;
            Stop;
    }

    // These are the ammo types we'll be scanning for
    static const class<Ammo> AmmoTypes[] = { 'Z21AM', 'K23AM', 'G44AM', 'MP4AM' };

    override bool TryPickup(in out Actor toucher)
    {
        bool success = false;
   
        // Try to give ammo of each type, as long as we have anything to give at all
        for (int i = 0; i < AmmoTypes.Size() && Amount > 0; i++)
        {
            let at = AmmoTypes[i];
       
            // Check if we have any weapons that use this ammo type
            for (let ii = toucher.Inv; ii != null; ii = ii.Inv)
            {
                let weap = Weapon(ii);
               
                if (weap == null)
                {
                    // Not a weapon
                    continue;
                }

                Ammo aa;

                if (weap.Ammo1 is at)
                {
                    // Correct ammo1
                    aa = weap.Ammo1;
                }
                else if (weap.Ammo2 is at)
                {
                    // Correct ammo2
                    aa = weap.Ammo2;
                }
                else
                {
                    // Wrong ammo types
                    continue;
                }

                // Correct ammo. See how much we can give
                int toGive = Min(aa.MaxAmount - aa.Amount, Amount);
               
                aa.Amount += toGive;
                Amount -= toGive;
               
                // Pickup will succeed if at least 1 ammo has been given
                success = success || toGive > 0;
            }
        }

        if (success)
        {
            // If there's still some ammo left, create a residual pickup
            if (Amount > 0 && (bDropped || !ShouldRespawn()))
            {
                let newItem = Inventory(Actor.Spawn(GetClass(), Pos));

                if (newItem != null)
                {
                    newItem.Vel = Vel;
                    newItem.Angle = Angle;
                    newItem.Pitch = Pitch;
                    newItem.Roll = Roll;
                    newItem.FloatBobPhase = FloatBobPhase;
                    newItem.Amount = Amount;
                    newItem.bIgnoreSkill = true;
                    newItem.bDropped = bDropped;
                }
            }

            // OK, register pickup
            GoAwayAndDie();
        }
        else
        {
            toucher.A_Print("All my 9mm weapons are full.");
        }

        return success;
    }
}

class Loose9mm50 : Loose9mm1
{
    Default
    {
        Inventory.Amount 50;
        Inventory.PickupMessage "Loose Ammunition: 9mm bullets (x50)";
    }
}
User avatar
NeoTerraNova
Posts: 153
Joined: Tue Mar 14, 2017 5:18 pm
Location: Western North Southlandia (East Side)

Re: No idea where to begin. A "smart" item.

Post by NeoTerraNova »

OH! I didn't realize it was THAT easy...

As I said, I don't know ZScript that well, but I'm learning. That was a thing I knew "should work" but I had no idea it could be done the way you showed me. That "Vel,Angle,Pitch" set of lines there is the same sort of thing I had elsewhere for a "Press {Key} to Discard" EventHandler. I honestly didn't think it would work, here. Now, I know.

After testing, it works perfectly. Thank you SO much.

Also, I'm really digging the new Avatar. It really conveys a sense of "not this crap again" which fit perfectly with the warning you gave me earlier about not using CustomInventory.

Again, my thanks to everyone for their help and time.
User avatar
Player701
 
 
Posts: 1710
Joined: Wed May 13, 2009 3:15 am
Graphics Processor: nVidia with Vulkan support
Contact:

Re: No idea where to begin. A "smart" item.

Post by Player701 »

NeoTerraNova wrote:That "Vel,Angle,Pitch" set of lines there is the same sort of thing I had elsewhere for a "Press {Key} to Discard" EventHandler. I honestly didn't think it would work, here. Now, I know.
Regarding the "vel,angle,pitch" thing, it's mostly useful in scenarios like when a monster drops the item and it gets picked up in flight. In case of a "partial" pickup (which in this case really means just replacing the original pickup with a "residual" one) it wouldn't look good because it would seem as if the item suddenly changed its trajectory. The only lines that are absolutely needed are:
  1. setting bIgnoreSkill to true (to prevent the amount from being modified by skill factors)
  2. copying Amount (self-explanatory)
  3. copying the bDropped flag (to preserve the behavior for dropped items, e.g. dropped items get crushed and non-dropped ones don't)
NeoTerraNova wrote:Also, I'm really digging the new Avatar. It really conveys a sense of "not this crap again" which fit perfectly with the warning you gave me earlier about not using CustomInventory.
I take no credit for it - here is where it comes from. (I honestly recommend checking that stuff out if you're into point-and-click adventure games.)
User avatar
NeoTerraNova
Posts: 153
Joined: Tue Mar 14, 2017 5:18 pm
Location: Western North Southlandia (East Side)

Re: No idea where to begin. A "smart" item.

Post by NeoTerraNova »

I take no credit for it - here is where it comes from. (I honestly recommend checking that stuff out if you're into point-and-click adventure games.)
..and I am, which means I'll end up getting that game sooner or later.

Once more, thank you so much for all your help.
Post Reply

Return to “Scripting”