1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 */
20 // cl.input.c -- builds an intended movement command to send to the server
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include "client.h"
27
28 cvar_t *cl_nodelta;
29
30 extern unsigned sys_frame_time;
31 unsigned frame_msec;
32 unsigned old_sys_frame_time;
33
34 /*
35 ===============================================================================
36
37 KEY BUTTONS
38
39 Continuous button event tracking is complicated by the fact that two different
40 input sources (say, mouse button 1 and the control key) can both press the
41 same button, but the button should only be released when both of the
42 pressing key have been released.
43
44 When a key event issues a button command (+forward, +attack, etc), it appends
45 its key number as a parameter to the command so it can be matched up with
46 the release.
47
48 state bit 0 is the current state of the key
49 state bit 1 is edge triggered on the up to down transition
50 state bit 2 is edge triggered on the down to up transition
51
52
53 Key_Event (int key, qboolean down, unsigned time);
54
55 +mlook src time
56
57 ===============================================================================
58 */
59
60
61 kbutton_t in_klook;
62 kbutton_t in_left, in_right, in_forward, in_back;
63 kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright;
64 kbutton_t in_strafe, in_speed, in_use, in_attack, in_attack2;
65 kbutton_t in_up, in_down;
66
67 int in_impulse;
68
69
KeyDown(kbutton_t * b)70 void KeyDown (kbutton_t *b)
71 {
72 int k;
73 char *c;
74
75 c = Cmd_Argv(1);
76 if (c[0])
77 k = atoi(c);
78 else
79 k = -1; // typed manually at the console for continuous down
80
81 if (k == b->down[0] || k == b->down[1])
82 return; // repeating key
83
84 if (!b->down[0])
85 b->down[0] = k;
86 else if (!b->down[1])
87 b->down[1] = k;
88 else
89 {
90 Com_Printf ("Three keys down for a button!\n");
91 return;
92 }
93
94 if (b->state & 1)
95 return; // still down
96
97 // save timestamp
98 c = Cmd_Argv(2);
99 b->downtime = atoi(c);
100 if (!b->downtime)
101 b->downtime = sys_frame_time - 100;
102
103 b->state |= 1 + 2; // down + impulse down
104 }
105
KeyUp(kbutton_t * b)106 void KeyUp (kbutton_t *b)
107 {
108 int k;
109 char *c;
110 unsigned uptime;
111
112 c = Cmd_Argv(1);
113 if (c[0])
114 k = atoi(c);
115 else
116 { // typed manually at the console, assume for unsticking, so clear all
117 b->down[0] = b->down[1] = 0;
118 b->state = 4; // impulse up
119 return;
120 }
121
122 if (b->down[0] == k)
123 b->down[0] = 0;
124 else if (b->down[1] == k)
125 b->down[1] = 0;
126 else
127 return; // key up without coresponding down (menu pass through)
128 if (b->down[0] || b->down[1])
129 return; // some other key is still holding it down
130
131 if (!(b->state & 1))
132 return; // still up (this should not happen)
133
134 // save timestamp
135 c = Cmd_Argv(2);
136 uptime = atoi(c);
137 if (uptime)
138 b->msec += uptime - b->downtime;
139 else
140 b->msec += 10;
141
142 b->state &= ~1; // now up
143 b->state |= 4; // impulse up
144 }
145
IN_KLookDown(void)146 void IN_KLookDown (void) {KeyDown(&in_klook);}
IN_KLookUp(void)147 void IN_KLookUp (void) {KeyUp(&in_klook);}
IN_UpDown(void)148 void IN_UpDown(void) {KeyDown(&in_up);}
IN_UpUp(void)149 void IN_UpUp(void) {KeyUp(&in_up);}
IN_DownDown(void)150 void IN_DownDown(void) {KeyDown(&in_down);}
IN_DownUp(void)151 void IN_DownUp(void) {KeyUp(&in_down);}
IN_LeftDown(void)152 void IN_LeftDown(void) {KeyDown(&in_left);}
IN_LeftUp(void)153 void IN_LeftUp(void) {KeyUp(&in_left);}
IN_RightDown(void)154 void IN_RightDown(void) {KeyDown(&in_right);}
IN_RightUp(void)155 void IN_RightUp(void) {KeyUp(&in_right);}
IN_ForwardDown(void)156 void IN_ForwardDown(void) {KeyDown(&in_forward);}
IN_ForwardUp(void)157 void IN_ForwardUp(void) {KeyUp(&in_forward);}
IN_BackDown(void)158 void IN_BackDown(void) {KeyDown(&in_back);}
IN_BackUp(void)159 void IN_BackUp(void) {KeyUp(&in_back);}
IN_LookupDown(void)160 void IN_LookupDown(void) {KeyDown(&in_lookup);}
IN_LookupUp(void)161 void IN_LookupUp(void) {KeyUp(&in_lookup);}
IN_LookdownDown(void)162 void IN_LookdownDown(void) {KeyDown(&in_lookdown);}
IN_LookdownUp(void)163 void IN_LookdownUp(void) {KeyUp(&in_lookdown);}
IN_MoveleftDown(void)164 void IN_MoveleftDown(void) {KeyDown(&in_moveleft);}
IN_MoveleftUp(void)165 void IN_MoveleftUp(void) {KeyUp(&in_moveleft);}
IN_MoverightDown(void)166 void IN_MoverightDown(void) {KeyDown(&in_moveright);}
IN_MoverightUp(void)167 void IN_MoverightUp(void) {KeyUp(&in_moveright);}
168
IN_SpeedDown(void)169 void IN_SpeedDown(void) {KeyDown(&in_speed);}
IN_SpeedUp(void)170 void IN_SpeedUp(void) {KeyUp(&in_speed);}
IN_StrafeDown(void)171 void IN_StrafeDown(void) {KeyDown(&in_strafe);}
IN_StrafeUp(void)172 void IN_StrafeUp(void) {KeyUp(&in_strafe);}
173
IN_AttackDown(void)174 void IN_AttackDown(void) {KeyDown(&in_attack);}
IN_AttackUp(void)175 void IN_AttackUp(void) {KeyUp(&in_attack);}
176
177 //alt fire
IN_Attack2Down(void)178 void IN_Attack2Down(void) {KeyDown(&in_attack2);}
IN_Attack2Up(void)179 void IN_Attack2Up(void) {KeyUp(&in_attack2);}
180
IN_UseDown(void)181 void IN_UseDown (void) {KeyDown(&in_use);}
IN_UseUp(void)182 void IN_UseUp (void) {KeyUp(&in_use);}
183
IN_Impulse(void)184 void IN_Impulse (void) {in_impulse=atoi(Cmd_Argv(1));}
185
186 /*
187 ===============
188 CL_KeyState
189
190 Returns the fraction of the frame that the key was down
191 ===============
192 */
CL_KeyState(kbutton_t * key)193 float CL_KeyState (kbutton_t *key)
194 {
195 float val;
196 int msec;
197
198 key->state &= 1; // clear impulses
199
200 msec = key->msec;
201 key->msec = 0;
202
203 if (key->state)
204 { // still down
205 msec += sys_frame_time - key->downtime;
206 key->downtime = sys_frame_time;
207 }
208
209 #if 0
210 if (msec)
211 {
212 Com_Printf ("%i ", msec);
213 }
214 #endif
215
216 val = (float)msec / frame_msec;
217 if (val < 0)
218 val = 0;
219 if (val > 1)
220 val = 1;
221
222 return val;
223 }
224
225
226
227
228 //==========================================================================
229
230 cvar_t *cl_upspeed;
231 cvar_t *cl_forwardspeed;
232 cvar_t *cl_sidespeed;
233
234 cvar_t *cl_yawspeed;
235 cvar_t *cl_pitchspeed;
236
237 cvar_t *cl_run;
238
239 cvar_t *cl_anglespeedkey;
240
241
242 /*
243 ================
244 CL_AdjustAngles
245
246 Moves the local angle positions
247 ================
248 */
CL_AdjustAngles(void)249 void CL_AdjustAngles (void)
250 {
251 float speed;
252 float up, down;
253
254 if (in_speed.state & 1)
255 speed = cls.frametime * cl_anglespeedkey->value;
256 else
257 speed = cls.frametime;
258
259 if (!(in_strafe.state & 1))
260 {
261 cl.viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right);
262 cl.viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left);
263 }
264 if (in_klook.state & 1)
265 {
266 cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_forward);
267 cl.viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_back);
268 }
269
270 up = CL_KeyState (&in_lookup);
271 down = CL_KeyState(&in_lookdown);
272
273 cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * up;
274 cl.viewangles[PITCH] += speed*cl_pitchspeed->value * down;
275 }
276
277 /*
278 ================
279 CL_BaseMove
280
281 Send the intended movement message to the server
282 ================
283 */
CL_BaseMove(usercmd_t * cmd)284 void CL_BaseMove (usercmd_t *cmd)
285 {
286 CL_AdjustAngles ();
287
288 memset (cmd, 0, sizeof(*cmd));
289
290 VectorCopy (cl.viewangles, cmd->angles);
291 if (in_strafe.state & 1)
292 {
293 cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_right);
294 cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_left);
295 }
296
297 cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_moveright);
298 cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_moveleft);
299
300 cmd->upmove += cl_upspeed->value * CL_KeyState (&in_up);
301 cmd->upmove -= cl_upspeed->value * CL_KeyState (&in_down);
302
303 if (! (in_klook.state & 1) )
304 {
305 cmd->forwardmove += cl_forwardspeed->value * CL_KeyState (&in_forward);
306 cmd->forwardmove -= cl_forwardspeed->value * CL_KeyState (&in_back);
307 }
308
309 //
310 // adjust for speed key / running
311 //
312 if (!cl.tactical && ( (in_speed.state & 1) ^ cl_run->integer ))
313 {
314 cmd->forwardmove *= 2;
315 cmd->sidemove *= 2;
316 cmd->upmove *= 2;
317 }
318 }
319
CL_ClampPitch(void)320 void CL_ClampPitch (void)
321 {
322 float pitch;
323
324 pitch = SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[PITCH]);
325 if (pitch > 180)
326 pitch -= 360;
327
328 if (cl.viewangles[PITCH] + pitch < -360)
329 cl.viewangles[PITCH] += 360; // wrapped
330 if (cl.viewangles[PITCH] + pitch > 360)
331 cl.viewangles[PITCH] -= 360; // wrapped
332
333 if (cl.viewangles[PITCH] + pitch > 89)
334 cl.viewangles[PITCH] = 89 - pitch;
335 if (cl.viewangles[PITCH] + pitch < -89)
336 cl.viewangles[PITCH] = -89 - pitch;
337 }
338
339 /*
340 ==============
341 CL_FinishMove
342 ==============
343 */
CL_FinishMove(usercmd_t * cmd)344 void CL_FinishMove (usercmd_t *cmd)
345 {
346 int ms;
347 int i;
348
349 //
350 // figure button bits
351 //
352 if ( in_attack.state & 3 )
353 cmd->buttons |= BUTTON_ATTACK;
354 in_attack.state &= ~2;
355
356 //alt fire
357 if ( in_attack2.state & 3 )
358 cmd->buttons |= BUTTON_ATTACK2;
359 in_attack2.state &= ~2;
360
361 if (in_use.state & 3)
362 cmd->buttons |= BUTTON_USE;
363 in_use.state &= ~2;
364
365 if (anykeydown && cls.key_dest == key_game)
366 cmd->buttons |= BUTTON_ANY;
367
368 // send milliseconds of time to apply the move
369 ms = cls.frametime * 1000;
370 if (ms > 250)
371 ms = 100; // time was unreasonable
372 cmd->msec = ms;
373
374 CL_ClampPitch ();
375 for (i=0 ; i<3 ; i++)
376 cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]);
377
378 cmd->impulse = in_impulse;
379 in_impulse = 0;
380 }
381
382 /*
383 =================
384 CL_CreateCmd
385 =================
386 */
CL_CreateCmd(void)387 usercmd_t CL_CreateCmd (void)
388 {
389 usercmd_t cmd;
390
391 frame_msec = sys_frame_time - old_sys_frame_time;
392 if (frame_msec < 1)
393 frame_msec = 1;
394 if (frame_msec > 200)
395 frame_msec = 200;
396
397 // get basic movement from keyboard
398 CL_BaseMove (&cmd);
399
400 // allow mice or other external controllers to add to the move
401 IN_Move (&cmd);
402 IN_JoyMove (&cmd);
403
404 CL_FinishMove (&cmd);
405
406 old_sys_frame_time = sys_frame_time;
407
408 //cmd.impulse = cls.framecount;
409
410 return cmd;
411 }
412
413
IN_CenterView(void)414 void IN_CenterView (void)
415 {
416 cl.viewangles[PITCH] = -SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[PITCH]);
417 }
418
419 /*
420 ============
421 CL_InitInput
422 ============
423 */
CL_InitInput(void)424 void CL_InitInput (void)
425 {
426 Cmd_AddCommand ("centerview",IN_CenterView);
427
428 Cmd_AddCommand ("+moveup",IN_UpDown);
429 Cmd_AddCommand ("-moveup",IN_UpUp);
430 Cmd_AddCommand ("+movedown",IN_DownDown);
431 Cmd_AddCommand ("-movedown",IN_DownUp);
432 Cmd_AddCommand ("+left",IN_LeftDown);
433 Cmd_AddCommand ("-left",IN_LeftUp);
434 Cmd_AddCommand ("+right",IN_RightDown);
435 Cmd_AddCommand ("-right",IN_RightUp);
436 Cmd_AddCommand ("+forward",IN_ForwardDown);
437 Cmd_AddCommand ("-forward",IN_ForwardUp);
438 Cmd_AddCommand ("+back",IN_BackDown);
439 Cmd_AddCommand ("-back",IN_BackUp);
440 Cmd_AddCommand ("+lookup", IN_LookupDown);
441 Cmd_AddCommand ("-lookup", IN_LookupUp);
442 Cmd_AddCommand ("+lookdown", IN_LookdownDown);
443 Cmd_AddCommand ("-lookdown", IN_LookdownUp);
444 Cmd_AddCommand ("+strafe", IN_StrafeDown);
445 Cmd_AddCommand ("-strafe", IN_StrafeUp);
446 Cmd_AddCommand ("+moveleft", IN_MoveleftDown);
447 Cmd_AddCommand ("-moveleft", IN_MoveleftUp);
448 Cmd_AddCommand ("+moveright", IN_MoverightDown);
449 Cmd_AddCommand ("-moveright", IN_MoverightUp);
450 Cmd_AddCommand ("+speed", IN_SpeedDown);
451 Cmd_AddCommand ("-speed", IN_SpeedUp);
452 Cmd_AddCommand ("+attack", IN_AttackDown);
453 Cmd_AddCommand ("-attack", IN_AttackUp);
454 Cmd_AddCommand ("+use", IN_UseDown);
455 Cmd_AddCommand ("-use", IN_UseUp);
456 Cmd_AddCommand ("impulse", IN_Impulse);
457 Cmd_AddCommand ("+klook", IN_KLookDown);
458 Cmd_AddCommand ("-klook", IN_KLookUp);
459 //alt fire
460 Cmd_AddCommand ("+attack2", IN_Attack2Down);
461 Cmd_AddCommand ("-attack2", IN_Attack2Up);
462
463 cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0);
464 }
465
466
467
468 /*
469 =================
470 CL_SendCmd
471 =================
472 */
CL_SendCmd(void)473 void CL_SendCmd (void)
474 {
475 sizebuf_t buf;
476 byte data[128];
477 int i;
478 usercmd_t *cmd, *oldcmd;
479 usercmd_t nullcmd;
480 int checksumIndex;
481
482 // build a command even if not connected
483
484 // save this command off for prediction
485 i = cls.netchan.outgoing_sequence & (CMD_BACKUP-1);
486 cmd = &cl.cmds[i];
487 cl.cmd_time[i] = cls.realtime; // for netgraph ping calculation
488
489 *cmd = CL_CreateCmd ();
490
491 cl.cmd = *cmd;
492
493 if (cls.state == ca_disconnected || cls.state == ca_connecting)
494 return;
495
496 if ( cls.state == ca_connected)
497 {
498 if (cls.netchan.message.cursize || curtime - cls.netchan.last_sent > 1000 )
499 Netchan_Transmit (&cls.netchan, 0, data);
500 return;
501 }
502
503 // send a userinfo update if needed
504 if (userinfo_modified)
505 {
506 CL_FixUpGender();
507 userinfo_modified = false;
508 MSG_WriteByte (&cls.netchan.message, clc_userinfo);
509 MSG_WriteString (&cls.netchan.message, Cvar_Userinfo() );
510 }
511
512 SZ_Init (&buf, data, sizeof(data));
513 SZ_SetName ( &buf, "CL_SendCmd", false );
514
515 // begin a client move command
516 MSG_WriteByte (&buf, clc_move);
517
518 // save the position for a checksum byte
519 checksumIndex = buf.cursize;
520 MSG_WriteByte (&buf, 0);
521
522 // let the server know what the last frame we
523 // got was, so the next message can be delta compressed
524 if (cl_nodelta->value || !cl.frame.valid || cls.demowaiting)
525 MSG_WriteLong (&buf, -1); // no compression
526 else
527 MSG_WriteLong (&buf, cl.frame.serverframe);
528
529 // send this and the previous cmds in the message, so
530 // if the last packet was dropped, it can be recovered
531 i = (cls.netchan.outgoing_sequence-2) & (CMD_BACKUP-1);
532 cmd = &cl.cmds[i];
533 memset (&nullcmd, 0, sizeof(nullcmd));
534 MSG_WriteDeltaUsercmd (&buf, &nullcmd, cmd);
535 oldcmd = cmd;
536
537 i = (cls.netchan.outgoing_sequence-1) & (CMD_BACKUP-1);
538 cmd = &cl.cmds[i];
539 MSG_WriteDeltaUsercmd (&buf, oldcmd, cmd);
540 oldcmd = cmd;
541
542 i = (cls.netchan.outgoing_sequence) & (CMD_BACKUP-1);
543 cmd = &cl.cmds[i];
544 MSG_WriteDeltaUsercmd (&buf, oldcmd, cmd);
545
546 // calculate a checksum over the move commands
547 buf.data[checksumIndex] = COM_BlockSequenceCRCByte(
548 buf.data + checksumIndex + 1, buf.cursize - checksumIndex - 1,
549 cls.netchan.outgoing_sequence);
550
551 //
552 // deliver the message
553 //
554 Netchan_Transmit (&cls.netchan, buf.cursize, buf.data);
555 }
556
557
558 #if defined WIN32_VARIANT
559 # define OS_MENU_MOUSESCALE 0.35
560 #else
561 # if defined UNIX_VARIANT
562 # define OS_MENU_MOUSESCALE 0.1
563 # else
564 # define OS_MENU_MOUSESCALE 1
565 # endif
566 #endif
567
568
569 qboolean mouse_available = false;
570 qboolean mouse_is_position = false;
571 qboolean mlooking = false;
572 int mouse_diff_x = 0;
573 int mouse_diff_y = 0;
574 int mouse_odiff_x = 0;
575 int mouse_odiff_y = 0;
576 cursor_t cursor;
577
578
IN_MLookDown(void)579 void IN_MLookDown (void)
580 {
581 mlooking = true;
582 }
583
IN_MLookUp(void)584 void IN_MLookUp (void)
585 {
586 mlooking = false;
587 if (!freelook->integer && lookspring->integer) {
588 IN_CenterView ();
589 }
590 }
591
IN_MoveMenuMouse(int x,int y)592 static void IN_MoveMenuMouse( int x , int y )
593 {
594 cursor.oldx = cursor.x;
595 cursor.oldy = cursor.y;
596
597 // Clip cursor location to window
598 if ( x < 0 ) {
599 x = 0;
600 } else if ( x > viddef.width ) {
601 x = viddef.width;
602 }
603 if ( y < 0 ) {
604 y = 0;
605 } else if ( y > viddef.height ) {
606 y = viddef.height;
607 }
608
609 cursor.x = x;
610 cursor.y = y;
611 cursor.mouseaction = cursor.mouseaction || cursor.x != cursor.oldx
612 || cursor.y != cursor.oldy;
613
614 M_Think_MouseCursor();
615 }
616
617 extern cvar_t *fov;
IN_Move(usercmd_t * cmd)618 void IN_Move (usercmd_t *cmd)
619 {
620 float fmx;
621 float fmy;
622 float adjust;
623
624 if ( ! mouse_available ) {
625 return;
626 }
627
628 // If we have a position instead of a diff, don't go through
629 // the normal process. Instead, update the menu's cursor
630 // as necessary, and bail out.
631 if ( mouse_is_position ) {
632 if ( cls.key_dest == key_menu ) {
633 IN_MoveMenuMouse( mouse_diff_x , mouse_diff_y );
634 }
635 return;
636 }
637
638 // Apply interpolation with previous value if necessary
639 fmx = (float) mouse_diff_x;
640 fmy = (float) mouse_diff_y;
641 if ( m_filter->integer ) {
642 fmx = ( fmx + mouse_odiff_x ) * 0.5f;
643 fmy = ( fmy + mouse_odiff_y ) * 0.5f;
644 }
645 if (cmd)
646 {
647 mouse_odiff_x = mouse_diff_x;
648 mouse_odiff_y = mouse_diff_y;
649 mouse_diff_x = mouse_diff_y = 0;
650 }
651
652 // No mouse in console
653 if ( cls.key_dest == key_console ) {
654 return;
655 }
656
657 // Compute adjustments
658 adjust = 1.0;
659 if ( m_smoothing->value ) {
660 // reduce sensitivity when frames per sec is below maximum
661 // setting by multiplying by:
662 // current measured fps / cvar set maximum fps
663 adjust /= cls.frametime * cl_maxfps->value;
664 }
665
666 // Update menu cursor location
667 if ( cls.key_dest == key_menu ) {
668 adjust *= menu_sensitivity->value * OS_MENU_MOUSESCALE;
669 IN_MoveMenuMouse( cursor.x + fmx * adjust ,
670 cursor.y + fmy * adjust );
671 return;
672 }
673
674 // Game mouse
675 adjust *= sensitivity->value * cl.refdef.fov_x/fov->value;
676 fmx *= adjust;
677 fmy *= adjust;
678
679 // Add mouse X/Y movement to cmd
680 if ( ( lookstrafe->integer && mlooking ) || ( in_strafe.state & 1 ) ) {
681 if (!cmd)
682 return;
683 cmd->sidemove += (short)( ( m_side->value * fmx ) + 0.5f );
684 } else {
685 if (cmd)
686 cl.viewangles[ YAW ] -= m_yaw->value * fmx;
687 else
688 cl.predicted_angles[ YAW ] = cl.last_predicted_angles[ YAW ] - m_yaw->value * fmx;
689 }
690
691 if ( ( mlooking || freelook->integer ) && !( in_strafe.state & 1 ) ) {
692 if (cmd)
693 cl.viewangles[ PITCH ] += m_pitch->value * fmy;
694 else
695 cl.predicted_angles[ PITCH ] = cl.last_predicted_angles[ PITCH ] + m_pitch->value * fmy;
696 } else {
697 if (!cmd)
698 return;
699 cmd->forwardmove -= (short)( ( m_forward->value * fmy )
700 + 0.5f );
701 }
702 }
703