[ZScript]Toggle-able Mouse Control

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!)
User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.

[ZScript]Toggle-able Mouse Control

Post by Sarah »

ZScript is proving extremely difficult to break into, so I thought I'd start with a small goal, but even that has been fraught with frustration. My goal is to create interfaces just like Z-Windows that can be interacted with with the tap of a single button, i.e. toggle-able mouse control. The interfaces themselves are a gargantuan task so currently I'm focusing on just toggle-able mouse control. What I'm hoping to create is something that will allow me to toggle mouse control on and off with the push of a button and send the cursor coordinates to ACS for interpretation by Z-Windows.

First off, I created a CVar to hold the toggle bind:

Code: Select all

user string zmse_toggle = "Q";
Am I reading the wiki correctly that a user CVar will allow players in multiplayer to bind their own key for the toggle?

Next, as part of my forward-compatibility tweaks I made a new menu for Z-Windows settings; this one is shortened since it's part of my ZScript fiddling and not part of the main Z-Windows branch:

Code: Select all

OptionMenu "ZSWinOptions"
{
	Title		"Z-Windows Options"
	Control		"Cursor Toggle", zmse_toggle
}

AddOptionMenu "OptionsMenu"
{
	StaticText	""
	Submenu		"Z-Windows Options", "ZSWinOptions"
}
Now this is where I go cross-eyed. My assumption is that I need an event handler with an overridden method to read input. When the toggle bind is the received input, enable or disable the mouse. My solution was to use a bool and a short conditional to take the right action. However GZD informs me that the bool is not a modifiable value??? I can't modify a member variable from a member method????

Code: Select all

/*
*	Z-Windows ZScript Cursor Event Handler
*
*/

class zWinCursor : EventHandler
{
	//bCursorToggle;
	override void OnRegister()
	{
		Console.Printf("zWin Cursor Handler Registered!");
		//bCursorToggle = false;
		self.IsUiProcessor = true;    //This seems to freeze the player and ignore all input, forcing Alt+F4
	}
	
	override bool InputProcess (InputEvent e)
	{
		CVar cvCursorToggle = CVar.FindCVar('zmse_toggle');
		if (!bCursorToggle && e.KeyString == cvCursorToggle.GetString())
		{
			Console.Printf("Cursor Toggle, %s, was activated.", cvCursorToggle.GetString());
			//bCursorToggle = true;  // Can't be modified?
		}
		/*
		else if (bCursorToggle && e.KeyString == cvCursorToggle.GetString())
		{
			//bCursorToggle = false;
			// Other cursor stuff I have no clue about yet.
		}
		*/

		return false;
	}
}
Besides just getting the event handler going to recognize the toggle, I'm very unsure of where to go from there. How do I get the cursor coordinates? Can I call ACS from ZScript so that once I have both values (X &Y) I can send them to Z-Windows? I am quite lost here and would really appreciate some help.
Spoiler: One other thought that might get brought up:
User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.

Re: [ZScript]Toggle-able Mouse Control

Post by Sarah »

Can someone clarify the usage of KeyString when using it in the InputProcess event? I can get the above event handler to inform me that the toggle was hit, it responds to both key down and key up but that's another issue, when changing it to a UiProcess event, but that requires me to set IsUiProcessor to true, which freezes the player and nulls all input, which requires an Alt+F4 to get out of GZD. The problem I'm seeing is that KeyString usage within an InputProcess event doesn't seem to be working.

While the event is clearly firing, the end of the string is empty, which implies to me that no key was received. Bug or am I missing something?

Code: Select all

/*
*	Z-Windows ZScript Cursor Event Handler
*
*/

class zWinCursor : EventHandler
{
	override void OnRegister()
	{
		Console.Printf("zWin Cursor Handler Registered!");
	}
	
	override bool InputProcess (InputEvent e)
	{
		Console.Printf("InputProcess recieved, %s", e.KeyString);
		return false;
	}
}
User avatar
AFADoomer
Posts: 1337
Joined: Tue Jul 15, 2003 4:18 pm

Re: [ZScript]Toggle-able Mouse Control

Post by AFADoomer »

If you just want the character code, use KeyScan.

This will give you the key's letter/name if you want that directly:

Code: Select all

        String value = KeyBindings.NameKeys(e.KeyScan, 0);
KeyString isn't what you think it is... I'm not even sure what it's supposed to be, actually. It appears to be returning the DoomInputKeys enum's value for the pressed key, but since it's a String, it's using that value to give an ASCII character - so, for example, SHIFT prints an asterisk because its value in the enum is 0x2A, which is '*' in ASCII. Most of the keys don't have a DoomInputKeys value, so don't print anything.

Also, your cvar check won't work, I don't think - key bindings aren't stored in the cvar, they're an internal array... I think for what you want to do, KeyBindings.GetKeysForCommand or something similar from the menu code would need to be static - but I don't think there's a way to look up key bindings in ZScript right now.
User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.

Re: [ZScript]Toggle-able Mouse Control

Post by Sarah »

Ah, thanks, that works wonders!

So to clarify the CVar is only halfway a keybind. It's needed so there's something to compare whatever key the user has pressed to. Basically by setting the CVar to say "Q" you're saying, "The letter Q is what I want the system to recognize as the mouse control toggle."

New issue though, I noticed that the value of the string using CVar.GetString() is 0.

First, what is the correct way to define a string CVar?
This is what I'm currently doing.

Code: Select all

user string zmse_toggle = "Q";


And second, what is the correct way to retrieve the value of that string CVar? Right now I'm doing a CVar.GetString():

Code: Select all

	override bool InputProcess (InputEvent e)
	{
		CVar cvCursorToggle = CVar.FindCVar('zmse_toggle');  // <---- Is that the right way to lookup a CVar with the single quotes?
		if (cvCursorToggle)
		{
			Console.Printf("Key Pressed: %s", KeyBindings.NameKeys(e.KeyScan, 0));
			Console.Printf("Value of Cursor Toggle: %s", cvCursorToggle.GetString());
			if (!bCursorToggle && KeyBindings.NameKeys(e.KeyScan, 0) == cvCursorToggle.GetString())
			{
				// Stuff
			}
		}
		else
			Console.Printf("Could not find Cursor Toggle CVar!");
		return false;
	}
Heh, it works if you press 0 :lol:
User avatar
AFADoomer
Posts: 1337
Joined: Tue Jul 15, 2003 4:18 pm

Re: [ZScript]Toggle-able Mouse Control

Post by AFADoomer »

Everything is essentially correct... Use double quotes on the cvar name, and use ~== instead of == for the string comparison to make it case insensitive.

This works in game (doesn't include your toggle boolean):

Code: Select all

	override bool InputProcess (InputEvent e)
	{
		cvar cvCursorToggle = CVar.FindCVar("zmse_toggle");

		if (cvCursorToggle && cvCursorToggle.GetString() ~== KeyBindings.NameKeys(e.KeyScan, 0))
		{
			Console.Printf("zmse_toggle is %s", cvCursorToggle.GetString());
		}

		return false;
	}
Console output is:
zmse_toggle is Q

FYI: I also submitted a feature request to get the KeyBindings.GetKeysForCommand call made static, so it would be callable from the event handler... If this gets done, you'll be able to use real keybinds instead of the cvar solution (or someone with more knowledge of why it can't be done that way will answer in the request thread).

I'm pretty sure your MENUDEF changes won't let you actually change the binding in the way you're thinking... It'll just bind a key to the non-existent zmse_toggle console command. I could be wrong, though.
User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.

Re: [ZScript]Toggle-able Mouse Control

Post by Sarah »

Ok double quotes, I was going from an example I found that iirc was using single quotes. Looks like the feature request was shot down and thinking about it i think you're right in that the menu control is just setting a bind to nothing. I will test later today, I'm at work and on mobile so no way to test. If that is the case perhaps a menu can be created that will set the cvar as otherwise flexibility is really broken if the only was to set the cvar is by editing a lump.
User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.

Re: [ZScript]Toggle-able Mouse Control

Post by Sarah »

Ok, the basic comparison of the toggle CVar to the input key is working! At first it was not, but after deleting the ini file it did. So the problem was outside the code, and probably the lack of "~==". Somehow I misread info on that and thought it enforced case sensitivity. And after some quick testing I can confirm that the keybind menu control is not setting the CVar. So as I said in the paragraph above, I'm going to look into creating a menu that will allow users to set the CVar.

All in all, over one hurdle, a mountain yet to climb.

Lastly, I haven't gotten an answer to this: Why can't I modify the toggle boolean/How do I modify the toggle boolean? Attempting to set the bool in InputProcess causes a "must be modifiable value" error.

That feeling of learning a new language...joy mixed with infuriating rage?
User avatar
AFADoomer
Posts: 1337
Joined: Tue Jul 15, 2003 4:18 pm

Re: [ZScript]Toggle-able Mouse Control

Post by AFADoomer »

I know the feeling well...

I don't think the feature request has been No'd, yet - Graf's response was more toward Nash's post.

I had issues with modifying variables from inside of the InputProcess function - I think it runs in ui scope or something, and can't modify a bunch of variables that I wanted to be able to modify.

Just as an experiment, I implemented a global zoom function as an event handler, using the same keybind style as this. I ended up dumping keypress info to a network event because I couldn't do what I wanted within the InputProcess function, then added some handling in WorldTick to let me see how long a key was held down. This let me implement a zoom feature that zooms the view in gradually while you hold down the key, and stops at the current zoom level when you release the key. Pressing the key again resets the zoom level to default.

I'm still experimenting with trying to make some kind of generic in-game keypress handling that could be handled with a switch/case or something like that.

(other than the event handler name, this should work with your existing zsme_toggle keybind, if you want to demo it)

Code: Select all

class TrekEventHandler : StaticEventHandler
{
    bool pressed[256]; // Array used to track what keys are pressed (index in array is ASCII code of key)
    int holdtime[256]; // Array used to track how long keys are pressed (index in array is ASCII code of key)

    // Used by zoom code to track some self-explanatory info
    double defaultFOV; 
    bool bZoomed;

    override void WorldLoaded(WorldEvent e)
    {
        // Save the startup FOV
        defaultFOV = players[consoleplayer].FOV;
    }

    override void WorldUnloaded(WorldEvent e)
    {
        // Restore the startup FOV
        players[consoleplayer].SetFOV(defaultFOV);
    }

    // Gets the keypress, then sends the ASCII character code and type info as a network event
    override bool InputProcess(InputEvent e)
    {
        EventHandler.SendNetworkEvent("keypress", e.KeyChar, e.Type);

        return false;
    }

    // Gets the network event and sets that key as pressed or not pressed in the pressed[] array
    override void NetworkProcess(ConsoleEvent e)
    {
        if (e.Name == "keypress" && e.Player == consoleplayer)
        {
            int keychar = e.args[0];

            if (e.args[1] == InputEvent.Type_KeyDown) { pressed[keychar] = true; }
            else { pressed[keychar] = false; }
        }
    }

    // Gets the first character of a string cvar and returns the lowercase version as an ASCII code
    // Ideally, this will be replaced by some GetKeysForCommand handling
    int GetBoundKey(String cvarname)
    {
        cvar lookup = CVar.FindCVar(cvarname);

        if (lookup)
        {
            String keyname = lookup.GetString();
            keyname.ToLower();

            return keyname.CharCodeAt(0);
        }

        return 0;
    }

    override void WorldTick()
    {
        int zoom = GetBoundKey("zmse_toggle");

        if (zoom)
        {
            if (pressed[zoom])
            {
                double targetFOV;

                if (holdtime[zoom] == 0 && players[consolePlayer].FOV != defaultFOV) // Detect second press to reset the zoom amount
                {
                    targetFOV = defaultFOV;
                    pressed[zoom] = false;
                    bZoomed = false;
                }
                else // Increment the FOV based on how long the key is being held down
                {
                    targetFOV = clamp(defaultFOV - holdtime[zoom] * 3, 10, defaultFOV);
                    bZoomed = true;
                }

                // Actually sets the player's FOV
                players[consolePlayer].SetFOV(targetFOV);
            }

            if (bZoomed)
            {
                //Do some aesthetic stuff...
            }
        }

        // Increment through all of the keys and, if they are currently pressed, increment the holdtime[] counter.
        // If the key's not being pressed, reset the counter to 0.
        // This is what lets you see how long a key has been held down.
        for (int i = 0; i < pressed.Size(); i++)
        {
            if (pressed[i]) { holdtime[i]++; }
            else { holdtime[i] = 0; }
        }
    }
} 
User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.

Re: [ZScript]Toggle-able Mouse Control

Post by Sarah »

That's a good example, I think I'll need WorldTick for this next step. What I'm trying to do is create a menu that will let the user set the cursor toggle CVar. I've been studying a menu Nash created that gives the player health and I've gotten the menu added to Options and the OptionMenuItem displaying.

My current problems are:
  1. Since this is basically a keybind the menu should show what key is assigned to the CVar.
  2. Just like setting a keybind the user should hit Enter to select the control and then a key to set the bind.
  3. A StaticText prompt should pop up with instructions. <-- Maybe not necessary as the Controls menu is pretty self-explanatory. I think I can solve this one myself with more fiddling but tips are welcome.
Current code is below, other than the menu I'm not sure if anything else is right or working. Also thanks for your help thus far AFADoomer :D

Code: Select all

/*
*	Z-Windows ZScript Cursor Bind Menu Classes
*
*/

class zWinCursorBindMenu : EventHandler
{
	override void NetworkProcess(ConsoleEvent ev)
	{
		if (ev.Name == "SetCursorCVar")
		{
			CVar cvCursorToggle = CVar.FindCVar("zmse_toggle");
			if (cvCursorToggle)
			{
				Console.Printf("Setting Cursor Toggle CVar to %s", KeyBindings.NameKeys(ev.args[0], 0));
				cvCursorToggle.SetString(KeyBindings.NameKeys(ev.args[0], 0));
			}
		}
	}
}

class OptionMenuItemZMSEToggle : OptionMenuItem
{
	OptionMenuItemZMSEToggle Init(string label = "", string command = "")
	{
		CVar cvCursorToggle = CVar.FindCVar("zmse_toggle");
		if (cvCursorToggle)
			Super.Init(label, cvCursorToggle.GetString());
		else
			Super.Init(label, command);
		return self;
	}
	
	override bool MenuEvent (int mkey, bool fromcontroller)
	{
		if (mkey == Menu.MKEY_Enter)
		{
			zWinCursorBindMenu.SendNetworkEvent("SetCursorCVar");
			Menu.MenuSound("Menu/Activate");
			return true;
		}
		
		return Super.MenuEvent(mkey, fromcontroller);
	}
	
	override int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected)
	{
		drawLabel(indent, y, selected ? OptionMenuSettings.mFontColorSelection : OptionMenuSettings.mFontColorMore);
		return indent;
	}
}
User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.

Re: [ZScript]Toggle-able Mouse Control

Post by Sarah »

Can someone please explain the Name type?

From "optionmenuitems.txt" in the GZD pk3:

Code: Select all

	OptionMenuItemOption Init(String label, Name command, Name values, CVar graycheck = null, int center = 0)
	{
		Super.Init(label, command, values, graycheck, center);
		mCVar = CVar.FindCVar(mAction);
		return self;
	}
If I'm reading the file correctly, the OptionMenuItemOption class can be used to set a CVar via a menu. Can someone clarify usage? Thanks :)
User avatar
AFADoomer
Posts: 1337
Joined: Tue Jul 15, 2003 4:18 pm

Re: [ZScript]Toggle-able Mouse Control

Post by AFADoomer »

OK, so Graf pointed out the Bindings global in my request thread... So keybindings *are* exposed to us in ZScript already.

Code: Select all

        int key1, key2;
        [key1, key2] = Bindings.GetKeysForCommand(bindname);
 
So, to get the first bound key, use:

Code: Select all

		int toggle = Bindings.GetKeysForCommand("zsme_toggle"); // Doesn't handle double bindings
and set up a normal keybind and default for zsme_toggle in KEYCONF.

Make sure you also add a blank alias (alias zsme_toggle "") to your KEYCONF file, so that the engine actually has something to bind the key to.
ZzZombo
Posts: 317
Joined: Mon Jul 16, 2012 2:02 am

Re: [ZScript]Toggle-able Mouse Control

Post by ZzZombo »

GetKeysForCommand
Something tells me it DOES handle double bindings just fine, you need to assign it to more than one variable at the same time tho.
User avatar
AFADoomer
Posts: 1337
Joined: Tue Jul 15, 2003 4:18 pm

Re: [ZScript]Toggle-able Mouse Control

Post by AFADoomer »

Of course it does. The code I posted clearly only assigns it to one variable, though, so that one line of code doesn't handle double bindings. Hence, "To get the first bound key" in my comment.
User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.

Re: [ZScript]Toggle-able Mouse Control

Post by Sarah »

Well good news! I have toggle-able mouse control mostly working!

One issue I have is that whenever I hit the bind the console spits "zmse_toggle is Q". Is there a way to suppress that?

The next step is capturing the mouse coordinates and pushing them to Z-Windows. The first step, capturing the coordinates, looks simple enough, but I'm not sure how to go about getting them to the ACS. Is the 35 tic/sec rate of ACS going to be an issue?

Here's the event handler code. I am wondering if I can get it compacted into one handler instead of two, so this may change but is what I'm currently working with.

Code: Select all

/*
*	Z-Windows ZScript Cursor Event Handler
*
*/

class zWinCursorToggle : EventHandler
{
	override void OnRegister()
	{
		Console.Printf("zWin Cursor Toggle Handler Registered!");
	}
	
	override bool InputProcess (InputEvent ev)
	{
		if (Bindings.GetKeysForCommand("zmse_toggle") == ev.KeyScan &&
			ev.Type == InputEvent.Type_KeyUp)
		{
			zWinCursorControl.SendNetworkEvent("zToggle_On");
		}
		return false;
	}
}

class zWinCursorControl : EventHandler
{
	override void OnRegister()
	{
		Console.Printf("zWin Cursor Control Handler Registered!");
	}
	
	override bool UiProcess(UiEvent ev)
	{
		CVar cvCursorToggle = CVar.FindCVar("zmse_toggle");
		if (cvCursorToggle)
		{
			if (cvCursorToggle.GetString() ~== ev.KeyString &&
				ev.Type == UiEvent.Type_KeyUp)
			{
				SendNetworkEvent("zToggle_Off");
			}
		}
		return false;
	}
	
	override void NetworkProcess(ConsoleEvent ev)
	{
		if (ev.Name == "zToggle_On")
		{
			self.IsUiProcessor = true;
			self.RequireMouse = true;
		}
		else if (ev.Name == "zToggle_Off")
		{
			self.IsUiProcessor = false;
			self.RequireMouse = false;
		}
	}
}

User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.

Re: [ZScript]Toggle-able Mouse Control

Post by Sarah »

Well I'm sure some have seen the little teaser trailer I threw together in my excitement over getting mouse control working. There's a couple of bugs yet to be figured out and a couple of questions I still have.

Questions first:
  1. What is the best way to get the player's number within an actor?

    To clarify, the event handlers store the mouse coordinates in a global variable class. There should be a unique instance of this class for each player. When sending the coordinates to Z-Windows (the ACS part), the player has to locate their "Mouse Coordinate Storage" instance and call a script, supplying the coordinates as arguments. This all works good an well, and I've even set up a quick and dirty script to supply the player their number, which is where the question comes in. How do I do this via ZScript so I can drop that little dirty script?

    Here's the player's code:

    Code: Select all

    /*
    *	Z-Windows ZScript DoomPlayer
    *
    */
    
    
    // =========== class ZSWinPlayer : DoomPlayer ===========
    // - This is an actor class inheriting from the DoomPlayer and replacing the Spawn state
    class ZSWinPlayer : DoomPlayer
    {	
    	zWinGlobalMousePosition mcs;
    	int playNum;
    	bool bNumSet;
    	
    	States
    	{
    		Spawn:
    			PLAY A 1;
    			TNT1 A 0
    			{
    				// Setup variables
    				let zDefs = zWinDefs.Get();
    				CVar cvZDebug = CVar.FindCVar(zDefs.zcv_Debug);
    				if (!bNumSet)
    				{
    					// Is there a way to do this via ZScript?
    					playNum = ACS_NamedExecuteWithResult("getPlayerNumber", 0);
    					bNumSet = true;
    				}
    				
    				if (mcs == null)
    				{
    					if (cvZDebug.GetInt() == true)
    						Console.Printf("Player %i: Mouse coordinate storage is null, searching.", playNum);
    					mcs = zWinGlobalMousePosition.Find(playNum);
    				}
    				
    				// Check for working MCS and mouse is toggled, then send coordinates to zWin
    				if (mcs && mcs.bmseToggle == true)
    					ACS_NamedExecute("thisIsATestScriptCall", 0, mcs.zmse_CoordX, mcs.zmse_CoordY);
    				// MCS doesn't exist so make a new one.
    				else if (mcs == null)
    				{
    					if (cvZDebug.GetInt() == true)
    						Console.Printf("Player %i: No mouse coordinate storage class found, making new.", playNum);
    					mcs = new("zWinGlobalMousePosition").Init(playNum);
    				}
    			}
    			Loop;
    	}
    }
  2. Spoiler: Solved - How to compare keybind to ASCII value. The answer: don't
That addresses the questions and one of the bugs, the other bug involves the player states but isn't supper important so I'll skip it for now. Thanks!


Update
I've figured out a solution for question 2, so no more CVar bastardization! As the spoiler hiding question 2 says, don't bother comparing the keybind to an ASCII value, some quick console logging showed the values are very different, so instead use KeyString and convert the binding to a string for comparison.

Code: Select all

	override bool UiProcess(UiEvent ev)
	{
		SendNetworkEvent(zDefs.zevn_MsePos, ev.MouseX, ev.MouseY);
		
		if (KeyBindings.NameKeys(Bindings.GetKeysForCommand(zDefs.zkb_MseTgl), 0) ~== ev.KeyString &&
			ev.Type == UiEvent.Type_KeyUp)
		{
			SendNetworkEvent(zDefs.zevn_TglOff);
		}
		return false;
	}

Return to “Scripting”