Setting up a keystroke handler

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
SPZ1
Posts: 324
Joined: Wed Aug 02, 2017 3:01 pm
Location: Illinois

Setting up a keystroke handler

Post by SPZ1 »

I've been experimenting with this but I'm going in circles. I put this code in my MAPINFO/GameInfo:

Code: Select all

GameInfo {
	AddEventHandlers = "KeyTest"
}
And this code in my ZScript:

Code: Select all

CLASS KeyTest : ACTOR {

}
And I get this output: Fatal: event handler class KeyTest is not derived from StaticEventHandler

Then I did this:

Code: Select all

CLASS KeyTest :  StaticEventHandler  {

}
And I got this: ZSCRIPT.lmp:ZSCRIPT.lmp, line 36: Parent class StaticEventHandler of KeyTest not accessible to ZScript version 2.3.0
How do I get started with this?
User avatar
Caligari87
Admin
Posts: 6190
Joined: Thu Feb 26, 2004 3:02 pm
Preferred Pronouns: He/Him

Re: Setting up a keystroke handler

Post by Caligari87 »

You need to declare the script version you're using at the top of your main zscript file. Most people match the engine version, so

Code: Select all

version "4.5"

#include "some/other/file.zsc"

class MyClass { }
//etc
If you don't include a version declaration, it defaults to 2.3.0 as shown in your error message. Script version 2.3.0 doesn't support many of the features you'll want to use for event handlers.

8-)
User avatar
SPZ1
Posts: 324
Joined: Wed Aug 02, 2017 3:01 pm
Location: Illinois

Re: Setting up a keystroke handler

Post by SPZ1 »

I got this far but I don't know what to do now:

Code: Select all

Map MAP01 "Crock Town and Terminal Field" {
	Cluster = 1
	LevelNum = 1
	UsePlayerStartZ
	Music = "M_MAP01"
	EventHandlers = "KeyTest"
}

Code: Select all

CLASS KeyTest : EventHandler {
	override bool InputProcess(InputEvent e){
				

		Return True;
	}	
}
User avatar
Player701
 
 
Posts: 1649
Joined: Wed May 13, 2009 3:15 am
Graphics Processor: nVidia with Vulkan support

Re: Setting up a keystroke handler

Post by Player701 »

SPZ1 wrote:I got this far but I don't know what to do now
Well, it depends on what you intend to achieve with that code.

Here's an example of an event handler that can handle pressing down a certain key (in this example, right arrow):

Code: Select all

version "4.5.0"

class TestHandler : EventHandler
{
    override bool InputProcess(InputEvent e)
    {
        // For special keys (e.g. arrows, F1-F12, mouse buttons etc.) use e.KeyScan
        // (see EDoomInputKeys in gzdoom.pk3/zscript/events.zs)
        // For normal keys, use KeyChar or KeyString
        if (e.Type == InputEvent.Type_KeyDown && e.KeyScan == InputEvent.Key_RightArrow)
        {
            // TODO: Handle input here
            //...
            
            // Remove the next line if you want the game to process input for this key normally
            return true;
        }
        
        return false;
    }
}
Note that InputProcess runs in ui scope, which means that you cannot directly affect the game state from there. To do this, you have to send a network event via SendNetworkEvent and handle it via NetworkProcess (see Events and handlers#Networking). If the key you want to handle is intended to be bindable, skip InputProcess entirely and directly use either ConsoleProcess (for ui-only events) or NetworkProcess (to manipulate the game state).

Code: Select all

class TestHandler : EventHandler
{
    override void NetworkProcess(ConsoleEvent e)
    {
        if (e.Name ~== "testevent")
        {
            // TODO: Process the event here
            // e.Player is the number of the player who triggered this event
            // Access their PlayerInfo via players[e.Player] or PlayerPawn via players[e.Player].mo
            // Note that in network games the event can be received after the player has left the game!
            // (verify that playeringame[e.Player] is true before accessing players[e.Player].mo)
        }
    }
}
The KEYCONF lump can be used to create a menu key binding that would trigger the event:

Code: Select all

addkeysection "My Mod Special Commands" MyModSpecialCommandsKeysection
addmenukey "Test Event" "netevent testevent"
User avatar
SPZ1
Posts: 324
Joined: Wed Aug 02, 2017 3:01 pm
Location: Illinois

Re: Setting up a keystroke handler

Post by SPZ1 »

Basically what I came up with is a hardcoded replacement for using KEYCONF binds. I still am where I was before where I needed a way to make the code repeat itself. I wanted to keep the code simple but maybe this is not possible? Another thing I discovered is that ACS_NamedExecute does not work in ZScript here.

Code: Select all

CLASS BankKeyRepeater : EventHandler {
	override bool InputProcess(InputEvent e){
		if (e.Type == InputEvent.Type_KeyDown && e.KeyScan == InputEvent.Key_UpArrow){
			//ACS_NamedExecute("Turn_Bank_Menu_Up___Handler", 0, 0, 0, 0);
			ACS_Execute(5, 0, 0, 0, 0);
		} else if  (e.Type == InputEvent.Type_KeyDown && e.KeyScan == InputEvent.Key_DownArrow){
			//ACS_NamedExecute("Turn_Bank_Menu_Down___Handler", 0, 0, 0, 0);
			ACS_Execute(6, 0, 0, 0, 0);
		} else if (e.Type == InputEvent.Type_KeyDown && e.KeyScan == InputEvent.Key_Enter){
			ACS_Execute(7, 0, 0, 0, 0);
		}

		if (e.Type == InputEvent.Type_KeyDown && e.KeyScan == InputEvent.Key_UpArrow){
			// Need to restart here!!	
		} else if  (e.Type == InputEvent.Type_KeyDown && e.KeyScan == InputEvent.Key_DownArrow){
			// Need to restart here!!	
		}
		

		Return False;
	}	
}
User avatar
Player701
 
 
Posts: 1649
Joined: Wed May 13, 2009 3:15 am
Graphics Processor: nVidia with Vulkan support

Re: Setting up a keystroke handler

Post by Player701 »

ACS_NamedExecute can be called from an actor class, but since EventHandler does not derive from Actor and ACS_NamedExecute is not an action special, that's probably why it isn't available there. It seems that it can be emulated with ACS_Execute though:

Code: Select all

// TODO: Replace MyScriptName with the name of your script
ACS_Execute(-int('MyScriptName'), 0, 0, 0, 0)
However, like I said before, please do note that you shouldn't manipulate the game state from within your script if you call it directly from InputProcess. This is because the script will be called only for the player whose input gets processed, and this will result in multiplayer games desyncing. You should trigger a network event via EventHandler.SendNetworkEvent and do everything from there unless your script deals with UI only.
SPZ1 wrote:I still am where I was before where I needed a way to make the code repeat itself.
Sorry, but I have no idea what you mean by that. I may be able to provide more help if you could describe in as much detail as possible how you want your code to work.
User avatar
SPZ1
Posts: 324
Joined: Wed Aug 02, 2017 3:01 pm
Location: Illinois

Re: Setting up a keystroke handler

Post by SPZ1 »

All I am trying to do is make the script repeat when entering the money amount so the player doesn't have to hit a key 150 times.

This part is in an ACS library:

Code: Select all

world int 1:TotalRupeesInPocket;
world int 2:TotalRupeesInBank;

function void Pause__Movement(bool is__){
	SetActorProperty(42, APROP_Invulnerable, is__);
	SetActorProperty(42, APROP_NoTarget, is__);

	SetHudSize(1024, 768, FALSE);
	SetFont("HowlingNightmare");

	if (is__){
		HUDMessageBold(s:"\c[White]Lock-Out:\c[Red] Activated"; HUDMSG_PLAIN, 123654, CR_WHITE, 1024.2, 768.2, 6.0);
		SetActorProperty(42, APROP_Speed, 0.0);
	}else{
		HUDMessageBold(s:"\c[White]Lock-Out:\c[Green] DeActivated"; HUDMSG_PLAIN, 123654, CR_WHITE, 1024.2, 768.2, 6.0);
		SetActorProperty(42, APROP_Speed, 1.0);
	}
}

	// Add rupees, also makes the indicator appear with 0 to add
script 255 (int amount){
	TotalRupeesInPocket += amount;

	if (TotalRupeesInPocket > 500){
		TotalRupeesInPocket = 500;
	}

	SetFont("Rupee_Numbers");
	HUDMessageBold(i:TotalRupeesInPocket,s:" :$"; 0, 9000, CR_GREEN, 1.0, 0.85, 0);
}
This part is in MAP01.acs :

Code: Select all

script 04 (void){
	ACS_NamedExecute("Turn_Bank_Menu_On", 0, 0, 0, 0);
}	

int whichItemSelected = 0;
int tempMoney = 0;
bool menuDepth = FALSE;
bool isInBank = FALSE;

script "Turn_Bank_Menu_On" (void) {
	Pause__Movement(TRUE);
	isInBank = TRUE;

	SetHudSize(1024, 768, FALSE);
	SetFont("HowlingNightmare");

	if (!menuDepth){
 
		HUDMessageBold(s:"\c[Blue]Welcome to the Crock Town Bank\n\c[White]Your Money: \c[Green]",i:TotalRupeesInBank; HUDMSG_PLAIN, 695, CR_WHITE, 128.1, 64.1, 0);

		if (whichItemSelected == 0){
			HUDMessageBold(s:"\c[Green]Deposit Money\c[White]\nWithdraw Money\nExit"; HUDMSG_PLAIN, 694, CR_WHITE, 128.1, 384.1, 0);
		} else if (whichItemSelected == 1) {
			HUDMessageBold(s:"\c[White]Deposit Money\n\c[Green]Withdraw Money\c[White]\nExit"; HUDMSG_PLAIN, 694, CR_WHITE, 128.1, 384.1, 0);
		} else if (whichItemSelected == 2) {
			HUDMessageBold(s:"\c[White]Deposit Money\nWithdraw Money\n\c[Green]Exit"; HUDMSG_PLAIN, 694, CR_WHITE, 128.1, 384.1, 0);
		}	
	} else {

		HUDMessageBold(s:"..."; HUDMSG_PLAIN, 695, CR_WHITE, 128.1, 256.1, 0.01);

		if (whichItemSelected == 0) { 
			HUDMessageBold(s:"Deposit Money: ",d:tempMoney; HUDMSG_PLAIN, 694, CR_WHITE, 128.1, 384.1, 0);
		} else if (whichItemSelected == 1){
			HUDMessageBold(s:"Withdraw Money: ",d:tempMoney; HUDMSG_PLAIN, 694, CR_WHITE, 128.1, 384.1, 0);
		}
	}
}

script 05 (void){
	if (isInBank){
		ACS_NamedExecute("Turn_Bank_Menu_Up", 0, 0, 0, 0);
	}
}

script "Turn_Bank_Menu_Up" (void){
	if (menuDepth){
		if (tempMoney+1 < TotalRupeesInPocket)
			tempMoney++;

	} else {
		whichItemSelected--;

		if (whichItemSelected == -1){
			whichItemSelected = 2;
		}
	}

	ACS_NamedExecute("Turn_Bank_Menu_On", 0, 0, 0, 0);
}


script 06 (void){
	if (isInBank){
		ACS_NamedExecute("Turn_Bank_Menu_Down", 0, 0, 0, 0);
	}

}


script "Turn_Bank_Menu_Down" (void){
	if (menuDepth){
		tempMoney--;

		if (tempMoney < 0)
			tempMoney = 0;

	} else {
		whichItemSelected++;

		if (whichItemSelected == 3){
			whichItemSelected = 0;
		}
	}

	ACS_NamedExecute("Turn_Bank_Menu_On", 0, 0, 0, 0);
}

//script "Enter_Bank_Menu" (void){
script 07 (void){
	if (!isInBank){
		Terminate;
	}

	SetHudSize(1024, 768, FALSE);
	SetFont("HowlingNightmare");
		// Exit the menu
	if (whichItemSelected == 2 && !menuDepth){
		HUDMessageBold(s:"..."; HUDMSG_PLAIN, 695, CR_WHITE, 128.1, 256.1, 0.01);
		HUDMessageBold(s:"..."; HUDMSG_PLAIN, 694, CR_WHITE, 128.1, 384.1, 0.01);
		Pause__Movement(FALSE);
		isInBank = FALSE;
		whichItemSelected = 0;
		Terminate;
	}

	if ((whichItemSelected == 0 || whichItemSelected == 1) && !menuDepth){
		menuDepth = TRUE;
	} else if (whichItemSelected == 0 && menuDepth){
		if (tempMoney > TotalRupeesInPocket){
			HUDMessageBold(s:"\c[Red]You do not have this much money in your wallet!!"; HUDMSG_PLAIN, 699, CR_WHITE, 128.1, 128.1, 4.0);
			Delay(35 * 4);
		} else {
			TotalRupeesInBank += tempMoney;
			TotalRupeesInPocket -= tempMoney;
			tempMoney = 0;
		}

		menuDepth = FALSE;
	} else if (whichItemSelected == 1 && menuDepth){
		if (tempMoney > TotalRupeesInBank){
			HUDMessageBold(s:"\c[Red]You do not have this much money in the bank!!"; HUDMSG_PLAIN, 696, CR_WHITE, 128.1, 128.1, 4.0);
			Delay(35 * 4);
		} else {
			TotalRupeesInBank -= tempMoney;
			TotalRupeesInPocket += tempMoney;
			tempMoney = 0;		
		}

		menuDepth = FALSE;
	}

	Delay(45);
	ACS_Execute(255, 0, 0, 0, 0);
	ACS_NamedExecute("Turn_Bank_Menu_On", 0, 0, 0, 0);
}
User avatar
Player701
 
 
Posts: 1649
Joined: Wed May 13, 2009 3:15 am
Graphics Processor: nVidia with Vulkan support

Re: Setting up a keystroke handler

Post by Player701 »

Sorry, I still don't understand your intentions exactly, but what I can say right away is that InputProcess is not called when keys are being held, only when a key is pressed or released. However, the latter means that you can also check for e.Type == InputEvent.Type_KeyUp and do something when the player has released a certain key.

For example: you could introduce a flag variable and have a script that runs in a loop and does something useful as long as the flag's value is true. Pressing and releasing the key will call additional scripts to toggle the value between true and false repsectively. Note that you can also use GetPlayerInput for this and implement everything entirely in ACS (especially if you want Zandronum compatibility). Failing that, you should probably still use EventHandler.SendNetworkEvent and NetworkProcess instead of calling scripts directly from the input handler. Even though your current scripts do not seem to be multiplayer-friendly (for that, you're going to need to turn your map variables into arrays), calling ACS_Execute from InputProcess will still result in a desync in multiplayer, and you probably don't want that.

Ideally though, stuff like this should be written entirely in ZScript by handling InputProcess and RenderOverlay, or perhaps even with a MENUDEF since your scripts appear to implement some sort of a multiple-choice menu. If I could see a demonstration of how it looks and works in-game, perhaps I'd be able to write a bare-bones implementation of such a menu in ZScript/MENUDEF and provide some explanation of how it works and how you can extend it. Though I'm not sure if I'm going to have a lot of free time in the following month, I'm confident that you'll find other people around here who may be willing to provide additional help with this kind of stuff.
User avatar
SPZ1
Posts: 324
Joined: Wed Aug 02, 2017 3:01 pm
Location: Illinois

Re: Setting up a keystroke handler

Post by SPZ1 »

Good news guys: I have solved this problem!! 8-)

First, I simply needed to add a delay and restart in the right position:

Code: Select all

	// Turn Menu Up
script 05 (void){
	if (isInBank){
		if (menuDepth){
			if (tempMoney+1 < TotalRupeesInPocket){
				tempMoney++;
			}

		} else {
			whichItemSelected--;

			if (whichItemSelected == -1){
				whichItemSelected = 2;
			}
		}

		ACS_NamedExecute("Turn_Bank_Menu_On", 0, 0, 0, 0);
		Delay(3);
		Restart;
	}
}
	// Turn Menu Down
script 06 (void){
	if (isInBank){
		if (menuDepth){
			tempMoney--;

			if (tempMoney < 0){
				tempMoney = 0;
			}

		} else {
			whichItemSelected++;

			if (whichItemSelected == 3){
				whichItemSelected = 0;
			}
		}

		ACS_NamedExecute("Turn_Bank_Menu_On", 0, 0, 0, 0);
		Delay(3);
		Restart;
	}
}
And then I added the KeyUp event in the right place:

Code: Select all

CLASS BankKeyRepeater : EventHandler {
	override bool InputProcess(InputEvent e){
		if (e.Type == InputEvent.Type_KeyDown && e.KeyScan == InputEvent.Key_UpArrow){
			ACS_Execute(5, 0, 0, 0, 0);
		} else if  (e.Type == InputEvent.Type_KeyDown && e.KeyScan == InputEvent.Key_DownArrow){
			ACS_Execute(6, 0, 0, 0, 0);
		} else if (e.Type == InputEvent.Type_KeyDown && e.KeyScan == InputEvent.Key_Enter){
			ACS_Execute(7, 0, 0, 0, 0);
		}

		if (e.Type == InputEvent.Type_KeyUp){
			ACS_Terminate(5, 0);
			ACS_Terminate(6, 0);
		}

		Return False;
	}	
}

Return to “Scripting”