Custom GUI EventHandler

From WiCWiki

Revision as of 20:45, 5 February 2009 by Lt. Sherpa (Talk | contribs)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search

Contents

Introduction

This is an extension of the Event Handlers tutorial, and how event handlers can be used to add custom GUI interaction in WiC. As an example, we're going to create a simple GUI that contains a background, a Yes button, and a No button. The Yes button, when clicked, will turn itself off and turn the No button on. The No button does the exact opposite. When clicked, it will hide itself and then activate the Yes button. This is a simple switch that really does nothing. A more tangible use of GUI interaction would be to create a voting system or a method of interacting with platoon organization.

Discussion thread

What you need to do first

Before you look at this tutorial, it would help immensely if you were to understand WiC's python structure and how event handlers work.

Understanding this structure, and the division between client and server commands is crucial. Also, The Client-Server Communication tutorial by Plucked is an excellent first read on understanding EventHandlers, the very basis of this tutorial.


Debugging

Also, if you're new to modding or just weren't aware of debugging, create a shortcut to your modkit's wic.exe on the desktop. Under properties, in the target area, add the -debug -dev tags to the end, so it should look like


"C:\Program Files\Sierra Entertainment\World in Conflict ModKit\wic.exe" -dev -debug


wic.common.DebugMessage( "aString" ) is useful for debugging when you have the -debug tag enabled. You can write yourself messages, such as when you need to perform checks, or if you want to print out data. A few examples:

def aMethod ():
    wic.common.DebugMessage( "This method has been called" )
    
    # method stuff

def printPlayerId( aPlayer ):
    wic.common.DebugMessage( "PlayerId: " + str( aPlayer.Id ) )


These messages are written to the wic_*username*debug.txt file, found in C:\Documents and Settings\*username*\My Documents\World in Conflict\Debug

Creating the new GUI

Before you can interact with a custom GUI, you need to make sure that there is a gui to interact with. Since this is a test, we're not going to do anything fancy.


First, open up the gui_ingame_slim.xml inside of the GUI Editor. Under the 'Screens' panel, create a new GUI screen. Rename this screen to "Test". Make sure that "Test" is the current screen, then under 'Project', right click -> add Widget. In the add Widget window, scroll up above the user templates to the native templates. Select Plate. At the bottom of the window, set widget name to "myPlate_BG". Repeat this process twice, except create two Buttons named "myButton_Yes" and "myButton_No"


I'm not going to explain the process of creating these buttons to specification, but it should look something like this in the end. I recommend reading this and this to get a better understanding of how to properly create new buttons etc...

Editor.JPG


The Python

Creating a gui is only the first step. After this, we need to make a few necessary changes to a few files, and create the eventhandler python that will listen and react to the appropriate button presses.

Editing the clientimports


If you read Plucked's Client-Server Communication tutorial (from here on, CSC), you'll recognize this:

wic.game.ClientPythonCommand( aPlayerId, "aCommandInClientImports", ( arg1, arg2, arg... ) )

ClientPythonCommand will call any method for the specified player, provided that it's found within clientimports.py, or if it's imported within clientimports. clientimports does import from wicp, but there is a problem with this. wicp is an interface for wic.player, but not wic.player.Gui. Because of this, we need to define custom methods that interface with wic.player.Gui


At the bottom of clientimports, add the following:

# Custom Commands

# GUI Screen Methods
def GuiFindGui( aGuiScreenName ):
	return wic.player.gui.FindGui( aGuiScreenName )

def GuiActivate( aGui ):
	aGui.Activate()

def GuiDeactivate( aGui ):
	aGui.Deactivate()
	
	
# Widget Methods
def WidgetSetPrefab( aWidget, aPrefab ):
	aWidget.SetPrefab( aPrefab )

def WidgetSetPosition( aWidget, aPosition ):
	aWidget.SetPosition( aPosition )
	
def WidgetSetSize( aWidget, aSize ):
	aWidget.SetSize( aSize )
	
def WidgetSetStatus( aWidget, aStatus ):
	aWidget.SetStatus( aStatus )	
	
	
# Button Methods
def ButtonSetText( aButton, aText ):
	aButton.SetText( aText )
	
	
# TextLabel Methods
def TextLabelSetText( aTextLabel, aText ):
	aTextLabel.SetText( aText )

Editing the serverimports


Editing the serverimports is simple. We just need to import the proper files. Somewhere in the imports area of serverimports.py, add the following:

# Test Gui Imports
from clientEventHandler import *
from serverEventHandler import *
I haven't tested this on an actual server, so I'm not sure if this works properly. It may be that the clientEventHandler
needs to be imported from within the clientimports, since these reactions are registerd clientside, not serverside. Will
update after testing.

Creating the Client EventHandler


If you read the CSC tutorial by Plucked, you'll understand that the client event handler is listening for client specific actions. Here is a table of known client events.


anEventName Explanation Argument 1 Argument 2
OnButtonPressed Thrown when a Button is pressed The Gui that the Button is located in The hashed name of the Button
OnWidgetLostFocus Thrown when the mouse leaves a Widget The Gui that the Widget is located in The hashed name of the Widget
OnWidgetGainedFocus Thrown when the mouse is over a Widget The Gui that the Widget is located in The hashed name of the Widget
OnGuiActivated Thrown when a Gui becomes activated The activated Gui The hashed name of the Gui
OnGuiDeactivated Thrown when a Gui becomes deactivated The deactivated Gui The hashed name of the Gui


Client event handling is actually very simple in terms of client-server communication, since all the handler can do is to report what has happened. This is done by sending a player event. Specifically, wic.player.SendPlayerEvent( "anEventStringThatYouMadeUpThatDoesNothingButIdentifyThisAsSomeCustomPlayerEvent" ). Keep in mind that SendPlayerEvent is the only method of communicating from the client to the server. You can react differently to events, but this information won't be sent to the server. For example,

if anEventName == "OnButtonPressed":
		
	aGui = someEventData[0]
	aHashedButtonName = someEventData[1]
	
	if aGui.Name == "testGUI" :
		if aHashedButtonName == wic.common.StringToInt( "myButton_ShakeCamButton" ):
			wic.player.Shake()


While this method of shaking the player camera works, there is no way for the server to process this information at a higher level. The client isn't adept at interpreting conditionals, so more complex reactions aren't possible. A more proper way to do this would be to do the following:


In the client eventhandler:

if anEventName == "OnButtonPressed":
		
	aGui = someEventData[0]
	aHashedButtonName = someEventData[1]
	
	if aGui.Name == "testGUI" :
		if aHashedButtonName == wic.common.StringToInt( "myButton_ShakeCamButton" ):
			wic.player.SendPlayerEvent( "PlayerCamShaken" )


in the server eventhandler:

if anEventName == "OnPlayerEvent":
		
	anEvent = someEventData[0]
	aPlayer = someEventData[1]
			
	if anEvent == "PlayerCamShaken":
		wic.game.ClientPythonCommand( aPlayer.Id, "Shake" )


By this method, the camera is still shook, but the server can further process this information.

Since in this tutorial we want the server to process the events, we'll be resorting to the wic.player.SendPlayerEvent( "anEventName" )

In a file named clientEventHandler.py post the following.


clientEventHandler.py

import wic
import sys


def EventHandler( anEventName, someEventData ):
	
	if anEventName == "OnButtonPressed":
		
		aGui = someEventData[0]
		aHashedButtonName = someEventData[1]
		
		if aGui.Name == "Test" :
			if aHashedButtonName == wic.common.StringToInt( "myButton_Yes" ):
				wic.player.SendPlayerEvent( "YesButtonPressed" )
				
		if aGui.Name == "Test" :
			if aHashedButtonName == wic.common.StringToInt( "myButton_No" ):
				wic.player.SendPlayerEvent( "NoButtonPressed" )
				
				
	if anEventName == "OnGuiActivated":
		
		aGui = someEventData[0]
		
		if aGui.Name == "Combat":
			wic.player.SendPlayerEvent( "CombatGUIActivated" )
				
				
	if anEventName == "OnGuiDeactivated":
		
		aGui = someEventData[0]
		
		if aGui.Name == "Combat":
			wic.player.SendPlayerEvent( "CombatGUIDeactivated" )
			
			
def ClientInit():
	wic.common.DebugMessage("**************** ClientInit ****************")
	wic.ClientEventHandler = EventHandler
	
ClientInit()

Creating the Server EventHandler


The server event handler listens to all incoming player events, and reacts accordingly. Since we're dealing with GUI's we need two initialize the GUI members, and listen to the incoming PlayerEvents. OnPlayerReady will be the event used to initialize the GUI members. This is because we know that when OnPlayerReady, a player has fully loaded the game, and there will be no chance of crashing the game. If you try to initialize the GUI members before the GUI has been set up, you'll cause a crash. OnPlayerEvent will be the custom reactions to these GUI members. In a file named serverEventHandler.py, copy the following


serverEventHandler.py

import wic
import sys
import serverimports as si

atRoundBeginning = False

supportGUI = None

testGUI = None
testBG = None
testYes = None
testNo = None


def EventHandler( anEventName, someEventData ):
	# --------------------------------
	# Setup Globals
	# --------------------------------
	global atRoundBeginning
	
	global testGUI
	
	global testBG
	global testYes
	global testNo
	
	# --------------------------------
	# EventHandler Checks
	# --------------------------------
	if anEventName == "OnPlayerReady":
		if atRoundBeginning:
			atRoundBeginning = False
			
			supportGUI = wic.player.gui.FindGui( "TacticalAids" )
			
			testGUI = wic.player.gui.FindGui( "Test" )
			testBG = wic.player.gui.FindWidget( "Test", "myPlate_BG" )
			testYes = wic.player.gui.FindButton( "Test", "myButton_Yes" )
			testNo = wic.player.gui.FindButton( "Test", "myButton_No" )
		
		
	if anEventName == "OnPlayerEvent":
		
		anEvent = someEventData[0]
		aPlayer = someEventData[1]
				
		if anEvent == "YesButtonPressed":
			wic.game.ClientPythonCommand( aPlayer.Id, "WidgetSetStatus", ( testYes, 2 ) )
			wic.game.ClientPythonCommand( aPlayer.Id, "WidgetSetStatus", ( testNo, 0 ) )
			
		if anEvent == "NoButtonPressed":
			wic.game.ClientPythonCommand( aPlayer.Id, "WidgetSetStatus", ( testYes, 0 ) )
			wic.game.ClientPythonCommand( aPlayer.Id, "WidgetSetStatus", ( testNo, 2 ) )
			
		if anEvent == "CombatGUIActivated":
			wic.game.ClientPythonCommand( aPlayer.Id, "GuiActivate", ( testGUI, ) )
			
		if anEvent == "CombatGUIDeactivated":
			wic.game.ClientPythonCommand( aPlayer.Id, "GuiDeactivate", ( testGUI, ) )

	
def ServerInit():
	wic.common.DebugMessage( "**************** ServerInit ****************" )
	wic.ServerEventHandler = EventHandler
	
	global atRoundBeginning
	atRoundBeginning = True

ServerInit()

serverEventHandler Explained

This is an explanation of the different parts of the server eventhandler, and why they are important

	if anEventName == "OnPlayerReady":
		if atRoundBeginning:
			atRoundBeginning = False	# Keep in mind that this is an assignment, not an 'is equal to' logic check
			
			supportGUI = wic.player.gui.FindGui( "TacticalAids" )
			
			testGUI = wic.player.gui.FindGui( "Test" )
			testBG = wic.player.gui.FindWidget( "Test", "myPlate_BG" )
			testYes = wic.player.gui.FindButton( "Test", "myButton_Yes" )
			testNo = wic.player.gui.FindButton( "Test", "myButton_No" )

The atRoundBeginning check is there to simply ensure that the server doesn't have to go through the process of reassigning the gui items every time a player joins.

		if anEvent == "YesButtonPressed":
			wic.game.ClientPythonCommand( aPlayer.Id, "WidgetSetStatus", ( testYes, 2 ) )
			wic.game.ClientPythonCommand( aPlayer.Id, "WidgetSetStatus", ( testNo, 0 ) )

		if anEvent == "NoButtonPressed":
			wic.game.ClientPythonCommand( aPlayer.Id, "WidgetSetStatus", ( testYes, 0 ) )
			wic.game.ClientPythonCommand( aPlayer.Id, "WidgetSetStatus", ( testNo, 2 ) )

These events are where you see functionality. When a button is pressed it will deactivate itself and activate the other button.

		if anEvent == "CombatGUIActivated":
			wic.game.ClientPythonCommand( aPlayer.Id, "GuiActivate", ( testGUI, ) )
			
		if anEvent == "CombatGUIDeactivated":
			wic.game.ClientPythonCommand( aPlayer.Id, "GuiDeactivate", ( testGUI, ) )

These events are actually very important. WiC's GUI can be annoying at times, not operating when you think it should, so you have to be very explicit about what you want. For example, we don't want this to show up when the ingame main menu is accessed, so in essence, only when the combat GUI is activated. You need to know if your GUI is going to have to mirror another GUI's personality. For example, is your GUI a feature designed to operate alongside the ingame GPS, or is it designed to operate next to the reinforcements menu. Your gui should be activated accordingly.

The Next Step

While this tutorial really doesn't produce a usable product, this is simply a tutorial on reacting to simple GUI events. The next step would be to create a more complex system, where player actions have to be processed and reacted to. An example of this is a project in development by Madmonk and I, where a voting system is used to determine the commander of a team. This commander has sole control over the TA, as the TA points are funneled to the commander. Also, this tutorial only focuses on OnPlayerEvent. There other events, such as OnCommandPointCaptured and OnTAUsed. Another proposal on the forum was to allow TA access only to the team in control of the map's airport. With a little creativity, event handlers are the next step to achieving virtually anything in WiC.

Personal tools
User Created Content