New netplay UI.

git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@2500 8092fc87-c524-0410-9efc-e669fe64eaf9
This commit is contained in:
mcmartin
2006-11-13 11:49:41 +00:00
parent 84590a8b36
commit b105684fbd
4 changed files with 579 additions and 46 deletions
+1
View File
@@ -1,4 +1,5 @@
Changes towards version 0.6:
- Netplay configuration dialog - Michael
- Better error message for 'Kernel failed to load' (#917) - Michael
- Pushbutton for connecting to netplay, in anticipation of a full
configuration dialog later - Michael
+22 -41
View File
@@ -1,42 +1,23 @@
To use Netplay you need to pass some parameters to uqm. In future versions
you should be able to set everything you need to set from within the
game itself.
--netport1 specifies the port the bottom player will connect to or accept
incoming connections on. If this parameter is not specified, 21837 will
be used.
Your firewall needs to be set up to allow TCP connections from/to
the used port.
--nethost1 specifies the name or ip number of the host to connect to for
the bottom player. If this parameter is not specified, UQM will
instead accept incoming connections.
--netport2
As --netport1, but for the top player
--nethost2
As --nethost1, but for the top player
--netdelay
The desired input delay (in frames). A keypress or keyrelease
will only take effect after this many frames. The result is that while
higher values make your ship to seem to respond more sluggishly,
they give the keypress information more time to reach the remote party.
If the game stutters, this is because it is waiting for this
keypress information to arrive, which is an indication that the
input delay is too low.
As SuperMelee runs at 24 frames per second, each frame delayed will
delay the input by about 42ms.
The delay used is the maximum of the desired value for both parties.
The default is 0. Values lower than 4 will be hardly noticable
in terms of responsiveness.
Future versions may automatically decide on the best value to use.
In SuperMelee, there is now an extra button, "NET". When you push it,
UQM will start paying attention to incoming connections or try to
establish an outgoing connection, depending on the specified
parameters. Pressing SPACE at the connection screen will cancel
connection attempts. Once a connection has been established, the
input will be listed as "NETWORK CONTROL". To abort an already
established connection, change the control scheme away from NETWORK
CONTROL. The game won't start until both parties have confirmed the
current setup. If one player moves away from the 'Battle!' button,
both parties need to reconfirm.
In SuperMelee, there is now an extra button for each side, marked
"NET". When you push it, you will get the net configuration dialog.
To have someone else connect to you, choose the first option.
Otherwise, edit the host name and choose "connect". Pressing SPACE on
the resulting connection screen will cancel connection attempts. Once
a connection has been established, the input will be listed as
"NETWORK CONTROL". To abort an already established connection, change
the control scheme away from NETWORK CONTROL. The game won't start
until both parties have confirmed the current setup. If one player
moves away from the 'Battle!' button, both parties need to reconfirm.
In addition to setting the hostname, you can also configure the port
and the input delay in frames. A keypress or keyrelease will only take
effect after this many frames. The result is that while higher values
make your ship to seem to respond more sluggishly, they give the
keypress information more time to reach the remote party. If the game
stutters, this is because it is waiting for this keypress information
to arrive, which is an indication that the input delay is too low. As
SuperMelee runs at 24 frames per second, each frame delayed will delay
the input by about 42ms. The delay used is the maximum of the desired
value for both parties. The default is 0. Values lower than 4 will be
hardly noticable in terms of responsiveness. Future versions may
automatically decide on the best value to use.
+552 -4
View File
@@ -20,32 +20,575 @@
#include "cnctdlg.h"
#include "controls.h"
#include "colors.h"
#include "setup.h"
#include "libs/graphics/widgets.h"
#include "netplay/netoptions.h"
#define MCD_WIDTH 260
#define MCD_HEIGHT 100
#define MENU_FRAME_RATE (ONE_SECOND / 20)
typedef struct connect_dialog_state
{
BOOLEAN (*InputFunc) (struct connect_dialog_state *pInputState);
COUNT MenuRepeatDelay;
DWORD NextTime;
BOOLEAN Initialized;
int which_side;
int option;
int confirmed;
} CONNECT_DIALOG_STATE;
static void DrawConnectDialog (void);
typedef CONNECT_DIALOG_STATE *PCONNECT_DIALOG_STATE;
static WIDGET_MENU_SCREEN menu;
static WIDGET_BUTTON buttons[3];
static WIDGET_SLIDER slider;
static WIDGET_TEXTENTRY texts[2];
static WIDGET *menu_widgets[] = {
(WIDGET *)&buttons[1],
(WIDGET *)&texts[0],
(WIDGET *)&buttons[0],
(WIDGET *)&slider,
(WIDGET *)&texts[1],
(WIDGET *)&buttons[2] };
static BOOLEAN done;
/* This kind of sucks, but the Button callbacks need access to the
* CONNECT_DIALOG_STATE, so we need a pointer to it */
static PCONNECT_DIALOG_STATE current_state;
static int do_connect (WIDGET *self, int event);
static int do_listen (WIDGET *self, int event);
static int do_cancel (WIDGET *self, int event);
static void
MCD_DrawMenuScreen (WIDGET *_self, int x, int y)
{
int widget_index, widget_y;
WIDGET_MENU_SCREEN *self = (WIDGET_MENU_SCREEN *)_self;
widget_y = y + 8;
for (widget_index = 0; widget_index < self->num_children; widget_index++)
{
WIDGET *c = self->child[widget_index];
(*c->draw)(c, x, widget_y);
widget_y += (*c->height)(c) + 8;
}
}
static void
MCD_DrawButton (WIDGET *_self, int x, int y)
{
WIDGET_BUTTON *self = (WIDGET_BUTTON *)_self;
COLOR oldtext;
COLOR inactive, selected;
FONT oldfont = SetContextFont (StarConFont);
FRAME oldFontEffect = SetContextFontEffect (NULL);
TEXT t;
selected = MENU_HIGHLIGHT_COLOR;
inactive = MENU_TEXT_COLOR;
t.baseline.x = 160;
t.baseline.y = y;
t.align = ALIGN_CENTER;
t.CharCount = ~0;
t.pStr = self->name;
if (widget_focus == _self)
{
oldtext = SetContextForeGroundColor (selected);
}
else
{
oldtext = SetContextForeGroundColor (inactive);
}
font_DrawText (&t);
SetContextFontEffect (oldFontEffect);
SetContextFont (oldfont);
SetContextForeGroundColor (oldtext);
(void) x;
}
static void
MCD_DrawSlider (WIDGET *_self, int x, int y)
{
WIDGET_SLIDER *self = (WIDGET_SLIDER *)_self;
COLOR oldtext;
COLOR default_color, selected;
FONT oldfont = SetContextFont (StarConFont);
FRAME oldFontEffect = SetContextFontEffect (NULL);
TEXT t;
RECT r;
int tick = (MCD_WIDTH) / 8;
default_color = MENU_TEXT_COLOR;
selected = MENU_HIGHLIGHT_COLOR;
t.baseline.x = x;
t.baseline.y = y;
t.align = ALIGN_LEFT;
t.CharCount = ~0;
t.pStr = self->category;
if (widget_focus == _self)
{
oldtext = SetContextForeGroundColor (selected);
}
else
{
oldtext = SetContextForeGroundColor (default_color);
}
font_DrawText (&t);
r.corner.x = t.baseline.x + 3 * tick;
r.corner.y = t.baseline.y - 4;
r.extent.height = 2;
r.extent.width = 3 * tick;
DrawFilledRectangle (&r);
r.extent.width = 3;
r.extent.height = 8;
r.corner.y = t.baseline.y - 7;
r.corner.x = t.baseline.x + 3 * tick + (3 * tick * (self->value - self->min) /
(self->max - self->min)) - 1;
DrawFilledRectangle (&r);
(*self->draw_value)(self, t.baseline.x + 7 * tick, t.baseline.y);
SetContextFontEffect (oldFontEffect);
SetContextFont (oldfont);
SetContextForeGroundColor (oldtext);
}
void
MCD_DrawTextEntry (WIDGET *_self, int x, int y)
{
WIDGET_TEXTENTRY *self = (WIDGET_TEXTENTRY *)_self;
COLOR oldtext;
COLOR inactive, default_color, selected;
FONT oldfont = SetContextFont (StarConFont);
FRAME oldFontEffect = SetContextFontEffect (NULL);
TEXT t;
default_color = MENU_TEXT_COLOR;
selected = MENU_HIGHLIGHT_COLOR;
inactive = MENU_TEXT_COLOR;
BatchGraphics ();
t.baseline.x = x;
t.baseline.y = y;
t.align = ALIGN_LEFT;
t.CharCount = ~0;
t.pStr = self->category;
if (widget_focus == _self)
{
oldtext = SetContextForeGroundColor (selected);
}
else
{
oldtext = SetContextForeGroundColor (default_color);
}
font_DrawText (&t);
/* Force string termination */
self->value[WIDGET_TEXTENTRY_WIDTH-1] = 0;
t.baseline.y = y;
t.CharCount = utf8StringCount (self->value);
t.pStr = self->value;
if (!(self->state & WTE_EDITING))
{ // normal or selected state
t.baseline.x = 160;
t.align = ALIGN_CENTER;
if (widget_focus == _self)
{
oldtext = SetContextForeGroundColor (selected);
}
else
{
oldtext = SetContextForeGroundColor (inactive);
}
font_DrawText (&t);
}
else
{ // editing state
COUNT i;
RECT text_r;
BYTE char_deltas[WIDGET_TEXTENTRY_WIDTH];
PBYTE pchar_deltas;
RECT r;
SIZE leading;
t.baseline.x = x + 90;
t.align = ALIGN_LEFT;
// calc background box dimensions
// XXX: this may need some tuning, especially if a
// different font is used. The font 'leading' values
// are not what they should be.
#define BOX_VERT_OFFSET 2
GetContextFontLeading (&leading);
r.corner.x = t.baseline.x - 1;
r.corner.y = t.baseline.y - leading + BOX_VERT_OFFSET;
r.extent.width = MCD_WIDTH - r.corner.x - 10;
r.extent.height = leading + 2;
TextRect (&t, &text_r, char_deltas);
#if 0
// XXX: this should potentially be used in ChangeCallback
if ((text_r.extent.width + 2) >= r.extent.width)
{ // the text does not fit the input box size and so
// will not fit when displayed later
UnbatchGraphics ();
// disallow the change
return (FALSE);
}
#endif
oldtext = SetContextForeGroundColor (selected);
DrawFilledRectangle (&r);
// calculate the cursor position and draw it
pchar_deltas = char_deltas;
for (i = self->cursor_pos; i > 0; --i)
r.corner.x += (SIZE)*pchar_deltas++;
if (self->cursor_pos < t.CharCount) /* cursor mid-line */
--r.corner.x;
if (self->state & WTE_BLOCKCUR)
{ // Use block cursor for keyboardless systems
if (self->cursor_pos == t.CharCount)
{ // cursor at end-line -- use insertion point
r.extent.width = 1;
}
else if (self->cursor_pos + 1 == t.CharCount)
{ // extra pixel for last char margin
r.extent.width = (SIZE)*pchar_deltas + 2;
}
else
{ // normal mid-line char
r.extent.width = (SIZE)*pchar_deltas + 1;
}
}
else
{ // Insertion point cursor
r.extent.width = 1;
}
// position cursor within input field rect
++r.corner.x;
++r.corner.y;
r.extent.height -= 2;
SetContextForeGroundColor (MENU_TEXT_COLOR);
DrawFilledRectangle (&r);
SetContextForeGroundColor (inactive);
font_DrawText (&t);
}
UnbatchGraphics ();
SetContextFontEffect (oldFontEffect);
SetContextFont (oldfont);
SetContextForeGroundColor (oldtext);
}
/* Text entry stuff, mostly C&Ped from setupmenu.c. Could use some
* refactoring, as redraw_menu () is the only real change. */
static BOOLEAN
OnTextEntryChange (PTEXTENTRY_STATE pTES)
{
WIDGET_TEXTENTRY *widget = (WIDGET_TEXTENTRY *) pTES->CbParam;
widget->cursor_pos = pTES->CursorPos;
if (pTES->JoystickMode)
widget->state |= WTE_BLOCKCUR;
else
widget->state &= ~WTE_BLOCKCUR;
// XXX TODO: Here, we can examine the text entered so far
// to make sure it fits on the screen, for example,
// and return FALSE to disallow the last change
return TRUE; // allow change
}
static BOOLEAN
OnTextEntryFrame (PTEXTENTRY_STATE pTES)
{
WIDGET_TEXTENTRY *widget = (WIDGET_TEXTENTRY *) pTES->CbParam;
DrawConnectDialog ();
SleepThreadUntil (pTES->NextTime);
pTES->NextTime = GetTimeCounter () + MENU_FRAME_RATE;
return TRUE; // continue
}
static int
OnTextEntryEvent (WIDGET_TEXTENTRY *widget)
{ // Going to edit the text
TEXTENTRY_STATE tes;
UNICODE revert_buf[256];
// position cursor at the end of text
widget->cursor_pos = utf8StringCount (widget->value);
widget->state = WTE_EDITING;
DrawConnectDialog ();
// make a backup copy for revert on cancel
utf8StringCopy (revert_buf, sizeof (revert_buf), widget->value);
// text entry setup
tes.Initialized = FALSE;
tes.MenuRepeatDelay = 0;
tes.NextTime = GetTimeCounter () + MENU_FRAME_RATE;
tes.BaseStr = widget->value;
tes.MaxSize = widget->maxlen;
tes.CursorPos = widget->cursor_pos;
tes.CbParam = widget;
tes.ChangeCallback = OnTextEntryChange;
tes.FrameCallback = OnTextEntryFrame;
// SetMenuSounds (0, MENU_SOUND_SELECT);
if (!DoTextEntry (&tes))
{ // editing failed (canceled) -- revert the changes
utf8StringCopy (widget->value, widget->maxlen, revert_buf);
}
else
{
if (widget->onChange)
{
(*(widget->onChange))(widget);
}
}
widget->state = WTE_NORMAL;
DrawConnectDialog ();
return TRUE; // event handled
}
/* Button response routines */
static int
do_connect (WIDGET *self, int event)
{
if (event == WIDGET_EVENT_SELECT)
{
/* These assignments are safe exactly because texts[] is file-scope) */
netplayOptions.peer[current_state->which_side].host = texts[0].value;
netplayOptions.peer[current_state->which_side].port = texts[1].value;
netplayOptions.peer[current_state->which_side].isServer = FALSE;
current_state->confirmed = TRUE;
netplayOptions.inputDelay = slider.value;
done = TRUE;
}
(void)self;
return FALSE;
}
static int
do_listen (WIDGET *self, int event)
{
if (event == WIDGET_EVENT_SELECT)
{
/* These assignments are safe exactly because texts[] is file-scope) */
netplayOptions.peer[current_state->which_side].port = texts[1].value;
netplayOptions.peer[current_state->which_side].isServer = TRUE;
netplayOptions.inputDelay = slider.value;
current_state->confirmed = TRUE;
done = TRUE;
}
(void)self;
return FALSE;
}
static int
do_cancel (WIDGET *self, int event)
{
if (event == WIDGET_EVENT_SELECT)
{
current_state->confirmed = FALSE;
done = TRUE;
}
(void)self;
return FALSE;
}
static void
CreateWidgets (void)
{
int i;
done = false;
for (i = 0; i < 3; i++)
{
buttons[i].tag = WIDGET_TYPE_BUTTON;
buttons[i].parent = NULL;
buttons[i].receiveFocus = Widget_ReceiveFocusSimple;
buttons[i].draw = MCD_DrawButton;
buttons[i].height = Widget_HeightOneLine;
buttons[i].width = Widget_WidthFullScreen;
}
buttons[0].name = "Connect to remote host";
buttons[1].name = "Wait for incoming connection";
buttons[2].name = "Cancel";
buttons[0].handleEvent = do_connect;
buttons[1].handleEvent = do_listen;
buttons[2].handleEvent = do_cancel;
menu.tag = WIDGET_TYPE_MENU_SCREEN;
menu.parent = NULL;
menu.receiveFocus = Widget_ReceiveFocusMenuScreen;
menu.draw = MCD_DrawMenuScreen;
menu.height = Widget_HeightFullScreen;
menu.width = Widget_WidthFullScreen;
menu.num_children = 6;
menu.child = menu_widgets;
menu.handleEvent = Widget_HandleEventMenuScreen;
slider.tag = WIDGET_TYPE_SLIDER;
slider.parent = NULL;
slider.handleEvent = Widget_HandleEventSlider;
slider.receiveFocus = Widget_ReceiveFocusSimple;
slider.draw = MCD_DrawSlider;
slider.height = Widget_HeightOneLine;
slider.width = Widget_WidthFullScreen;
slider.draw_value = Widget_Slider_DrawValue;
slider.min = 0;
slider.max = 9;
slider.step = 1;
slider.value = netplayOptions.inputDelay;
slider.category = "Net Delay";
for (i = 0; i < 2; i++)
{
texts[i].tag = WIDGET_TYPE_TEXTENTRY;
texts[i].parent = NULL;
texts[i].handleEvent = Widget_HandleEventTextEntry;
texts[i].receiveFocus = Widget_ReceiveFocusSimple;
texts[i].draw = MCD_DrawTextEntry;
texts[i].height = Widget_HeightOneLine;
texts[i].width = Widget_WidthFullScreen;
texts[i].handleEventSelect = OnTextEntryEvent;
texts[i].maxlen = WIDGET_TEXTENTRY_WIDTH-1;
texts[i].state = WTE_NORMAL;
texts[i].cursor_pos = 0;
}
texts[0].category = "Host";
texts[1].category = "Port";
/* We sometimes assign to these internals; cannot strncpy over self! */
if (texts[0].value != netplayOptions.peer[current_state->which_side].host)
{
strncpy (texts[0].value, netplayOptions.peer[current_state->which_side].host, texts[0].maxlen);
}
if (texts[1].value != netplayOptions.peer[current_state->which_side].port)
{
strncpy (texts[1].value, netplayOptions.peer[current_state->which_side].port, texts[1].maxlen);
}
texts[0].value[texts[0].maxlen]=0;
texts[1].value[texts[1].maxlen]=0;
menu.receiveFocus ((WIDGET *)&menu, WIDGET_EVENT_DOWN);
}
static void
DrawConnectDialog (void)
{
RECT r;
r.extent.width = MCD_WIDTH;
r.extent.height = MCD_HEIGHT;
r.corner.x = (SCREEN_WIDTH - r.extent.width) >> 1;
r.corner.y = (SCREEN_HEIGHT - r.extent.height) >> 1;
LockMutex (GraphicsLock);
DrawShadowedBox (&r, SHADOWBOX_BACKGROUND_COLOR,
SHADOWBOX_DARK_COLOR, SHADOWBOX_MEDIUM_COLOR);
menu.draw ((WIDGET *)&menu, r.corner.x + 10, r.corner.y + 10);
UnlockMutex (GraphicsLock);
}
static BOOLEAN
DoMeleeConnectDialog (PCONNECT_DIALOG_STATE state)
{
BOOLEAN changed;
if (!state->Initialized)
{
state->Initialized = TRUE;
SetDefaultMenuRepeatDelay ();
state->NextTime = GetTimeCounter ();
/* Prepare widgets, draw stuff, etc. */
CreateWidgets ();
DrawConnectDialog ();
}
/* But this is skeletal, so just end immediately */
return FALSE;
changed = TRUE;
if (PulsedInputState.menu[KEY_MENU_UP])
{
Widget_Event (WIDGET_EVENT_UP);
}
else if (PulsedInputState.menu[KEY_MENU_DOWN])
{
Widget_Event (WIDGET_EVENT_DOWN);
}
else if (PulsedInputState.menu[KEY_MENU_LEFT])
{
Widget_Event (WIDGET_EVENT_LEFT);
}
else if (PulsedInputState.menu[KEY_MENU_RIGHT])
{
Widget_Event (WIDGET_EVENT_RIGHT);
}
else if (PulsedInputState.menu[KEY_MENU_SELECT])
{
Widget_Event (WIDGET_EVENT_SELECT);
}
else if (PulsedInputState.menu[KEY_MENU_CANCEL])
{
Widget_Event (WIDGET_EVENT_CANCEL);
}
else if (PulsedInputState.menu[KEY_MENU_DELETE])
{
Widget_Event (WIDGET_EVENT_DELETE);
}
else
{
changed = FALSE;
}
if (changed)
{
DrawConnectDialog ();
}
SleepThreadUntil (state->NextTime + MENU_FRAME_RATE);
state->NextTime = GetTimeCounter ();
return !((GLOBAL (CurrentActivity) & CHECK_ABORT) ||
done);
}
BOOLEAN
@@ -56,10 +599,15 @@ MeleeConnectDialog (int side)
state.Initialized = FALSE;
state.which_side = side;
state.InputFunc = DoMeleeConnectDialog;
state.confirmed = TRUE;
current_state = &state;
DoInput ((PVOID)&state, TRUE);
return TRUE;
current_state = NULL;
return state.confirmed;
}
#endif /* NETPLAY */
+4 -1
View File
@@ -43,7 +43,7 @@ typedef enum {
NUM_WIDGET_TYPES
} WIDGET_TYPE;
#define WIDGET_TEXTENTRY_WIDTH 30
#define WIDGET_TEXTENTRY_WIDTH 50
#define WIDGET_CONTROLENTRY_WIDTH 16
typedef struct _widget {
@@ -213,4 +213,7 @@ void Widget_DrawControlEntry (WIDGET *_self, int x, int y);
void Widget_Slider_DrawValue (WIDGET_SLIDER *self, int x, int y);
/* Other implementations will need these values */
extern WIDGET *widget_focus;
#endif /* _WIDGETS_H */