1 /////////////////////////////////////////
2 //
3 // OpenLieroX
4 //
5 // Worm class - input handling
6 //
7 // code under LGPL, based on JasonBs work,
8 // enhanced by Dark Charlie and Albert Zeyer
9 //
10 //
11 /////////////////////////////////////////
12
13
14 // TODO: rename this file (only input handling here)
15
16 // Created 2/8/02
17 // Jason Boettcher
18
19
20
21 #include "LieroX.h"
22 #include "Sounds.h"
23 #include "GfxPrimitives.h"
24 #include "InputEvents.h"
25 #include "CWorm.h"
26 #include "MathLib.h"
27 #include "Entity.h"
28 #include "CClient.h"
29 #include "CServerConnection.h"
30 #include "CWormHuman.h"
31 #include "ProfileSystem.h"
32 #include "CGameScript.h"
33 #include "Debug.h"
34 #include "CGameMode.h"
35 #include "Physics.h"
36 #include "WeaponDesc.h"
37 #include "AuxLib.h" // for doActionInMainThread
38 #include "Touchscreen.h"
39
40
41 ///////////////////
42 // Get the input from a human worm
getInput()43 void CWormHumanInputHandler::getInput() {
44 // HINT: we are calling this from simulateWorm
45 TimeDiff dt = GetPhysicsTime() - m_worm->fLastInputTime;
46 m_worm->fLastInputTime = GetPhysicsTime();
47
48 mouse_t *ms = GetMouse();
49
50 // do it here to ensure that it is called exactly once in a frame (needed because of intern handling)
51 bool leftOnce = cLeft.isDownOnce();
52 bool rightOnce = cRight.isDownOnce();
53
54 worm_state_t *ws = &m_worm->tState;
55
56 // Init the ws
57 ws->bCarve = false;
58 ws->bMove = false;
59 ws->bShoot = false;
60 ws->bJump = false;
61
62 const bool mouseControl =
63 tLXOptions->bMouseAiming &&
64 ApplicationHasFocus(); // if app has no focus, don't use mouseaiming, the mousewarping is pretty annoying then
65 const float mouseSensity = (float)tLXOptions->iMouseSensity; // how sensitive is the mouse in X/Y-dir
66
67 // TODO: here are width/height of the window hardcoded
68 //int mouse_dx = ms->X - 640/2;
69 //int mouse_dy = ms->Y - 480/2;
70 int mouse_dx = 0, mouse_dy = 0;
71 SDL_GetRelativeMouseState(&mouse_dx, &mouse_dy); // Won't lose mouse movement and skip frames, also it doesn't call libX11 funcs, so it's safe to call not from video thread
72
73 if(mouseControl)
74 {
75 struct CenterMouse: public Action
76 {
77 int handle()
78 {
79 SDL_WarpMouse(640/2, 480/2); // Should be called from main thread, or you'll get race condition with libX11
80 return 0;
81 }
82 };
83 doActionInMainThread( new CenterMouse() );
84 }
85
86 {
87 /* // only some debug output for checking the values
88 if(mouseControl && (mouse_dx != 0 || mouse_dy != 0))
89 notes("mousepos changed: %i, %i\n", mouse_dx, mouse_dy),
90 notes("anglespeed: %f\n", fAngleSpeed),
91 notes("movespeed: %f\n", fMoveSpeedX),
92 notes("dt: %f\n", dt); */
93 }
94
95 // angle section
96 {
97 static const float joystickCoeff = 150.0f/65536.0f;
98 static const float joystickShift = 15; // 15 degrees
99
100 // Joystick up
101 if (cDown.isJoystickThrottle()) {
102 m_worm->fAngleSpeed = 0;
103 m_worm->fAngle = CLAMP((float)cUp.getJoystickValue() * joystickCoeff - joystickShift, -90.0f, 60.0f);
104 }
105
106 // Joystick down
107 if (cDown.isJoystickThrottle()) {
108 m_worm->fAngleSpeed = 0;
109 m_worm->fAngle = CLAMP((float)cUp.getJoystickValue() * joystickCoeff - joystickShift, -90.0f, 60.0f);
110 }
111
112 // Note: 100 was LX56 max aimspeed
113 const float aimMaxSpeed = MAX((float)fabs(tLXOptions->fAimMaxSpeed), 20.0f);
114 // HINT: 500 is the LX56 value here (rev 1)
115 const float aimAccel = MAX((float)fabs(tLXOptions->fAimAcceleration), 100.0f);
116 if(cUp.isDown() && !cUp.isJoystickThrottle()) { // Up
117 m_worm->fAngleSpeed -= aimAccel * dt.seconds();
118 if(!tLXOptions->bAimLikeLX56) CLAMP_DIRECT(m_worm->fAngleSpeed, -aimMaxSpeed, aimMaxSpeed);
119 } else if(cDown.isDown() && !cDown.isJoystickThrottle()) { // Down
120 m_worm->fAngleSpeed += aimAccel * dt.seconds();
121 if(!tLXOptions->bAimLikeLX56) CLAMP_DIRECT(m_worm->fAngleSpeed, -aimMaxSpeed, aimMaxSpeed);
122 } else {
123 // we didn't had that in LX56; it behaves more natural
124 const float aimFriction = CLAMP(tLXOptions->fAimFriction, 0.0f, 1.0f);
125
126 if(!mouseControl) {
127 // HINT: this is the original order and code (before mouse patch - rev 1007)
128 CLAMP_DIRECT(m_worm->fAngleSpeed, -aimMaxSpeed, aimMaxSpeed);
129 if(tLXOptions->bAimLikeLX56) REDUCE_CONST(m_worm->fAngleSpeed, 200*dt.seconds());
130 else m_worm->fAngleSpeed *= powf(aimFriction, dt.seconds() * 100.0f);
131 RESET_SMALL(m_worm->fAngleSpeed, 5.0f);
132
133 } else { // mouseControl for angle
134 // HINT: to behave more like keyboard, we use CLAMP(..aimAccel) here
135 float diff = mouse_dy * mouseSensity;
136 CLAMP_DIRECT(diff, -aimAccel, aimAccel); // same limit as keyboard
137 m_worm->fAngleSpeed += diff * dt.seconds();
138
139 // this tries to be like keyboard where this code is only applied if up/down is not pressed
140 if(abs(mouse_dy) < 5) {
141 CLAMP_DIRECT(m_worm->fAngleSpeed, -aimMaxSpeed, aimMaxSpeed);
142 if(tLXOptions->bAimLikeLX56) REDUCE_CONST(m_worm->fAngleSpeed, 200*dt.seconds());
143 else m_worm->fAngleSpeed *= powf(aimFriction, dt.seconds() * 100.0f);
144 RESET_SMALL(m_worm->fAngleSpeed, 5.0f);
145 }
146 }
147 }
148
149 m_worm->fAngle += m_worm->fAngleSpeed * dt.seconds();
150 if(CLAMP_DIRECT(m_worm->fAngle, -90.0f, cClient->getGameLobby()->features[FT_FullAimAngle] ? 90.0f : 60.0f) != 0)
151 m_worm->fAngleSpeed = 0;
152
153 } // end angle section
154
155 const CVec ninjaShootDir = m_worm->getFaceDirection();
156
157 // basic mouse control (moving)
158 if(mouseControl) {
159 // no dt here, it's like the keyboard; and the value will be limited by dt later
160 m_worm->fMoveSpeedX += mouse_dx * mouseSensity * 0.01f;
161
162 REDUCE_CONST(m_worm->fMoveSpeedX, 1000*dt.seconds());
163 //RESET_SMALL(m_worm->fMoveSpeedX, 5.0f);
164 CLAMP_DIRECT(m_worm->fMoveSpeedX, -500.0f, 500.0f);
165
166 if(fabs(m_worm->fMoveSpeedX) > 50) {
167 if(m_worm->fMoveSpeedX > 0) {
168 m_worm->iMoveDirectionSide = DIR_RIGHT;
169 if(mouse_dx < 0) m_worm->lastMoveTime = tLX->currentTime;
170 } else {
171 m_worm->iMoveDirectionSide = DIR_LEFT;
172 if(mouse_dx > 0) m_worm->lastMoveTime = tLX->currentTime;
173 }
174 ws->bMove = true;
175 if(!cClient->isHostAllowingStrafing() || !cStrafe.isDown())
176 m_worm->iFaceDirectionSide = m_worm->iMoveDirectionSide;
177
178 } else {
179 ws->bMove = false;
180 }
181
182 }
183
184
185 if(mouseControl) { // set shooting, ninja and jumping, weapon selection for mousecontrol
186 // like Jason did it
187 ws->bShoot = (ms->Down & SDL_BUTTON(1)) ? true : false;
188 ws->bJump = (ms->Down & SDL_BUTTON(3)) ? true : false;
189 if(ws->bJump) {
190 if(m_worm->cNinjaRope.isReleased())
191 m_worm->cNinjaRope.Release();
192 }
193 else if(ms->FirstDown & SDL_BUTTON(2)) {
194 // TODO: this is bad. why isn't there a ws->iNinjaShoot ?
195 m_worm->cNinjaRope.Shoot(m_worm, m_worm->vPos, ninjaShootDir);
196 PlaySoundSample(sfxGame.smpNinja);
197 }
198
199 if( ms->WheelScrollUp || ms->WheelScrollDown ) {
200 m_worm->bForceWeapon_Name = true;
201 m_worm->fForceWeapon_Time = tLX->currentTime + 0.75f;
202 if( ms->WheelScrollUp )
203 m_worm->iCurrentWeapon ++;
204 else
205 m_worm->iCurrentWeapon --;
206 if(m_worm->iCurrentWeapon >= m_worm->iNumWeaponSlots)
207 m_worm->iCurrentWeapon=0;
208 if(m_worm->iCurrentWeapon < 0)
209 m_worm->iCurrentWeapon=m_worm->iNumWeaponSlots-1;
210 }
211 }
212
213
214
215 { // set carving
216
217 /* // this is a bit unfair to keyboard players
218 if(mouseControl) { // mouseControl
219 if(fabs(fMoveSpeedX) > 200) {
220 ws->iCarve = true;
221 }
222 } */
223
224 const float carveDelay = 0.2f;
225
226 if( (mouseControl && ws->bMove && m_worm->iMoveDirectionSide == DIR_LEFT)
227 || ( ( (cLeft.isJoystick() && cLeft.isDown()) /*|| (cLeft.isKeyboard() && leftOnce)*/ ) && !cSelWeapon.isDown())
228 ) {
229
230 if(tLX->currentTime - m_worm->fLastCarve >= carveDelay) {
231 ws->bCarve = true;
232 ws->bMove = true;
233 m_worm->fLastCarve = tLX->currentTime;
234 }
235 }
236
237 if( (mouseControl && ws->bMove && m_worm->iMoveDirectionSide == DIR_RIGHT)
238 || ( ( (cRight.isJoystick() && cRight.isDown()) /*|| (cRight.isKeyboard() && rightOnce)*/ ) && !cSelWeapon.isDown())
239 ) {
240
241 if(tLX->currentTime - m_worm->fLastCarve >= carveDelay) {
242 ws->bCarve = true;
243 ws->bMove = true;
244 m_worm->fLastCarve = tLX->currentTime;
245 }
246 }
247 }
248
249
250 bool allocombo = cClient->getGameLobby()->features[FT_WeaponCombos];
251
252 ws->bShoot = cShoot.isDown();
253
254 if(!ws->bShoot || allocombo) {
255 //
256 // Weapon changing
257 //
258 if(cSelWeapon.isDown()) {
259 // we don't want keyrepeats here, so only count the first down-event
260 int change = (rightOnce ? 1 : 0) - (leftOnce ? 1 : 0);
261
262 if (!GetTouchscreenControlsShown()) {
263 m_worm->iCurrentWeapon += change;
264 MOD(m_worm->iCurrentWeapon, m_worm->iNumWeaponSlots);
265 }
266
267 // Joystick: if the button is pressed, change the weapon (it is annoying to move the axis for weapon changing)
268 if (((cSelWeapon.isJoystick() && change == 0) || GetTouchscreenControlsShown()) && cSelWeapon.isDownOnce()) {
269 m_worm->iCurrentWeapon++;
270 MOD(m_worm->iCurrentWeapon, m_worm->iNumWeaponSlots);
271 }
272 }
273
274 // Process weapon quick-selection keys
275 for(size_t i = 0; i < sizeof(cWeapons) / sizeof(cWeapons[0]); i++ )
276 {
277 if( cWeapons[i].isDown() )
278 {
279 m_worm->iCurrentWeapon = i;
280 // Let the weapon name show up for a short moment
281 m_worm->bForceWeapon_Name = true;
282 m_worm->fForceWeapon_Time = tLX->currentTime + 0.75f;
283 }
284 }
285
286 }
287
288 if (ws->bShoot) {
289 const float comboTapTime = 0.3f;
290 if (GetTouchscreenControlsShown() && allocombo && cShoot.isDownOnce()) {
291 if (m_worm->fLastShoot + comboTapTime > tLX->currentTime) {
292 // Cycle weapons by tapping Shoot button rapidly, but skip weapons with no ammo
293 int prevWeapon = m_worm->iCurrentWeapon;
294 while(true) {
295 if (tLXOptions->bTouchscreenTapCycleWeaponsBackwards) {
296 m_worm->iCurrentWeapon--;
297 if (m_worm->iCurrentWeapon < 0) {
298 m_worm->iCurrentWeapon = m_worm->iNumWeaponSlots - 1;
299 }
300 } else {
301 m_worm->iCurrentWeapon++;
302 MOD(m_worm->iCurrentWeapon, m_worm->iNumWeaponSlots);
303 }
304 if (m_worm->iCurrentWeapon == prevWeapon)
305 break;
306 if (!m_worm->tWeapons[m_worm->iCurrentWeapon].Reloading && m_worm->tWeapons[m_worm->iCurrentWeapon].Enabled)
307 break;
308 }
309 if (m_worm->iCurrentWeapon != prevWeapon) {
310 // Let the weapon name show up for a short moment
311 m_worm->bForceWeapon_Name = true;
312 m_worm->fForceWeapon_Time = tLX->currentTime + 0.75f;
313 }
314 }
315 }
316 m_worm->fLastShoot = tLX->currentTime;
317 }
318
319 // Safety: clamp the current weapon
320 m_worm->iCurrentWeapon = CLAMP(m_worm->iCurrentWeapon, 0, m_worm->iNumWeaponSlots-1);
321
322
323
324 if(!cSelWeapon.isDown()) {
325 if(cLeft.isDown()) {
326 ws->bMove = true;
327 m_worm->lastMoveTime = tLX->currentTime;
328
329 if(!cRight.isDown()) {
330 if(!cClient->isHostAllowingStrafing() || !cStrafe.isDown()) m_worm->iFaceDirectionSide = DIR_LEFT;
331 m_worm->iMoveDirectionSide = DIR_LEFT;
332 }
333
334 if(rightOnce) {
335 ws->bCarve = true;
336 m_worm->fLastCarve = tLX->currentTime;
337 }
338 }
339
340 if(cRight.isDown()) {
341 ws->bMove = true;
342 m_worm->lastMoveTime = tLX->currentTime;
343
344 if(!cLeft.isDown()) {
345 if(!cClient->isHostAllowingStrafing() || !cStrafe.isDown()) m_worm->iFaceDirectionSide = DIR_RIGHT;
346 m_worm->iMoveDirectionSide = DIR_RIGHT;
347 }
348
349 if(leftOnce) {
350 ws->bCarve = true;
351 m_worm->fLastCarve = tLX->currentTime;
352 }
353 }
354
355 if (cDig.isDownOnce()) {
356 ws->bCarve = true;
357 ws->bMove = true;
358 }
359
360 // inform player about disallowed strafing
361 //if(!cClient->isHostAllowingStrafing() && cStrafe.isDownOnce())
362 //hints << "strafing is not allowed on this server." << endl; // TODO: perhaps in chat?
363 }
364
365
366 bool oldskool = tLXOptions->bOldSkoolRope;
367
368 bool jumpdownonce = cJump.isDownOnce();
369
370 // Jump
371 if(jumpdownonce) {
372 if( !(oldskool && cSelWeapon.isDown()) ) {
373 ws->bJump = true;
374
375 if(m_worm->cNinjaRope.isReleased())
376 m_worm->cNinjaRope.Release();
377 }
378 }
379
380 // Ninja Rope
381 if(oldskool) {
382 // Old skool style rope throwing
383 // Change-weapon & jump
384
385 if(!cSelWeapon.isDown() || !cJump.isDown()) {
386 m_worm->bRopeDown = false;
387 }
388
389 if(cSelWeapon.isDown() && cJump.isDown() && !m_worm->bRopeDown) {
390
391 m_worm->bRopeDownOnce = true;
392 m_worm->bRopeDown = true;
393 }
394
395 // Down
396 if(m_worm->bRopeDownOnce) {
397 m_worm->bRopeDownOnce = false;
398
399 m_worm->cNinjaRope.Shoot(m_worm, m_worm->vPos, ninjaShootDir);
400
401 // Throw sound
402 PlaySoundSample(sfxGame.smpNinja);
403 }
404
405
406 } else {
407 // Newer style rope throwing
408 // Seperate dedicated button for throwing the rope
409 if(cInpRope.isDownOnce()) {
410
411 m_worm->cNinjaRope.Shoot(m_worm, m_worm->vPos, ninjaShootDir);
412 // Throw sound
413 PlaySoundSample(sfxGame.smpNinja);
414 }
415 }
416
417 ws->iAngle = (int)m_worm->fAngle;
418 ws->iX = (int)m_worm->vPos.x;
419 ws->iY = (int)m_worm->vPos.y;
420
421 clearInput();
422 }
423
424
425 ///////////////////
426 // Clear the input
clearInput()427 void CWormHumanInputHandler::clearInput() {
428 // clear inputs
429 cUp.reset();
430 cDown.reset();
431 cLeft.reset();
432 cRight.reset();
433 cShoot.reset();
434 cJump.reset();
435 cSelWeapon.reset();
436 cInpRope.reset();
437 cStrafe.reset();
438 cDig.reset();
439 for( size_t i = 0; i < sizeof(cWeapons) / sizeof(cWeapons[0]) ; i++ )
440 cWeapons[i].reset();
441 }
442
443
444
445 struct HumanWormType : WormType {
createInputHandlerHumanWormType446 virtual CWormInputHandler* createInputHandler(CWorm* w) { return new CWormHumanInputHandler(w); }
toIntHumanWormType447 int toInt() { return 0; }
448 } PRF_HUMAN_instance;
449 WormType* PRF_HUMAN = &PRF_HUMAN_instance;
450
CWormHumanInputHandler(CWorm * w)451 CWormHumanInputHandler::CWormHumanInputHandler(CWorm* w) : CWormInputHandler(w) {
452 // we use the normal init system first after the weapons are selected and we are ready
453 stopInputSystem();
454 }
455
~CWormHumanInputHandler()456 CWormHumanInputHandler::~CWormHumanInputHandler() {}
457
startGame()458 void CWormHumanInputHandler::startGame() {
459 initInputSystem();
460 }
461
462
463
464 ///////////////////
465 // Setup the inputs
setupInputs(const PlyControls & Inputs)466 void CWormHumanInputHandler::setupInputs(const PlyControls& Inputs)
467 {
468 //bUsesMouse = false;
469 for (byte i=0;i<Inputs.ControlCount(); i++)
470 if (Inputs[i].find("ms")) {
471 //bUsesMouse = true;
472 break;
473 }
474
475 cUp.Setup( Inputs[SIN_UP] );
476 cDown.Setup( Inputs[SIN_DOWN] );
477 cLeft.Setup( Inputs[SIN_LEFT] );
478 cRight.Setup( Inputs[SIN_RIGHT] );
479
480 cShoot.Setup( Inputs[SIN_SHOOT] );
481 cJump.Setup( Inputs[SIN_JUMP] );
482 cSelWeapon.Setup(Inputs[SIN_SELWEAP] );
483 cInpRope.Setup( Inputs[SIN_ROPE] );
484
485 cStrafe.Setup( Inputs[SIN_STRAFE] );
486 cDig.Setup( Inputs[SIN_DIG] );
487
488 for( size_t i = 0; i < sizeof(cWeapons) / sizeof(cWeapons[0]) ; i++ )
489 cWeapons[i].Setup(Inputs[SIN_WEAPON1 + i]);
490 }
491
492
initInputSystem()493 void CWormHumanInputHandler::initInputSystem() {
494 cUp.setResetEachFrame( false );
495 cDown.setResetEachFrame( false );
496 cLeft.setResetEachFrame( false );
497 cRight.setResetEachFrame( false );
498 cShoot.setResetEachFrame( false );
499 cJump.setResetEachFrame( false );
500 cSelWeapon.setResetEachFrame( false );
501 cInpRope.setResetEachFrame( false );
502 cStrafe.setResetEachFrame( false );
503 cDig.setResetEachFrame( false );
504 for( size_t i = 0; i < sizeof(cWeapons) / sizeof(cWeapons[0]) ; i++ )
505 cWeapons[i].setResetEachFrame( false );
506 }
507
stopInputSystem()508 void CWormHumanInputHandler::stopInputSystem() {
509 cUp.setResetEachFrame( true );
510 cDown.setResetEachFrame( true );
511 cLeft.setResetEachFrame( true );
512 cRight.setResetEachFrame( true );
513 cShoot.setResetEachFrame( true );
514 cJump.setResetEachFrame( true );
515 cSelWeapon.setResetEachFrame( true );
516 cInpRope.setResetEachFrame( true );
517 cStrafe.setResetEachFrame( true );
518 cDig.setResetEachFrame( true );
519 for( size_t i = 0; i < sizeof(cWeapons) / sizeof(cWeapons[0]) ; i++ )
520 cWeapons[i].setResetEachFrame( true );
521 }
522
523
524
525
526
527 ///////////////////
528 // Initialize the weapon selection screen
initWeaponSelection()529 void CWormHumanInputHandler::initWeaponSelection() {
530 // This is used for the menu screen as well
531 m_worm->iCurrentWeapon = 0;
532
533 m_worm->bWeaponsReady = false;
534
535 m_worm->iNumWeaponSlots = 5;
536
537 m_worm->clearInput();
538
539 // Safety
540 if (!m_worm->tProfile) {
541 errors << "initWeaponSelection called and tProfile is not set" << endl;
542 return;
543 }
544
545 // Load previous settings from profile
546 short i;
547 for(i=0;i<m_worm->iNumWeaponSlots;i++) {
548
549 m_worm->tWeapons[i].Weapon = m_worm->cGameScript->FindWeapon( m_worm->tProfile->sWeaponSlots[i] );
550
551 // If this weapon is not enabled in the restrictions, find another weapon that is enabled
552 if( !m_worm->tWeapons[i].Weapon || !m_worm->cWeaponRest->isEnabled( m_worm->tWeapons[i].Weapon->Name ) ) {
553
554 m_worm->tWeapons[i].Weapon = m_worm->cGameScript->FindWeapon( m_worm->cWeaponRest->findEnabledWeapon( m_worm->cGameScript ) );
555 }
556
557 m_worm->tWeapons[i].Enabled = m_worm->tWeapons[i].Weapon != NULL;
558 }
559
560
561 for(short n=0;n<m_worm->iNumWeaponSlots;n++) {
562 m_worm->tWeapons[n].Charge = 1;
563 m_worm->tWeapons[n].Reloading = false;
564 m_worm->tWeapons[n].SlotNum = n;
565 m_worm->tWeapons[n].LastFire = 0;
566 }
567
568 // Skip the dialog if there's only one weapon available
569 int enabledWeaponsAmount = 0;
570 for( int f = 0; f < m_worm->cGameScript->GetNumWeapons(); f++ )
571 if( m_worm->cWeaponRest->isEnabled( m_worm->cGameScript->GetWeapons()[f].Name ) )
572 enabledWeaponsAmount++;
573
574 if( enabledWeaponsAmount <= 1 ) // server can ban ALL weapons, noone will be able to shoot then
575 m_worm->bWeaponsReady = true;
576 }
577
578
579 ///////////////////
580 // Draw/Process the weapon selection screen
doWeaponSelectionFrame(SDL_Surface * bmpDest,CViewport * v)581 void CWormHumanInputHandler::doWeaponSelectionFrame(SDL_Surface * bmpDest, CViewport *v)
582 {
583 // TODO: this should also be used for selecting the weapons for the bot (but this in CWorm_AI then)
584 // TODO: reduce local variables in this function
585 // TODO: make this function shorter
586 // TODO: give better names to local variables
587
588 if(bDedicated) {
589 warnings << "doWeaponSelectionFrame: we have a local human input in our dedicated server" << endl;
590 return; // just for safty; atm this function only handles non-bot players
591 }
592
593 // do that check here instead of initWeaponSelection() because at that time,
594 // not all params of the gamemode are set
595 if(cClient->getGameLobby()->gameMode && !cClient->getGameLobby()->gameMode->Shoot(m_worm)) {
596 // just skip weapon selection in game modes where shooting is not possible (e.g. hidenseek)
597 m_worm->bWeaponsReady = true;
598 return;
599 }
600
601 int t = 0;
602 short i;
603 int centrex = 320; // TODO: hardcoded screen width here
604
605 if( v ) {
606 if( v->getUsed() ) {
607 t = v->GetTop();
608 centrex = v->GetLeft() + v->GetVirtW()/2;
609 }
610 }
611
612 tLX->cFont.DrawCentre(bmpDest, centrex, t+30, tLX->clWeaponSelectionTitle, "~ Weapons Selection ~");
613
614 tLX->cFont.DrawCentre(bmpDest, centrex, t+48, tLX->clWeaponSelectionTitle, "(Use up/down and left/right for selection.)");
615 tLX->cFont.DrawCentre(bmpDest, centrex, t+66, tLX->clWeaponSelectionTitle, "(Go to 'Done' and press shoot then.)");
616 //tLX->cOutlineFont.DrawCentre(bmpDest, centrex, t+30, tLX->clWeaponSelectionTitle, "Weapons Selection");
617 //tLX->cOutlineFont.DrawCentre(bmpDest, centrex, t+30, tLX->clWeaponSelectionTitle, "Weapons Selection");
618
619 bool bChat_Typing = cClient->isTyping();
620
621 int y = t + 100;
622 for(i=0;i<m_worm->iNumWeaponSlots;i++) {
623
624 std::string slotDesc;
625 if(m_worm->tWeapons[i].Weapon) slotDesc = m_worm->tWeapons[i].Weapon->Name;
626 else slotDesc = "* INVALID WEAPON *";
627 Color col = tLX->clWeaponSelectionActive;
628 if(m_worm->iCurrentWeapon != i) col = tLX->clWeaponSelectionDefault;
629 tLX->cOutlineFont.Draw(bmpDest, centrex-70, y, col, slotDesc);
630
631 if (bChat_Typing) {
632 y += 18;
633 continue;
634 }
635
636 // Changing weapon
637 if(m_worm->iCurrentWeapon == i && !bChat_Typing) {
638 int change = cRight.wasDown() - cLeft.wasDown();
639 if(cSelWeapon.isDown()) change *= 6; // jump with multiple speed if selWeapon is pressed
640 int id = m_worm->tWeapons[i].Weapon ? m_worm->tWeapons[i].Weapon->ID : 0;
641 if(change > 0) while(change) {
642 id++; MOD(id, m_worm->cGameScript->GetNumWeapons());
643 if( m_worm->cWeaponRest->isEnabled( m_worm->cGameScript->GetWeapons()[id].Name ) )
644 change--;
645 if(!m_worm->tWeapons[i].Weapon && id == 0)
646 break;
647 if(m_worm->tWeapons[i].Weapon && id == m_worm->tWeapons[i].Weapon->ID) // back where we were before
648 break;
649 } else
650 if(change < 0) while(change) {
651 id--; MOD(id, m_worm->cGameScript->GetNumWeapons());
652 if( m_worm->cWeaponRest->isEnabled( m_worm->cGameScript->GetWeapons()[id].Name ) )
653 change++;
654 if(!m_worm->tWeapons[i].Weapon && id == 0)
655 break;
656 if(m_worm->tWeapons[i].Weapon && id == m_worm->tWeapons[i].Weapon->ID) // back where we were before
657 break;
658 }
659 m_worm->tWeapons[i].Weapon = &m_worm->cGameScript->GetWeapons()[id];
660 m_worm->tWeapons[i].Enabled = true;
661 }
662
663 y += 18;
664 }
665
666 for(i=0;i<5;i++)
667 m_worm->tProfile->sWeaponSlots[i] = m_worm->tWeapons[i].Weapon ? m_worm->tWeapons[i].Weapon->Name : "";
668
669 // Note: The extra weapon weapon is the 'random' button
670 if(m_worm->iCurrentWeapon == m_worm->iNumWeaponSlots) {
671
672 // Fire on the random button?
673 if((cShoot.isDownOnce()) && !bChat_Typing) {
674 m_worm->GetRandomWeapons();
675 }
676 }
677
678
679 // Note: The extra weapon slot is the 'done' button
680 if(m_worm->iCurrentWeapon == m_worm->iNumWeaponSlots+1) {
681
682 // Fire on the done button?
683 // we have to check isUp() here because if we continue while it is still down, we will fire after in the game
684 if((cShoot.isUp()) && !bChat_Typing) {
685 // we are ready with manual human weapon selection
686 m_worm->bWeaponsReady = true;
687 m_worm->iCurrentWeapon = 0;
688
689 // Set our profile to the weapons (so we can save it later)
690 for(byte i=0;i<5;i++)
691 m_worm->tProfile->sWeaponSlots[i] = m_worm->tWeapons[i].Weapon ? m_worm->tWeapons[i].Weapon->Name : "";
692 }
693 }
694
695
696
697 y+=5;
698 if(m_worm->iCurrentWeapon == m_worm->iNumWeaponSlots)
699 tLX->cOutlineFont.DrawCentre(bmpDest, centrex, y, tLX->clWeaponSelectionActive, "Random");
700 else
701 tLX->cOutlineFont.DrawCentre(bmpDest, centrex, y, tLX->clWeaponSelectionDefault, "Random");
702
703 y+=18;
704
705 if(m_worm->iCurrentWeapon == m_worm->iNumWeaponSlots+1)
706 tLX->cOutlineFont.DrawCentre(bmpDest, centrex, y, tLX->clWeaponSelectionActive, "Done");
707 else
708 tLX->cOutlineFont.DrawCentre(bmpDest, centrex, y, tLX->clWeaponSelectionDefault, "Done");
709
710
711 // list current key settings
712 // TODO: move this out here
713 y += 20;
714 tLX->cFont.DrawCentre(bmpDest, centrex, y += 15, tLX->clWeaponSelectionTitle, "~ Key settings ~");
715 tLX->cFont.Draw(bmpDest, centrex - 150, y += 15, tLX->clWeaponSelectionTitle, "up/down: " + cUp.getEventName() + "/" + cDown.getEventName());
716 tLX->cFont.Draw(bmpDest, centrex - 150, y += 15, tLX->clWeaponSelectionTitle, "left/right: " + cLeft.getEventName() + "/" + cRight.getEventName());
717 tLX->cFont.Draw(bmpDest, centrex - 150, y += 15, tLX->clWeaponSelectionTitle, "shoot: " + cShoot.getEventName());
718 y -= 45;
719 tLX->cFont.Draw(bmpDest, centrex, y += 15, tLX->clWeaponSelectionTitle, "jump/ninja: " + cJump.getEventName() + "/" + cInpRope.getEventName());
720 tLX->cFont.Draw(bmpDest, centrex, y += 15, tLX->clWeaponSelectionTitle, "select weapon: " + cSelWeapon.getEventName());
721 tLX->cFont.Draw(bmpDest, centrex, y += 15, tLX->clWeaponSelectionTitle, "strafe/dig: " + cStrafe.getEventName()+ "/" + cDig.getEventName());
722 tLX->cFont.Draw(bmpDest, centrex, y += 15, tLX->clWeaponSelectionTitle, "quick select weapon: " + cWeapons[0].getEventName() + " " + cWeapons[1].getEventName() + " " + cWeapons[2].getEventName() + " " + cWeapons[3].getEventName() + " " + cWeapons[4].getEventName() );
723
724
725 if(!bChat_Typing) {
726 // move selection up or down
727 if (cDown.isJoystickThrottle() || cUp.isJoystickThrottle()) {
728 m_worm->iCurrentWeapon = (cUp.getJoystickValue() + 32768) * (m_worm->iNumWeaponSlots + 2) / 65536; // We have 7 rows and 65536 throttle states
729
730 } else {
731 int change = cDown.wasDown() - cUp.wasDown();
732 m_worm->iCurrentWeapon += change;
733 m_worm->iCurrentWeapon %= m_worm->iNumWeaponSlots + 2;
734 if(m_worm->iCurrentWeapon < 0) m_worm->iCurrentWeapon += m_worm->iNumWeaponSlots + 2;
735 }
736 }
737 }
738