/*
Copyright (C) 1997-2001 Id Software, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/* Modifications
Copyright 2003-2004 Bytonic Software
Copyright 2010 Google Inc.
*/
package com.googlecode.gwtquake.shared.client;
import java.nio.ByteOrder;
import com.googlecode.gwtquake.shared.common.*;
import com.googlecode.gwtquake.shared.game.Commands;
import com.googlecode.gwtquake.shared.game.ConsoleVariable;
import com.googlecode.gwtquake.shared.game.UserCommand;
import com.googlecode.gwtquake.shared.sys.IN;
import com.googlecode.gwtquake.shared.util.Lib;
import com.googlecode.gwtquake.shared.util.Math3D;
/**
* Original name: CL_input
*/
public class ClientInput {
static long frameMsec;
static long oldSysFrameTime;
static ConsoleVariable cl_nodelta;
/*
* ===============================================================================
*
* KEY BUTTONS
*
* Continuous button event tracking is complicated by the fact that two
* different input sources (say, mouse button 1 and the control key) can
* both press the same button, but the button should only be released when
* both of the pressing key have been released.
*
* When a key event issues a button command (+forward, +attack, etc), it
* appends its key number as a parameter to the command so it can be matched
* up with the release.
*
* state bit 0 is the current state of the key state bit 1 is edge triggered
* on the up to down transition state bit 2 is edge triggered on the down to
* up transition
*
*
* Key_Event (int key, qboolean down, unsigned time);
*
* +mlook src time
*
* ===============================================================================
*/
static ButtonState in_klook = new ButtonState();
static ButtonState in_left = new ButtonState();
static ButtonState in_right = new ButtonState();
static ButtonState in_forward = new ButtonState();
static ButtonState in_back = new ButtonState();
static ButtonState in_lookup = new ButtonState();
static ButtonState in_lookdown = new ButtonState();
static ButtonState in_moveleft = new ButtonState();
static ButtonState in_moveright = new ButtonState();
public static ButtonState in_strafe = new ButtonState();
static ButtonState in_speed = new ButtonState();
static ButtonState in_use = new ButtonState();
static ButtonState in_attack = new ButtonState();
static ButtonState in_up = new ButtonState();
static ButtonState in_down = new ButtonState();
static int in_impulse;
static void KeyDown(ButtonState b) {
int k;
String c;
c = Commands.Argv(1);
if (c.length() > 0)
k = Lib.atoi(c);
else
k = -1; // typed manually at the console for continuous down
if (k == b.down[0] || k == b.down[1])
return; // repeating key
if (b.down[0] == 0)
b.down[0] = k;
else if (b.down[1] == 0)
b.down[1] = k;
else {
Com.Printf("Three keys down for a button!\n");
return;
}
if ((b.state & 1) != 0)
return; // still down
// save timestamp
c = Commands.Argv(2);
b.downtime = Lib.atoi(c);
if (b.downtime == 0)
b.downtime = Globals.sys_frame_time - 100;
b.state |= 3; // down + impulse down
}
static void KeyUp(ButtonState b) {
int k;
String c;
int uptime;
c = Commands.Argv(1);
if (c.length() > 0)
k = Lib.atoi(c);
else {
// typed manually at the console, assume for unsticking, so clear
// all
b.down[0] = b.down[1] = 0;
b.state = 4; // impulse up
return;
}
if (b.down[0] == k)
b.down[0] = 0;
else if (b.down[1] == k)
b.down[1] = 0;
else
return; // key up without coresponding down (menu pass through)
if (b.down[0] != 0 || b.down[1] != 0)
return; // some other key is still holding it down
if ((b.state & 1) == 0)
return; // still up (this should not happen)
// save timestamp
c = Commands.Argv(2);
uptime = Lib.atoi(c);
if (uptime != 0)
b.msec += uptime - b.downtime;
else
b.msec += 10;
b.state &= ~1; // now up
b.state |= 4; // impulse up
}
static void IN_KLookDown() {
KeyDown(in_klook);
}
static void IN_KLookUp() {
KeyUp(in_klook);
}
static void IN_UpDown() {
KeyDown(in_up);
}
static void IN_UpUp() {
KeyUp(in_up);
}
static void IN_DownDown() {
KeyDown(in_down);
}
static void IN_DownUp() {
KeyUp(in_down);
}
static void IN_LeftDown() {
KeyDown(in_left);
}
static void IN_LeftUp() {
KeyUp(in_left);
}
static void IN_RightDown() {
KeyDown(in_right);
}
static void IN_RightUp() {
KeyUp(in_right);
}
static void IN_ForwardDown() {
KeyDown(in_forward);
}
static void IN_ForwardUp() {
KeyUp(in_forward);
}
static void IN_BackDown() {
KeyDown(in_back);
}
static void IN_BackUp() {
KeyUp(in_back);
}
static void IN_LookupDown() {
KeyDown(in_lookup);
}
static void IN_LookupUp() {
KeyUp(in_lookup);
}
static void IN_LookdownDown() {
KeyDown(in_lookdown);
}
static void IN_LookdownUp() {
KeyUp(in_lookdown);
}
static void IN_MoveleftDown() {
KeyDown(in_moveleft);
}
static void IN_MoveleftUp() {
KeyUp(in_moveleft);
}
static void IN_MoverightDown() {
KeyDown(in_moveright);
}
static void IN_MoverightUp() {
KeyUp(in_moveright);
}
static void IN_SpeedDown() {
KeyDown(in_speed);
}
static void IN_SpeedUp() {
KeyUp(in_speed);
}
static void IN_StrafeDown() {
KeyDown(in_strafe);
}
static void IN_StrafeUp() {
KeyUp(in_strafe);
}
static void IN_AttackDown() {
KeyDown(in_attack);
}
static void IN_AttackUp() {
KeyUp(in_attack);
}
static void IN_UseDown() {
KeyDown(in_use);
}
static void IN_UseUp() {
KeyUp(in_use);
}
static void IN_Impulse() {
in_impulse = Lib.atoi(Commands.Argv(1));
}
/*
* =============== CL_KeyState
*
* Returns the fraction of the frame that the key was down ===============
*/
static float KeyState(ButtonState key) {
float val;
long msec;
key.state &= 1; // clear impulses
msec = key.msec;
key.msec = 0;
if (key.state != 0) {
// still down
msec += Globals.sys_frame_time - key.downtime;
key.downtime = Globals.sys_frame_time;
}
val = (float) msec / frameMsec;
if (val < 0)
val = 0;
if (val > 1)
val = 1;
return val;
}
// ==========================================================================
/*
* ================ CL_AdjustAngles
*
* Moves the local angle positions ================
*/
static void AdjustAngles() {
float speed;
float up, down;
if ((in_speed.state & 1) != 0)
speed = Globals.cls.frametime * Globals.cl_anglespeedkey.value;
else
speed = Globals.cls.frametime;
if ((in_strafe.state & 1) == 0) {
Globals.cl.viewangles[Constants.YAW] -= speed * Globals.cl_yawspeed.value * KeyState(in_right);
Globals.cl.viewangles[Constants.YAW] += speed * Globals.cl_yawspeed.value * KeyState(in_left);
}
if ((in_klook.state & 1) != 0) {
Globals.cl.viewangles[Constants.PITCH] -= speed * Globals.cl_pitchspeed.value * KeyState(in_forward);
Globals.cl.viewangles[Constants.PITCH] += speed * Globals.cl_pitchspeed.value * KeyState(in_back);
}
up = KeyState(in_lookup);
down = KeyState(in_lookdown);
Globals.cl.viewangles[Constants.PITCH] -= speed * Globals.cl_pitchspeed.value * up;
Globals.cl.viewangles[Constants.PITCH] += speed * Globals.cl_pitchspeed.value * down;
}
/*
* ================ CL_BaseMove
*
* Send the intended movement message to the server ================
*/
static void BaseMove(UserCommand cmd) {
AdjustAngles();
//memset (cmd, 0, sizeof(*cmd));
cmd.clear();
Math3D.VectorCopy(Globals.cl.viewangles, cmd.angles);
if ((in_strafe.state & 1) != 0) {
cmd.sidemove += Globals.cl_sidespeed.value * KeyState(in_right);
cmd.sidemove -= Globals.cl_sidespeed.value * KeyState(in_left);
}
cmd.sidemove += Globals.cl_sidespeed.value * KeyState(in_moveright);
cmd.sidemove -= Globals.cl_sidespeed.value * KeyState(in_moveleft);
cmd.upmove += Globals.cl_upspeed.value * KeyState(in_up);
cmd.upmove -= Globals.cl_upspeed.value * KeyState(in_down);
if ((in_klook.state & 1) == 0) {
cmd.forwardmove += Globals.cl_forwardspeed.value * KeyState(in_forward);
cmd.forwardmove -= Globals.cl_forwardspeed.value * KeyState(in_back);
}
//
// adjust for speed key / running
//
if (((in_speed.state & 1) ^ (int) (Globals.cl_run.value)) != 0) {
cmd.forwardmove *= 2;
cmd.sidemove *= 2;
cmd.upmove *= 2;
}
}
static void ClampPitch() {
float pitch;
pitch = Math3D.SHORT2ANGLE(Globals.cl.frame.playerstate.pmove.delta_angles[Constants.PITCH]);
if (pitch > 180)
pitch -= 360;
if (Globals.cl.viewangles[Constants.PITCH] + pitch < -360)
Globals.cl.viewangles[Constants.PITCH] += 360; // wrapped
if (Globals.cl.viewangles[Constants.PITCH] + pitch > 360)
Globals.cl.viewangles[Constants.PITCH] -= 360; // wrapped
if (Globals.cl.viewangles[Constants.PITCH] + pitch > 89)
Globals.cl.viewangles[Constants.PITCH] = 89 - pitch;
if (Globals.cl.viewangles[Constants.PITCH] + pitch < -89)
Globals.cl.viewangles[Constants.PITCH] = -89 - pitch;
}
/*
* ============== CL_FinishMove ==============
*/
static void FinishMove(UserCommand cmd) {
int ms;
int i;
//
// figure button bits
//
if ((in_attack.state & 3) != 0)
cmd.buttons |= Constants.BUTTON_ATTACK;
in_attack.state &= ~2;
if ((in_use.state & 3) != 0)
cmd.buttons |= Constants.BUTTON_USE;
in_use.state &= ~2;
if (Key.anykeydown != 0 && Globals.cls.key_dest == Constants.key_game)
cmd.buttons |= Constants.BUTTON_ANY;
// send milliseconds of time to apply the move
ms = (int) (Globals.cls.frametime * 1000);
if (ms > 250)
ms = 100; // time was unreasonable
cmd.msec = (byte) ms;
ClampPitch();
for (i = 0; i < 3; i++)
cmd.angles[i] = (short) Math3D.ANGLE2SHORT(Globals.cl.viewangles[i]);
cmd.impulse = (byte) in_impulse;
in_impulse = 0;
// send the ambient light level at the player's current position
cmd.lightlevel = (byte) Globals.cl_lightlevel.value;
}
/*
* ================= CL_CreateCmd =================
*/
static void CreateCmd(UserCommand cmd) {
//usercmd_t cmd = new usercmd_t();
frameMsec = Globals.sys_frame_time - oldSysFrameTime;
if (frameMsec < 1)
frameMsec = 1;
if (frameMsec > 200)
frameMsec = 200;
// get basic movement from keyboard
BaseMove(cmd);
// allow mice or other external controllers to add to the move
IN.Move(cmd);
FinishMove(cmd);
oldSysFrameTime = Globals.sys_frame_time;
//return cmd;
}
/*
* ============ CL_InitInput ============
*/
static void InitInput() {
Commands.addCommand("centerview", new ExecutableCommand() {
public void execute() {
IN.CenterView();
}
});
Commands.addCommand("+moveup", new ExecutableCommand() {
public void execute() {
IN_UpDown();
}
});
Commands.addCommand("-moveup", new ExecutableCommand() {
public void execute() {
IN_UpUp();
}
});
Commands.addCommand("+movedown", new ExecutableCommand() {
public void execute() {
IN_DownDown();
}
});
Commands.addCommand("-movedown", new ExecutableCommand() {
public void execute() {
IN_DownUp();
}
});
Commands.addCommand("+left", new ExecutableCommand() {
public void execute() {
IN_LeftDown();
}
});
Commands.addCommand("-left", new ExecutableCommand() {
public void execute() {
IN_LeftUp();
}
});
Commands.addCommand("+right", new ExecutableCommand() {
public void execute() {
IN_RightDown();
}
});
Commands.addCommand("-right", new ExecutableCommand() {
public void execute() {
IN_RightUp();
}
});
Commands.addCommand("+forward", new ExecutableCommand() {
public void execute() {
IN_ForwardDown();
}
});
Commands.addCommand("-forward", new ExecutableCommand() {
public void execute() {
IN_ForwardUp();
}
});
Commands.addCommand("+back", new ExecutableCommand() {
public void execute() {
IN_BackDown();
}
});
Commands.addCommand("-back", new ExecutableCommand() {
public void execute() {
IN_BackUp();
}
});
Commands.addCommand("+lookup", new ExecutableCommand() {
public void execute() {
IN_LookupDown();
}
});
Commands.addCommand("-lookup", new ExecutableCommand() {
public void execute() {
IN_LookupUp();
}
});
Commands.addCommand("+lookdown", new ExecutableCommand() {
public void execute() {
IN_LookdownDown();
}
});
Commands.addCommand("-lookdown", new ExecutableCommand() {
public void execute() {
IN_LookdownUp();
}
});
Commands.addCommand("+strafe", new ExecutableCommand() {
public void execute() {
IN_StrafeDown();
}
});
Commands.addCommand("-strafe", new ExecutableCommand() {
public void execute() {
IN_StrafeUp();
}
});
Commands.addCommand("+moveleft", new ExecutableCommand() {
public void execute() {
IN_MoveleftDown();
}
});
Commands.addCommand("-moveleft", new ExecutableCommand() {
public void execute() {
IN_MoveleftUp();
}
});
Commands.addCommand("+moveright", new ExecutableCommand() {
public void execute() {
IN_MoverightDown();
}
});
Commands.addCommand("-moveright", new ExecutableCommand() {
public void execute() {
IN_MoverightUp();
}
});
Commands.addCommand("+speed", new ExecutableCommand() {
public void execute() {
IN_SpeedDown();
}
});
Commands.addCommand("-speed", new ExecutableCommand() {
public void execute() {
IN_SpeedUp();
}
});
Commands.addCommand("+attack", new ExecutableCommand() {
public void execute() {
IN_AttackDown();
}
});
Commands.addCommand("-attack", new ExecutableCommand() {
public void execute() {
IN_AttackUp();
}
});
Commands.addCommand("+use", new ExecutableCommand() {
public void execute() {
IN_UseDown();
}
});
Commands.addCommand("-use", new ExecutableCommand() {
public void execute() {
IN_UseUp();
}
});
Commands.addCommand("impulse", new ExecutableCommand() {
public void execute() {
IN_Impulse();
}
});
Commands.addCommand("+klook", new ExecutableCommand() {
public void execute() {
IN_KLookDown();
}
});
Commands.addCommand("-klook", new ExecutableCommand() {
public void execute() {
IN_KLookUp();
}
});
cl_nodelta = ConsoleVariables.Get("cl_nodelta", "0", 0);
}
private static final Buffer buf = Buffer.allocate(128);
private static final UserCommand nullcmd = new UserCommand();
/*
* ================= CL_SendCmd =================
*/
static void SendCmd() {
int i;
UserCommand cmd, oldcmd;
int checksumIndex;
// build a command even if not connected
// save this command off for prediction
i = Globals.cls.netchan.outgoing_sequence & (Constants.CMD_BACKUP - 1);
cmd = Globals.cl.cmds[i];
Globals.cl.cmd_time[i] = (int) Globals.cls.realtime; // for netgraph
// ping calculation
// fill the cmd
CreateCmd(cmd);
Globals.cl.cmd.set(cmd);
if (Globals.cls.state == Constants.ca_disconnected || Globals.cls.state == Constants.ca_connecting)
return;
if (Globals.cls.state == Constants.ca_connected) {
if (Globals.cls.netchan.message.cursize != 0 || Globals.curtime - Globals.cls.netchan.last_sent > 1000)
NetworkChannel.Transmit(Globals.cls.netchan, 0, new byte[0]);
return;
}
// send a userinfo update if needed
if (Globals.userinfo_modified) {
Client.fixUpGender();
Globals.userinfo_modified = false;
Buffers.writeByte(Globals.cls.netchan.message, Constants.clc_userinfo);
Buffers.WriteString(Globals.cls.netchan.message, ConsoleVariables.Userinfo());
}
buf.clear();
buf.order(ByteOrder.LITTLE_ENDIAN);
// Buffer.Init(buf, data, data.length);
if (cmd.buttons != 0 && Globals.cl.cinematictime > 0 && !Globals.cl.attractloop
&& Globals.cls.realtime - Globals.cl.cinematictime > 1000) { // skip
// the
// rest
// of
// the
// cinematic
Screen.FinishCinematic();
}
// begin a client move command
Buffers.writeByte(buf, Constants.clc_move);
// save the position for a checksum byte
checksumIndex = buf.cursize;
Buffers.writeByte(buf, 0);
// let the server know what the last frame we
// got was, so the next message can be delta compressed
if (cl_nodelta.value != 0.0f || !Globals.cl.frame.valid || Globals.cls.demowaiting)
buf.putInt(-1);
else
buf.putInt(Globals.cl.frame.serverframe);
// send this and the previous cmds in the message, so
// if the last packet was dropped, it can be recovered
i = (Globals.cls.netchan.outgoing_sequence - 2) & (Constants.CMD_BACKUP - 1);
cmd = Globals.cl.cmds[i];
//memset (nullcmd, 0, sizeof(nullcmd));
nullcmd.clear();
Delta.WriteDeltaUsercmd(buf, nullcmd, cmd);
oldcmd = cmd;
i = (Globals.cls.netchan.outgoing_sequence - 1) & (Constants.CMD_BACKUP - 1);
cmd = Globals.cl.cmds[i];
Delta.WriteDeltaUsercmd(buf, oldcmd, cmd);
oldcmd = cmd;
i = (Globals.cls.netchan.outgoing_sequence) & (Constants.CMD_BACKUP - 1);
cmd = Globals.cl.cmds[i];
Delta.WriteDeltaUsercmd(buf, oldcmd, cmd);
// calculate a checksum over the move commands
buf.data[checksumIndex] = Com.BlockSequenceCRCByte(buf.data, checksumIndex + 1, buf.cursize - checksumIndex - 1,
Globals.cls.netchan.outgoing_sequence);
//
// deliver the message
//
NetworkChannel.Transmit(Globals.cls.netchan, buf.cursize, buf.data);
}
}