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!)
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:
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:
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????
/*
* 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:
ZZYZX wrote:Note that this system is NOT designed to be used with ACS. What you currently have is castrated version without any way to draw anything, and using it this way (ZScript for input, ACS for drawing) is probably going to make Graf very angry so that he disables input as well.
That response is old and we can now draw with ZScript, which is awesome, but until I get my legs under me with ZScript I do fully intend to replace one Z-Windows system at a time, which does mean drawing will be done by ACS for the meantime.
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?
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.
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.
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;
}
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):
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.
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.
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?
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)
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; }
}
}
}
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:
Since this is basically a keybind the menu should show what key is assigned to the CVar.
Just like setting a keybind the user should hit Enter to select the control and then a key to set the bind.
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
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.
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.
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:
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?
/*
* 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;
}
}
Spoiler: Solved - How to compare keybind to ASCII value. The answer: don't
How do I compare a keybind to a ASCII value?
I still have some reliance on the CVar that was being bastardized (that's actually a word ) as the toggle bind, and this is the source of a bug where you can set the actual keybind to toggle on the mouse control but must now hit the key assigned to the CVar to toggle it off. So I need to completely eliminate the toggle CVar and rely solely on the actual keybind. My confusion comes from the InputProcess function that gives me KeyScan, which gives an internal value, and the UiProcess function that only gives me KeyChar, which provides an ASCII value.
InputProcess - Here I can compare the keybinding to KeyScan
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.