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