zscript menu ... how to do actions from menu?

Mon Sep 20, 2021 5:37 am

Well, I started learning work with zforms. So far so good, I can set-up a menu, display it...and that's weher I end. I figured out how to make a menu with one button, but completely failed to make a menu with MORE than 1 button. I just don't know how to set it up...Would anyone help?

Here's my code:
Code:
class soaMenuHandler : soa_ZF_Handler {
    soaMenu link;
    override void buttonClickCommand(soa_ZF_Button caller, string command) {
        if ( command == "soaButton01" ) {
            //button action here
            //link.soaLabel_nadpis.setTextColor  (random [textColor] (Font.CR_BRICK, Font.CR_TEAL));// (Font.CR_BRICK, Font.CR_TEAL);
         let curmenu = Menu.GetCurrentMenu();
         if (curmenu != null) curmenu.Close();
        }
        else if ( command == "soaButton02" ) {
            //action
            link.soaLabel_nadpis.setTextColor  (random [textColor] (Font.CR_BRICK, Font.CR_TEAL));
        }
    }
}
class soaMenu : soa_ZF_GenericMenu {
   // The menu's command handler.
    // We need a command handler so we can make our menu interactable.
    soaMenuHandler handler;
    Font smallfont; // A font to use for text.
    soa_ZF_Image background; // A background image.
    soa_ZF_Button soaButton01; // A simple single-texture button.
    soa_ZF_Button soaButton02;
    soa_ZF_Label soaLabel_nadpis; // A text label.
    soa_ZF_Label soaLabel02;
    soa_ZF_Label soaLabel03;

    override void Init (Menu parent) {
        Vector2 baseRes = (320, 200);

        Super.Init(parent); // Call GenericMenu's 'Init' function to do some required initialization.

        SetBaseResolution(baseRes);

        smallfont = OptionFont(); // Get GZDoom's new options menu smallfont.

        handler = new("soaMenuHandler"); // Create an instance of the handler.
        handler.link = self; // Set the handler's "link" pointer to us.

        background = soa_ZF_Image.Create ( // Add a background.
            (0, 0),//position
            (320, 200), //size
            "graphics/soaMenu/menuBack.png",//image path/name
            soa_ZF_Image.AlignType_TopLeft
        );
        background.Pack(mainFrame); // Add the image element into the main frame.
       
        /*let boxTexture = soa_ZF_BoxTextures.CreateTexturePixels ( // Create the box image's textures.
            "graphics/soaMenu/menuBox.png",//the texture itself
            (32, 32), //top-left corner of the middle of the box
            (64, 64), //bottom-right corner of the middle of the box
            false, //scale-true, tile-false the sides
            false //scale-true, tile-false the middle
        );
        //add a box image
        let boxSize = (128, 128);
        let aBoxImage = soa_ZF_BoxImage.Create (
            ((baseRes.X - boxSize.X) / 2.0, (baseRes.Y - boxSize.Y) / 2.0), //position
            boxSize, //size
            boxTexture, //texture
            (1.0, 1.0) //scale 0.25, 0.25
        );
        aBoxImage.Pack(mainFrame); // Add the box image element into the main frame.*/

        //create the button's textures
        let buttonIdle = soa_ZF_BoxTextures.CreateSingleTexture("graphics/soaMenu/menuButtonIdle.png", true);
        let buttonHover = soa_ZF_BoxTextures.CreateSingleTexture("graphics/soaMenu/menuButtonHover.png", false);
        let buttonClick = soa_ZF_BoxTextures.CreateSingleTexture("graphics/soaMenu/menuButtonClick.png", false);

        //add a button
        soaButton01 = soa_ZF_Button.Create (
            (((baseRes.X - 32.0) / 2.0) - 50, ((baseRes.Y - 16.0) / 2.0) + 40),//position
            (32, 16), //size
            cmdHandler : handler, //command handler
            command : "soaButton01", //command string for button
            inactive: buttonIdle,
            hover: buttonHover,
            click: buttonClick
        );
        soaButton01.Pack(mainFrame);

        soaButton02 = soa_ZF_Button.Create (
            (((baseRes.X - 32.0) / 2.0) + 50, ((baseRes.Y - 16.0) / 2.0) + 40),//position
            (32, 16), //size
            cmdHandler : handler, //command handler
            command : "soaButton01", //command string for button
            inactive: buttonIdle,
            hover: buttonHover,
            click: buttonClick
        );
        soaButton02.Pack(mainFrame);

        //add a label
        soaLabel_nadpis = soa_ZF_Label.Create(
            //(soaButton01.GetPosX() / 2.0, soaButton01.GetPosY() - 40), //position
         ((baseRes.X / 2.0) - 40, soaButton01.GetPosY() - 80),
            (0, smallFont.GetHeight()),
            text: ":[ Bedroll Menu ]:", //label's text
            fnt: null, //font to use > if 'null', uses default smallfont
            wrap: false, //auto text wrap
            autoSize: true, //auto resize elements
            textColor: Font.CR_GREEN //text color
        );       
        soaLabel_nadpis.Pack(mainFrame); //add element ot mainframe

        soaLabel02 = soa_ZF_Label.Create (
            //(((baseRes.X - 32.0) / 2.0) - 120, soaButton01.GetPosY() - 15),
         (soaButton01.GetPosX() - 5, soaButton01.GetPosY() - 20),
            (0, 8), //size
            text: "Sleep in\nbedroll", //label's text
            fnt: null, //font to use
            wrap: false, //auto text wrap
            autoSize: true, //auto resize elements
            textColor: Font.CR_WHITE //text color
        );
        soaLabel02.Pack(mainFrame);

        soaLabel03 = soa_ZF_Label.Create (
            //(((baseRes.X - 32.0) / 2.0) + 40, soaButton02.GetPosY() - 15),
         (soaButton02.GetPosX() - 5, soaButton02.GetPosY() - 20),
            (0, 16),
            text: "Pack the\nbedroll", //label's text
            fnt: null, //font to use
            wrap: false, //auto text wrap
            autoSize: true, //auto resize elements
            textColor: Font.CR_WHITE //text color
        );
        soaLabel03.Pack(mainFrame);
    }
}


So, the button01 works, but the button02 peforms action assigned to button01. When I tried to set up command2 variable, I encountered the part where buttonClickCommand() has only one "command" parameter, so I cannot add second 'command2' parameter. And now I just don't know how to set it up. Would anyone provide me an example? Because, telling me what to do is not enough. I know what to do, I just don't know how to properly define it. :oops:

edit: Why I always make a post, when I'm able to solve this myself? It's the definition of button. When I copy it from the first button, and forget to change command: "soaButton01" to command:"SoaBUtton02", it sure performs the action assigned to it.

So, thank you all for your patience.

edit2: Hijacking the thread for another purpose. The above issue was solved, but, now I need to know how to actions from the menu button clicks. I unedrstand that I have to put the actions under the buttons commands in the eventhandler. But how do I set this up:
Code:
This is called from an actor placed by player. When player activates this actor, it disappears and gives player the item, which could be used to place the actor again.

override bool Used(Actor user) {
      user.GiveInventory("campFire_item", 1);
      A_StartSound("sounds/itemPickup");
      A_Print("$campfire_pickup");
      self.Destroy();
      return Super.Used(user);
   }


So, I want to know how to perform this chain of commannds to the eventhandler. I believe I have to cast the item and player, but I dont know how exactly.
And, what is expected from he button? Well, the first button will make player sleep, but I having this logic right now in ACS. But the second button should pack the bedroll (remove the bedroll actor from world and give player bedroll_item inv item). How do I set this up?

Re: zscript menu ... how to do actions from menu?

Mon Sep 20, 2021 7:26 am

The way I do it is to use a simple parser for the button command. Let's say you have two buttons with the commands $button=1 and $button=2. In the OnClick method, you then first check if IndexOf("$button=) is not -1. This tells you that the command comes from any of the buttons whose command starts with that substring. Then you split the command by "$button=" (or just "="). if the resulting array has a Size above 1, you know that something comes after the "=". So all you have to do then is access this part of the substring array (index 1), which is the button number (1 or 2, in this case).

ZScript String wiki article

You could theoretically include many more keywords and dividers, you just need to adapt the parser.

Re: zscript menu ... how to do actions from menu?

Mon Sep 20, 2021 8:47 am

ramon.dexter wrote:So, I want to know how to perform this chain of commannds to the eventhandler. I believe I have to cast the item and player, but I dont know how exactly.
And, what is expected from he button? Well, the first button will make player sleep, but I having this logic right now in ACS. But the second button should pack the bedroll (remove the bedroll actor from world and give player bedroll_item inv item). How do I set this up?

The way to do this is to set up 2 event handlers, the ZForms handler and a network event handler. (since that's the only way to affect the game from the menu)
In your ButtonClickCommand override, you would have to use EventHandler.SendNetworkEvent("name"); to call a function to the network handler from the menu.

Now, affecting another actor might be slightly harder, since you only have a pointer to the player, so I would do this like this:
- Add a pointer property in your player class, for example Actor newpointer.
- On the Used override set the player's newpointer to the actor currently being interacted with.
- Call the menu and then you can access the new pointer.

Now for the network event handler, it's a normal EventHandler that overrides the NetworkProcess virtual:
Code:
    Override void NetworkProcess(ConsoleEvent e)
    {
        let playe = PlayerBaseClass(players[e.Player].mo); //Get current player
        let newactor = playe.newpointer; //Get the newpointer of the player
        If(e.Name=="roll") //If the network call is "roll" (called with EventHandler.SendNetworkEvent("roll");)
        {
            If(newactor) //If newactor is even a thing
            {
                playe.GiveInventory("campFire_item", 1); //Give player the item,
                playe.A_StartSound("sounds/itemPickup"); //Play the sound
                playe.A_Print("$campfire_pickup"); //Print the text
                newactor.Destroy(); //And finally destroy the newactor
            }
        }
    }

I don't know if this is the ideal way to do this, but it works for me.

Re: zscript menu ... how to do actions from menu?

Mon Sep 20, 2021 10:20 am

Exactly what I was looking for! Are you reding my thoughts? :lol:

Jarewill wrote:- On the Used override set the player's newpointer to the actor currently being interacted with.


And this is the part where I fail. I don't know how to do this...

I've did what you showed me:
- placed the pointer on player base class: Actor bedrollPointer;
- then added into already existing eventhandler this:
Code:
// soamenu handler /////////////////////////////////////////////////////////
   override void NetworkProcess(ConsoleEvent e) {
        let playe = wastelandRanger(players[e.Player].mo);
        let placedBedroll = playe.bedrollPointer;
        if ( e.Name == "packBedroll" ) {
            if ( placedBedroll ) {
                playe.GiveInventory("bedroll_item", 1);
                playe.A_StartSound("sounds/itempickup");
                playe.A_Print("[ Sbalil jsi spacak! ]");
                placedBedroll.Destroy();
            }
        }
    }   
   // soamenu handler /////////////////////////////////////////////////////////

- And added the bool used to the bedroll actor:
Code:
class bedRoll : actor {
   override bool Used(actor user) {
      Menu.SetMenu("soaMenu", 0);
      let playe = wastelandRanger(player.mo); //mentioned line 98
      playe.bedrollPointer = self;
      return Super.Used(user);
   }
   Default {
      +FLATSPRITE
      +SOLID
      +USESPECIAL
      
      radius 32;
      height 8;
      scale 1;
   }   
   States {
      Spawn:
         BEDR D 1 /*A_SetSpecial(80, 2201)*/;
         Loop;
   }
}

But this code crashes gzdoom with following error:
Code:
VM execution aborted: tried to read from address zero.
Called from bedroll.Used at soa-base_v2.pk3:zscript/items/zamping.zsc, line 98
Called from PlayerPawn.PlayerThink at gzdoom.pk3:zscript/actors/player/player.zs, line 1645
Called from PlayerPawn.CheckUse [Native]


I simply don't know how to "set the player's newpointer to the actor currently being interacted with".

edit:
Modified the bedroll actor code with following:
Code:
class bedRoll : actor {
   actor thisBedroll;
   override bool Used(actor user) {
      Menu.SetMenu("soaMenu", 0);
      let playe = wastelandRanger(user);
      playe.bedrollPointer = self;
      return Super.Used(user);
   }
   Default {
      +FLATSPRITE
      +SOLID
      +USESPECIAL
      
      radius 32;
      height 8;
      scale 1;
   }   
   States {
      Spawn:
         BEDR D 1 /*A_SetSpecial(80, 2201)*/;
         Loop;
   }
}


And it works. So, again, many thanks for your wondeful advices. It helps me a lot. I'm not a programmer at all, and the zscript coding is too much complicated for me. Not that hard, but one thing could be achieved by many, many ways. Most of these are oblivious to me. So your advices allows me to understand the code, with actual examples. Many, many thanks to you.