code:
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "mainmenumanager.h"
#include "debug.h"
#include "clouds.h"
#include "server.h"
#include "filesys.h"
#include "guiMainMenu.h"
#include "game.h"
#include "chat.h"
#include "gettext.h"
#include "profiler.h"
#include "log.h"
#include "serverlist.h"
#include "guiEngine.h"
#include "player.h"
#include "fontengine.h"
#include "joystick_controller.h"
#include "clientlauncher.h"
#include "version.h"

/* mainmenumanager.h
 */
gui::IGUIEnvironment *guienv = NULL;
gui::IGUIStaticText *guiroot = NULL;
MainMenuManager g_menumgr;

bool isMenuActive()
{
	return g_menumgr.menuCount() != 0;
}

// Passed to menus to allow disconnecting and exiting
MainGameCallback *g_gamecallback = NULL;

ClientLauncher::~ClientLauncher()
{
	if (receiver)
		delete receiver;

	if (input)
		delete input;

	if (g_fontengine)
		delete g_fontengine;

	if (device)
		device->drop();
}

bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args)
{
	init_args(game_params, cmd_args);

	// List video modes if requested
	if (list_video_modes)
		return print_video_modes();

	if (!init_engine()) {
		errorstream << "Could not initialize game engine." << std::endl;
		return false;
	}

	// Speed tests (done after irrlicht is loaded to get timer)
	if (cmd_args.getFlag("speedtests")) {
		dstream << "Running speed tests" << std::endl;
		speed_tests();
		return true;
	}

	video::IVideoDriver *video_driver = device->getVideoDriver();
	if (video_driver == NULL) {
		errorstream << "Could not initialize video driver." << std::endl;
		return false;
	}

	porting::setXorgClassHint(video_driver->getExposedVideoData(), PROJECT_NAME_C);

	porting::setWindowIcon(device);

	/*
		This changes the minimum allowed number of vertices in a VBO.
		Default is 500.
	*/
	//driver->setMinHardwareBufferVertexCount(50);

	// Create game callback for menus
	g_gamecallback = new MainGameCallback(device);

	device->setResizable(true);

	init_input();

	smgr = device->getSceneManager();
	smgr->getParameters()->setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true);

	guienv = device->getGUIEnvironment();
	skin = guienv->getSkin();
	skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255, 255, 255, 255));
	skin->setColor(gui::EGDC_3D_LIGHT, video::SColor(0, 0, 0, 0));
	skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255, 30, 30, 30));
	skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255, 0, 0, 0));
	skin->setColor(gui::EGDC_HIGH_LIGHT, video::SColor(255, 70, 120, 50));
	skin->setColor(gui::EGDC_HIGH_LIGHT_TEXT, video::SColor(255, 255, 255, 255));

	g_fontengine = new FontEngine(g_settings, guienv);
	FATAL_ERROR_IF(g_fontengine == NULL, "Font engine creation failed.");

#if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2
	// Irrlicht 1.8 input colours
	skin->setColor(gui::EGDC_EDITABLE, video::SColor(255, 128, 128, 128));
	skin->setColor(gui::EGDC_FOCUSED_EDITABLE, video::SColor(255, 96, 134, 49));
#endif

	// Create the menu clouds
	if (!g_menucloudsmgr)
		g_menucloudsmgr = smgr->createNewSceneManager();
	if (!g_menuclouds)
		g_menuclouds = new Clouds(g_menucloudsmgr->getRootSceneNode(),
				g_menucloudsmgr, -1, rand(), 100);
	g_menuclouds->update(v2f(0, 0), video::SColor(255, 200, 200, 255));
	scene::ICameraSceneNode* camera;
	camera = g_menucloudsmgr->addCameraSceneNode(0,
				v3f(0, 0, 0), v3f(0, 60, 100));
	camera->setFarValue(10000);

	/*
		GUI stuff
	*/

	ChatBackend chat_backend;

	// If an error occurs, this is set to something by menu().
	// It is then displayed before the menu shows on the next call to menu()
	std::string error_message;
	bool reconnect_requested = false;

	bool first_loop = true;

	/*
		Menu-game loop
	*/
	bool retval = true;
	bool *kill = porting::signal_handler_killstatus();

	while (device->run() && !*kill && !g_gamecallback->shutdown_requested)
	{
		// Set the window caption
		const wchar_t *text = wgettext("Main Menu");
		device->setWindowCaption((utf8_to_wide(PROJECT_NAME_C) +
			L" " + utf8_to_wide(g_version_hash) +
			L" [" + text + L"]").c_str());
		delete[] text;

		try {	// This is used for catching disconnects

			guienv->clear();

			/*
				We need some kind of a root node to be able to add
				custom gui elements directly on the screen.
				Otherwise they won't be automatically drawn.
			*/
			guiroot = guienv->addStaticText(L"", core::rect<s32>(0, 0, 10000, 10000));

			bool game_has_run = launch_game(error_message, reconnect_requested,
				game_params, cmd_args);

			// Reset the reconnect_requested flag
			reconnect_requested = false;

			// If skip_main_menu, we only want to startup once
			if (skip_main_menu && !first_loop)
				break;

			first_loop = false;

			if (!game_has_run) {
				if (skip_main_menu)
					break;
				else
					continue;
			}

			// Break out of menu-game loop to shut down cleanly
			if (!device->run() || *kill) {
				if (g_settings_path != "")
					g_settings->updateConfigFile(g_settings_path.c_str());
				break;
			}

			if (current_playername.length() > PLAYERNAME_SIZE-1) {
				error_message = gettext("Player name too long.");
				playername = current_playername.substr(0, PLAYERNAME_SIZE-1);
				g_settings->set("name", playername);
				continue;
			}

			device->getVideoDriver()->setTextureCreationFlag(
					video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map"));

#ifdef HAVE_TOUCHSCREENGUI
			receiver->m_touchscreengui = new TouchScreenGUI(device, receiver);
			g_touchscreengui = receiver->m_touchscreengui;
#endif

			the_game(
				kill,
				random_input,
				input,
				device,
				worldspec.path,
				current_playername,
				current_password,
				current_address,
				current_port,
				error_message,
				chat_backend,
				&reconnect_requested,
				gamespec,
				simple_singleplayer_mode
			);
			smgr->clear();

#ifdef HAVE_TOUCHSCREENGUI
			delete g_touchscreengui;
			g_touchscreengui = NULL;
			receiver->m_touchscreengui = NULL;
#endif

		} //try
		catch (con::PeerNotFoundException &e) {
			error_message = gettext("Connection error (timed out?)");
			errorstream << error_message << std::endl;
		}

#ifdef NDEBUG
		catch (std::exception &e) {
			std::string error_message = "Some exception: \"";
			error_message += e.what();
			error_message += "\"";
			errorstream << error_message << std::endl;
		}
#endif

		// If no main menu, show error and exit
		if (skip_main_menu) {
			if (!error_message.empty()) {
				verbosestream << "error_message = "
				              << error_message << std::endl;
				retval = false;
			}
			break;
		}
	} // Menu-game loop

	g_menuclouds->drop();
	g_menucloudsmgr->drop();

	return retval;
}

void ClientLauncher::init_args(GameParams &game_params, const Settings &cmd_args)
{

	skip_main_menu = cmd_args.getFlag("go");

	// FIXME: This is confusing (but correct)

	/* If world_path is set then override it unless skipping the main menu using
	 * the --go command line param. Else, give preference to the address
	 * supplied on the command line
	 */
	address = g_settings->get("address");
	if (game_params.world_path != "" && !skip_main_menu)
		address = "";
	else if (cmd_args.exists("address"))
		address = cmd_args.get("address");

	playername = g_settings->get("name");
	if (cmd_args.exists("name"))
		playername = cmd_args.get("name");

	list_video_modes = cmd_args.getFlag("videomodes");

	use_freetype = g_settings->getBool("freetype");

	random_input = g_settings->getBool("random_input")
			|| cmd_args.getFlag("random-input");
}

bool ClientLauncher::init_engine()
{
	receiver = new MyEventReceiver();
	create_engine_device();
	return device != NULL;
}

void ClientLauncher::init_input()
{
	if (random_input)
		input = new RandomInputHandler();
	else
		input = new RealInputHandler(device, receiver);

	if (g_settings->getBool("enable_joysticks")) {
		irr::core::array<irr::SJoystickInfo> infos;
		std::vector<irr::SJoystickInfo> joystick_infos;

		// Make sure this is called maximum once per
		// irrlicht device, otherwise it will give you
		// multiple events for the same joystick.
		if (device->activateJoysticks(infos)) {
			infostream << "Joystick support enabled" << std::endl;
			joystick_infos.reserve(infos.size());
			for (u32 i = 0; i < infos.size(); i++) {
				joystick_infos.push_back(infos[i]);
			}
			input->joystick.onJoystickConnect(joystick_infos);
		} else {
			errorstream << "Could not activate joystick support." << std::endl;
		}
	}
}

bool ClientLauncher::launch_game(std::string &error_message,
		bool reconnect_requested, GameParams &game_params,
		const Settings &cmd_args)
{
	// Initialize menu data
	MainMenuData menudata;
	menudata.address                         = address;
	menudata.name                            = playername;
	menudata.password                        = password;
	menudata.port                            = itos(game_params.socket_port);
	menudata.script_data.errormessage        = error_message;
	menudata.script_data.reconnect_requested = reconnect_requested;

	error_message.clear();

	if (cmd_args.exists("password"))
		menudata.password = cmd_args.get("password");

	menudata.enable_public = g_settings->getBool("server_announce");

	// If a world was commanded, append and select it
	if (game_params.world_path != "") {
		worldspec.gameid = getWorldGameId(game_params.world_path, true);
		worldspec.name = _("[--world parameter]");

		if (worldspec.gameid == "") {	// Create new
			worldspec.gameid = g_settings->get("default_game");
			worldspec.name += " [new]";
		}
		worldspec.path = game_params.world_path;
	}

	/* Show the GUI menu
	 */
	if (!skip_main_menu) {
		main_menu(&menudata);

		// Skip further loading if there was an exit signal.
		if (*porting::signal_handler_killstatus())
			return false;

		address = menudata.address;
		int newport = stoi(menudata.port);
		if (newport != 0)
			game_params.socket_port = newport;

		simple_singleplayer_mode = menudata.simple_singleplayer_mode;

		std::vector<WorldSpec> worldspecs = getAvailableWorlds();

		if (menudata.selected_world >= 0
				&& menudata.selected_world < (int)worldspecs.size()) {
			g_settings->set("selected_world_path",
					worldspecs[menudata.selected_world].path);
			worldspec = worldspecs[menudata.selected_world];
		}
	}

	if (!menudata.script_data.errormessage.empty()) {
		/* The calling function will pass this back into this function upon the
		 * next iteration (if any) causing it to be displayed by the GUI
		 */
		error_message = menudata.script_data.errormessage;
		return false;
	}

	if (menudata.name == "" && !simple_singleplayer_mode) {
		error_message = gettext("Please choose a name!");
		return false;
	}

	playername = menudata.name;
	password = menudata.password;

	current_playername = playername;
	current_password   = password;
	current_address    = address;
	current_port       = game_params.socket_port;

	// If using simple singleplayer mode, override
	if (simple_singleplayer_mode) {
		assert(skip_main_menu == false);
		current_playername = "singleplayer";
		current_password = "";
		current_address = "";
		current_port = myrand_range(49152, 65535);
	} else {
		g_settings->set("name", playername);
		if (address != "") {
			ServerListSpec server;
			server["name"] = menudata.servername;
			server["address"] = menudata.address;
			server["port"] = menudata.port;
			server["description"] = menudata.serverdescription;
			ServerList::insert(server);
		}
	}

	infostream << "Selected world: " << worldspec.name
	           << " [" << worldspec.path << "]" << std::endl;

	if (current_address == "") { // If local game
		if (worldspec.path == "") {
			error_message = gettext("No world selected and no address "
					"provided. Nothing to do.");
			errorstream << error_message << std::endl;
			return false;
		}

		if (!fs::PathExists(worldspec.path)) {
			error_message = gettext("Provided world path doesn't exist: ")
					+ worldspec.path;
			errorstream << error_message << std::endl;
			return false;
		}

		// Load gamespec for required game
		gamespec = findWorldSubgame(worldspec.path);
		if (!gamespec.isValid() && !game_params.game_spec.isValid()) {
			error_message = gettext("Could not find or load game \"")
					+ worldspec.gameid + "\"";
			errorstream << error_message << std::endl;
			return false;
		}

		if (porting::signal_handler_killstatus())
			return true;

		if (game_params.game_spec.isValid() &&
				game_params.game_spec.id != worldspec.gameid) {
			warningstream << "Overriding gamespec from \""
			            << worldspec.gameid << "\" to \""
			            << game_params.game_spec.id << "\"" << std::endl;
			gamespec = game_params.game_spec;
		}

		if (!gamespec.isValid()) {
			error_message = gettext("Invalid gamespec.");
			error_message += " (world.gameid=" + worldspec.gameid + ")";
			errorstream << error_message << std::endl;
			return false;
		}
	}

	return true;
}

void ClientLauncher::main_menu(MainMenuData *menudata)
{
	bool *kill = porting::signal_handler_killstatus();
	video::IVideoDriver *driver = device->getVideoDriver();

	infostream << "Waiting for other menus" << std::endl;
	while (device->run() && *kill == false) {
		if (!isMenuActive())
			break;
		driver->beginScene(true, true, video::SColor(255, 128, 128, 128));
		guienv->drawAll();
		driver->endScene();
		// On some computers framerate doesn't seem to be automatically limited
		sleep_ms(25);
	}
	infostream << "Waited for other menus" << std::endl;

	// Cursor can be non-visible when coming from the game
#ifndef ANDROID
	device->getCursorControl()->setVisible(true);
#endif

	/* show main menu */
	GUIEngine mymenu(device, &input->joystick, guiroot,
		&g_menumgr, smgr, menudata, *kill);

	smgr->clear();	/* leave scene manager in a clean state */
}

bool ClientLauncher::create_engine_device()
{
	// Resolution selection
	bool fullscreen = g_settings->getBool("fullscreen");
	u16 screenW = g_settings->getU16("screenW");
	u16 screenH = g_settings->getU16("screenH");

	// bpp, fsaa, vsync
	bool vsync = g_settings->getBool("vsync");
	u16 bits = g_settings->getU16("fullscreen_bpp");
	u16 fsaa = g_settings->getU16("fsaa");

	// stereo buffer required for pageflip stereo
	bool stereo_buffer = g_settings->get("3d_mode") == "pageflip";

	// Determine driver
	video::E_DRIVER_TYPE driverType = video::EDT_OPENGL;
	const std::string &driverstring = g_settings->get("video_driver");
	std::vector<video::E_DRIVER_TYPE> drivers
		= porting::getSupportedVideoDrivers();
	u32 i;
	for (i = 0; i != drivers.size(); i++) {
		if (!strcasecmp(driverstring.c_str(),
			porting::getVideoDriverName(drivers[i]))) {
			driverType = drivers[i];
			break;
		}
	}
	if (i == drivers.size()) {
		errorstream << "Invalid video_driver specified; "
			"defaulting to opengl" << std::endl;
	}

	SIrrlichtCreationParameters params = SIrrlichtCreationParameters();
	params.DriverType    = driverType;
	params.WindowSize    = core::dimension2d<u32>(screenW, screenH);
	params.Bits          = bits;
	params.AntiAlias     = fsaa;
	params.Fullscreen    = fullscreen;
	params.Stencilbuffer = false;
	params.Stereobuffer  = stereo_buffer;
	params.Vsync         = vsync;
	params.EventReceiver = receiver;
	params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu");
	params.ZBufferBits   = 24;
#ifdef __ANDROID__
	params.PrivateData = porting::app_global;
	params.OGLES2ShaderPath = std::string(porting::path_user + DIR_DELIM +
			"media" + DIR_DELIM + "Shaders" + DIR_DELIM).c_str();
#endif

	device = createDeviceEx(params);

	if (device)
		porting::initIrrlicht(device);

	return device != NULL;
}

void ClientLauncher::speed_tests()
{
	// volatile to avoid some potential compiler optimisations
	volatile static s16 temp16;
	volatile static f32 tempf;
	static v3f tempv3f1;
	static v3f tempv3f2;
	static std::string tempstring;
	static std::string tempstring2;

	tempv3f1 = v3f();
	tempv3f2 = v3f();
	tempstring = std::string();
	tempstring2 = std::string();

	{
		infostream << "The following test should take around 20ms." << std::endl;
		TimeTaker timer("Testing std::string speed");
		const u32 jj = 10000;
		for (u32 j = 0; j < jj; j++) {
			tempstring = "";
			tempstring2 = "";
			const u32 ii = 10;
			for (u32 i = 0; i < ii; i++) {
				tempstring2 += "asd";
			}
			for (u32 i = 0; i < ii+1; i++) {
				tempstring += "asd";
				if (tempstring == tempstring2)
					break;
			}
		}
	}

	infostream << "All of the following tests should take around 100ms each."
	           << std::endl;

	{
		TimeTaker timer("Testing floating-point conversion speed");
		tempf = 0.001;
		for (u32 i = 0; i < 4000000; i++) {
			temp16 += tempf;
			tempf += 0.001;
		}
	}

	{
		TimeTaker timer("Testing floating-point vector speed");

		tempv3f1 = v3f(1, 2, 3);
		tempv3f2 = v3f(4, 5, 6);
		for (u32 i = 0; i < 10000000; i++) {
			tempf += tempv3f1.dotProduct(tempv3f2);
			tempv3f2 += v3f(7, 8, 9);
		}
	}

	{
		TimeTaker timer("Testing std::map speed");

		std::map<v2s16, f32> map1;
		tempf = -324;
		const s16 ii = 300;
		for (s16 y = 0; y < ii; y++) {
			for (s16 x = 0; x < ii; x++) {
				map1[v2s16(x, y)] =  tempf;
				tempf += 1;
			}
		}
		for (s16 y = ii - 1; y >= 0; y--) {
			for (s16 x = 0; x < ii; x++) {
				tempf = map1[v2s16(x, y)];
			}
		}
	}

	{
		infostream << "Around 5000/ms should do well here." << std::endl;
		TimeTaker timer("Testing mutex speed");

		Mutex m;
		u32 n = 0;
		u32 i = 0;
		do {
			n += 10000;
			for (; i < n; i++) {
				m.lock();
				m.unlock();
			}
		}
		// Do at least 10ms
		while(timer.getTimerTime() < 10);

		u32 dtime = timer.stop();
		u32 per_ms = n / dtime;
		infostream << "Done. " << dtime << "ms, " << per_ms << "/ms" << std::endl;
	}
}

bool ClientLauncher::print_video_modes()
{
	IrrlichtDevice *nulldevice;

	bool vsync = g_settings->getBool("vsync");
	u16 fsaa = g_settings->getU16("fsaa");
	MyEventReceiver* receiver = new MyEventReceiver();

	SIrrlichtCreationParameters params = SIrrlichtCreationParameters();
	params.DriverType    = video::EDT_NULL;
	params.WindowSize    = core::dimension2d<u32>(640, 480);
	params.Bits          = 24;
	params.AntiAlias     = fsaa;
	params.Fullscreen    = false;
	params.Stencilbuffer = false;
	params.Vsync         = vsync;
	params.EventReceiver = receiver;
	params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu");

	nulldevice = createDeviceEx(params);

	if (nulldevice == NULL) {
		delete receiver;
		return false;
	}

	std::cout << _("Available video modes (WxHxD):") << std::endl;

	video::IVideoModeList *videomode_list = nulldevice->getVideoModeList();

	if (videomode_list != NULL) {
		s32 videomode_count = videomode_list->getVideoModeCount();
		core::dimension2d<u32> videomode_res;
		s32 videomode_depth;
		for (s32 i = 0; i < videomode_count; ++i) {
			videomode_res = videomode_list->getVideoModeResolution(i);
			videomode_depth = videomode_list->getVideoModeDepth(i);
			std::cout << videomode_res.Width << "x" << videomode_res.Height
			        << "x" << videomode_depth << std::endl;
		}

		std::cout << _("Active video mode (WxHxD):") << std::endl;
		videomode_res = videomode_list->getDesktopResolution();
		videomode_depth = videomode_list->getDesktopDepth();
		std::cout << videomode_res.Width << "x" << videomode_res.Height
		        << "x" << videomode_depth << std::endl;

	}

	nulldevice->drop();
	delete receiver;

	return videomode_list != NULL;
}


code:
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef __CLIENT_LAUNCHER_H__
#define __CLIENT_LAUNCHER_H__

#include "irrlichttypes_extrabloated.h"
#include "client/inputhandler.h"
#include "gameparams.h"

class ClientLauncher
{
public:
	ClientLauncher() :
		list_video_modes(false),
		skip_main_menu(false),
		use_freetype(false),
		random_input(false),
		address(""),
		playername(""),
		password(""),
		device(NULL),
		input(NULL),
		receiver(NULL),
		skin(NULL),
		font(NULL),
		simple_singleplayer_mode(false),
		current_playername("inv£lid"),
		current_password(""),
		current_address("does-not-exist"),
		current_port(0)
	{}

	~ClientLauncher();

	bool run(GameParams &game_params, const Settings &cmd_args);

protected:
	void init_args(GameParams &game_params, const Settings &cmd_args);
	bool init_engine();
	void init_input();

	bool launch_game(std::string &error_message, bool reconnect_requested,
		GameParams &game_params, const Settings &cmd_args);

	void main_menu(MainMenuData *menudata);
	bool create_engine_device();

	void speed_tests();
	bool print_video_modes();

	bool list_video_modes;
	bool skip_main_menu;
	bool use_freetype;
	bool random_input;
	std::string address;
	std::string playername;
	std::string password;
	IrrlichtDevice *device;
	InputHandler *input;
	MyEventReceiver *receiver;
	gui::IGUISkin *skin;
	gui::IGUIFont *font;
	scene::ISceneManager *smgr;
	SubgameSpec gamespec;
	WorldSpec worldspec;
	bool simple_singleplayer_mode = false;

	// These are set up based on the menu and other things
	// TODO: Are these required since there's already playername, password, etc
	std::string current_playername;
	std::string current_password;
	std::string current_address;
	int current_port;
};

#endif

repo_name: pgimeno/minetest

license: unknown


code:
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "util/numeric.h"
#include "inputhandler.h"
#include "mainmenumanager.h"

bool MyEventReceiver::OnEvent(const SEvent &event)
{
	/*
		React to nothing here if a menu is active
	*/
	if (isMenuActive()) {
#ifdef HAVE_TOUCHSCREENGUI
		if (m_touchscreengui) {
			m_touchscreengui->Toggle(false);
		}
#endif
		return g_menumgr.preprocessEvent(event);
	}

	// Remember whether each key is down or up
	if (event.EventType == irr::EET_KEY_INPUT_EVENT) {
		const KeyPress &keyCode = event.KeyInput;
		if (keysListenedFor[keyCode]) {
			if (event.KeyInput.PressedDown) {
				keyIsDown.set(keyCode);
				keyWasDown.set(keyCode);
			} else {
				keyIsDown.unset(keyCode);
			}
			return true;
		}
	}

#ifdef HAVE_TOUCHSCREENGUI
	// case of touchscreengui we have to handle different events
	if (m_touchscreengui && event.EventType == irr::EET_TOUCH_INPUT_EVENT) {
		m_touchscreengui->translateEvent(event);
		return true;
	}
#endif

	if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) {
		/* TODO add a check like:
		if (event.JoystickEvent != joystick_we_listen_for)
			return false;
		*/
		return joystick->handleEvent(event.JoystickEvent);
	}
	// handle mouse events
	if (event.EventType == irr::EET_MOUSE_INPUT_EVENT) {
		if (isMenuActive()) {
			left_active = false;
			middle_active = false;
			right_active = false;
		} else {
			left_active = event.MouseInput.isLeftPressed();
			middle_active = event.MouseInput.isMiddlePressed();
			right_active = event.MouseInput.isRightPressed();

			if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
				leftclicked = true;
			}
			if (event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN) {
				rightclicked = true;
			}
			if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
				leftreleased = true;
			}
			if (event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP) {
				rightreleased = true;
			}
			if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
				mouse_wheel += event.MouseInput.Wheel;
			}
		}
	} else if (event.EventType == irr::EET_LOG_TEXT_EVENT) {
		static const LogLevel irr_loglev_conv[] = {
				LL_VERBOSE, // ELL_DEBUG
				LL_INFO,    // ELL_INFORMATION
				LL_WARNING, // ELL_WARNING
				LL_ERROR,   // ELL_ERROR
				LL_NONE,    // ELL_NONE
		};
		assert(event.LogEvent.Level < ARRLEN(irr_loglev_conv));
		g_logger.log(irr_loglev_conv[event.LogEvent.Level],
				std::string("Irrlicht: ") +
						(const char *)event.LogEvent.Text);
		return true;
	}
	/* always return false in order to continue processing events */
	return false;
}

/*
 * RandomInputHandler
 */
s32 RandomInputHandler::Rand(s32 min, s32 max)
{
	return (myrand() % (max - min + 1)) + min;
}


code:
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef INPUT_HANDLER_H
#define INPUT_HANDLER_H

#include "irrlichttypes_extrabloated.h"
#include "joystick_controller.h"
#include <list>
#include "keycode.h"

#ifdef HAVE_TOUCHSCREENGUI
#include "touchscreengui.h"
#endif

class KeyList : private std::list<KeyPress>
{
	typedef std::list<KeyPress> super;
	typedef super::iterator iterator;
	typedef super::const_iterator const_iterator;

	virtual const_iterator find(const KeyPress &key) const
	{
		const_iterator f(begin());
		const_iterator e(end());

		while (f != e) {
			if (*f == key)
				return f;

			++f;
		}

		return e;
	}

	virtual iterator find(const KeyPress &key)
	{
		iterator f(begin());
		iterator e(end());

		while (f != e) {
			if (*f == key)
				return f;

			++f;
		}

		return e;
	}

public:
	void clear() { super::clear(); }

	void set(const KeyPress &key)
	{
		if (find(key) == end())
			push_back(key);
	}

	void unset(const KeyPress &key)
	{
		iterator p(find(key));

		if (p != end())
			erase(p);
	}

	void toggle(const KeyPress &key)
	{
		iterator p(this->find(key));

		if (p != end())
			erase(p);
		else
			push_back(key);
	}

	bool operator[](const KeyPress &key) const { return find(key) != end(); }
};

class MyEventReceiver : public IEventReceiver
{
public:
	// This is the one method that we have to implement
	virtual bool OnEvent(const SEvent &event);

	bool IsKeyDown(const KeyPress &keyCode) const { return keyIsDown[keyCode]; }

	// Checks whether a key was down and resets the state
	bool WasKeyDown(const KeyPress &keyCode)
	{
		bool b = keyWasDown[keyCode];
		if (b)
			keyWasDown.unset(keyCode);
		return b;
	}

	void listenForKey(const KeyPress &keyCode) { keysListenedFor.set(keyCode); }
	void dontListenForKeys() { keysListenedFor.clear(); }

	s32 getMouseWheel()
	{
		s32 a = mouse_wheel;
		mouse_wheel = 0;
		return a;
	}

	void clearInput()
	{
		keyIsDown.clear();
		keyWasDown.clear();

		leftclicked = false;
		rightclicked = false;
		leftreleased = false;
		rightreleased = false;

		left_active = false;
		middle_active = false;
		right_active = false;

		mouse_wheel = 0;
	}

	MyEventReceiver()
	{
		clearInput();
#ifdef HAVE_TOUCHSCREENGUI
		m_touchscreengui = NULL;
#endif
	}

	bool leftclicked;
	bool rightclicked;
	bool leftreleased;
	bool rightreleased;

	bool left_active;
	bool middle_active;
	bool right_active;

	s32 mouse_wheel;

	JoystickController *joystick;

#ifdef HAVE_TOUCHSCREENGUI
	TouchScreenGUI *m_touchscreengui;
#endif

private:
	// The current state of keys
	KeyList keyIsDown;
	// Whether a key has been pressed or not
	KeyList keyWasDown;
	// List of keys we listen for
	// TODO perhaps the type of this is not really
	// performant as KeyList is designed for few but
	// often changing keys, and keysListenedFor is expected
	// to change seldomly but contain lots of keys.
	KeyList keysListenedFor;
};

class InputHandler
{
public:
	InputHandler() {}
	virtual ~InputHandler() {}

	virtual bool isKeyDown(const KeyPress &keyCode) = 0;
	virtual bool wasKeyDown(const KeyPress &keyCode) = 0;

	virtual void listenForKey(const KeyPress &keyCode) {}
	virtual void dontListenForKeys() {}

	virtual v2s32 getMousePos() = 0;
	virtual void setMousePos(s32 x, s32 y) = 0;

	virtual bool getLeftState() = 0;
	virtual bool getRightState() = 0;

	virtual bool getLeftClicked() = 0;
	virtual bool getRightClicked() = 0;
	virtual void resetLeftClicked() = 0;
	virtual void resetRightClicked() = 0;

	virtual bool getLeftReleased() = 0;
	virtual bool getRightReleased() = 0;
	virtual void resetLeftReleased() = 0;
	virtual void resetRightReleased() = 0;

	virtual s32 getMouseWheel() = 0;

	virtual void step(float dtime) {}

	virtual void clear() {}

	JoystickController joystick;
};
/*
	Separated input handler
*/

class RealInputHandler : public InputHandler
{
public:
	RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver)
	    : m_device(device), m_receiver(receiver), m_mousepos(0, 0)
	{
		m_receiver->joystick = &joystick;
	}
	virtual bool isKeyDown(const KeyPress &keyCode)
	{
		return m_receiver->IsKeyDown(keyCode);
	}
	virtual bool wasKeyDown(const KeyPress &keyCode)
	{
		return m_receiver->WasKeyDown(keyCode);
	}
	virtual void listenForKey(const KeyPress &keyCode)
	{
		m_receiver->listenForKey(keyCode);
	}
	virtual void dontListenForKeys() { m_receiver->dontListenForKeys(); }
	virtual v2s32 getMousePos()
	{
		if (m_device->getCursorControl()) {
			return m_device->getCursorControl()->getPosition();
		} else {
			return m_mousepos;
		}
	}
	virtual void setMousePos(s32 x, s32 y)
	{
		if (m_device->getCursorControl()) {
			m_device->getCursorControl()->setPosition(x, y);
		} else {
			m_mousepos = v2s32(x, y);
		}
	}

	virtual bool getLeftState() { return m_receiver->left_active; }
	virtual bool getRightState() { return m_receiver->right_active; }

	virtual bool getLeftClicked() { return m_receiver->leftclicked; }
	virtual bool getRightClicked() { return m_receiver->rightclicked; }
	virtual void resetLeftClicked() { m_receiver->leftclicked = false; }
	virtual void resetRightClicked() { m_receiver->rightclicked = false; }

	virtual bool getLeftReleased() { return m_receiver->leftreleased; }
	virtual bool getRightReleased() { return m_receiver->rightreleased; }
	virtual void resetLeftReleased() { m_receiver->leftreleased = false; }
	virtual void resetRightReleased() { m_receiver->rightreleased = false; }

	virtual s32 getMouseWheel() { return m_receiver->getMouseWheel(); }

	void clear()
	{
		joystick.clear();
		m_receiver->clearInput();
	}

private:
	IrrlichtDevice *m_device;
	MyEventReceiver *m_receiver;
	v2s32 m_mousepos;
};

class RandomInputHandler : public InputHandler
{
public:
	RandomInputHandler()
	{
		leftdown = false;
		rightdown = false;
		leftclicked = false;
		rightclicked = false;
		leftreleased = false;
		rightreleased = false;
		keydown.clear();
	}
	virtual bool isKeyDown(const KeyPress &keyCode) { return keydown[keyCode]; }
	virtual bool wasKeyDown(const KeyPress &keyCode) { return false; }
	virtual v2s32 getMousePos() { return mousepos; }
	virtual void setMousePos(s32 x, s32 y) { mousepos = v2s32(x, y); }

	virtual bool getLeftState() { return leftdown; }
	virtual bool getRightState() { return rightdown; }

	virtual bool getLeftClicked() { return leftclicked; }
	virtual bool getRightClicked() { return rightclicked; }
	virtual void resetLeftClicked() { leftclicked = false; }
	virtual void resetRightClicked() { rightclicked = false; }

	virtual bool getLeftReleased() { return leftreleased; }
	virtual bool getRightReleased() { return rightreleased; }
	virtual void resetLeftReleased() { leftreleased = false; }
	virtual void resetRightReleased() { rightreleased = false; }

	virtual s32 getMouseWheel() { return 0; }

	virtual void step(float dtime)
	{
		{
			static float counter1 = 0;
			counter1 -= dtime;
			if (counter1 < 0.0) {
				counter1 = 0.1 * Rand(1, 40);
				keydown.toggle(getKeySetting("keymap_jump"));
			}
		}
		{
			static float counter1 = 0;
			counter1 -= dtime;
			if (counter1 < 0.0) {
				counter1 = 0.1 * Rand(1, 40);
				keydown.toggle(getKeySetting("keymap_special1"));
			}
		}
		{
			static float counter1 = 0;
			counter1 -= dtime;
			if (counter1 < 0.0) {
				counter1 = 0.1 * Rand(1, 40);
				keydown.toggle(getKeySetting("keymap_forward"));
			}
		}
		{
			static float counter1 = 0;
			counter1 -= dtime;
			if (counter1 < 0.0) {
				counter1 = 0.1 * Rand(1, 40);
				keydown.toggle(getKeySetting("keymap_left"));
			}
		}
		{
			static float counter1 = 0;
			counter1 -= dtime;
			if (counter1 < 0.0) {
				counter1 = 0.1 * Rand(1, 20);
				mousespeed = v2s32(Rand(-20, 20), Rand(-15, 20));
			}
		}
		{
			static float counter1 = 0;
			counter1 -= dtime;
			if (counter1 < 0.0) {
				counter1 = 0.1 * Rand(1, 30);
				leftdown = !leftdown;
				if (leftdown)
					leftclicked = true;
				if (!leftdown)
					leftreleased = true;
			}
		}
		{
			static float counter1 = 0;
			counter1 -= dtime;
			if (counter1 < 0.0) {
				counter1 = 0.1 * Rand(1, 15);
				rightdown = !rightdown;
				if (rightdown)
					rightclicked = true;
				if (!rightdown)
					rightreleased = true;
			}
		}
		mousepos += mousespeed;
	}

	s32 Rand(s32 min, s32 max);

private:
	KeyList keydown;
	v2s32 mousepos;
	v2s32 mousespeed;
	bool leftdown;
	bool rightdown;
	bool leftclicked;
	bool rightclicked;
	bool leftreleased;
	bool rightreleased;
};

#endif


code:
/*
Minetest
Copyright (C) 2016 est31, <MTest31@outlook.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "joystick_controller.h"
#include "irrlichttypes_extrabloated.h"
#include "keys.h"
#include "settings.h"
#include "gettime.h"
#include "porting.h"
#include "../util/string.h"

bool JoystickButtonCmb::isTriggered(const irr::SEvent::SJoystickEvent &ev) const
{
	u32 buttons = ev.ButtonStates;

	buttons &= filter_mask;
	return buttons == compare_mask;
}

bool JoystickAxisCmb::isTriggered(const irr::SEvent::SJoystickEvent &ev) const
{
	s16 ax_val = ev.Axis[axis_to_compare];

	return (ax_val * direction < 0) && (thresh * direction > ax_val * direction);
}

// spares many characters
#define JLO_B_PB(A, B, C)    jlo.button_keys.push_back(JoystickButtonCmb(A, B, C))
#define JLO_A_PB(A, B, C, D) jlo.axis_keys.push_back(JoystickAxisCmb(A, B, C, D))

JoystickLayout create_default_layout()
{
	JoystickLayout jlo;

	jlo.axes_dead_border = 1024;

	const JoystickAxisLayout axes[JA_COUNT] = {
		{0, 1}, // JA_SIDEWARD_MOVE
		{1, 1}, // JA_FORWARD_MOVE
		{3, 1}, // JA_FRUSTUM_HORIZONTAL
		{4, 1}, // JA_FRUSTUM_VERTICAL
	};
	memcpy(jlo.axes, axes, sizeof(jlo.axes));

	u32 sb = 1 << 7; // START button mask
	u32 fb = 1 << 3; // FOUR button mask
	u32 bm = sb | fb; // Mask for Both Modifiers

	// The back button means "ESC".
	JLO_B_PB(KeyType::ESC,        1 << 6,      1 << 6);

	// The start button counts as modifier as well as use key.
	// JLO_B_PB(KeyType::USE,        sb,          sb));

	// Accessible without start modifier button pressed
	// regardless whether four is pressed or not
	JLO_B_PB(KeyType::SNEAK,      sb | 1 << 2, 1 << 2);

	// Accessible without four modifier button pressed
	// regardless whether start is pressed or not
	JLO_B_PB(KeyType::MOUSE_L,    fb | 1 << 4, 1 << 4);
	JLO_B_PB(KeyType::MOUSE_R,    fb | 1 << 5, 1 << 5);

	// Accessible without any modifier pressed
	JLO_B_PB(KeyType::JUMP,       bm | 1 << 0, 1 << 0);
	JLO_B_PB(KeyType::SPECIAL1,   bm | 1 << 1, 1 << 1);

	// Accessible with start button not pressed, but four pressed
	// TODO find usage for button 0
	JLO_B_PB(KeyType::DROP,       bm | 1 << 1, fb | 1 << 1);
	JLO_B_PB(KeyType::SCROLL_UP,  bm | 1 << 4, fb | 1 << 4);
	JLO_B_PB(KeyType::SCROLL_DOWN,bm | 1 << 5, fb | 1 << 5);

	// Accessible with start button and four pressed
	// TODO find usage for buttons 0, 1 and 4, 5

	// Now about the buttons simulated by the axes

	// Movement buttons, important for vessels
	JLO_A_PB(KeyType::FORWARD,  1,  1, 1024);
	JLO_A_PB(KeyType::BACKWARD, 1, -1, 1024);
	JLO_A_PB(KeyType::LEFT,     0,  1, 1024);
	JLO_A_PB(KeyType::RIGHT,    0, -1, 1024);

	// Scroll buttons
	JLO_A_PB(KeyType::SCROLL_UP,   2, -1, 1024);
	JLO_A_PB(KeyType::SCROLL_DOWN, 5, -1, 1024);

	return jlo;
}

JoystickLayout create_xbox_layout()
{
	JoystickLayout jlo;

	jlo.axes_dead_border = 7000;

	const JoystickAxisLayout axes[JA_COUNT] = {
		{0, 1}, // JA_SIDEWARD_MOVE
		{1, 1}, // JA_FORWARD_MOVE
		{2, 1}, // JA_FRUSTUM_HORIZONTAL
		{3, 1}, // JA_FRUSTUM_VERTICAL
	};
	memcpy(jlo.axes, axes, sizeof(jlo.axes));

	// The back button means "ESC".
	JLO_B_PB(KeyType::ESC,        1 << 8,  1 << 8); // back
	JLO_B_PB(KeyType::ESC,        1 << 9,  1 << 9); // start

	// 4 Buttons
	JLO_B_PB(KeyType::JUMP,        1 << 0,  1 << 0); // A/green
	JLO_B_PB(KeyType::ESC,         1 << 1,  1 << 1); // B/red
	JLO_B_PB(KeyType::SPECIAL1,    1 << 2,  1 << 2); // X/blue
	JLO_B_PB(KeyType::INVENTORY,   1 << 3,  1 << 3); // Y/yellow

	// Analog Sticks
	JLO_B_PB(KeyType::SPECIAL1,    1 << 11, 1 << 11); // left
	JLO_B_PB(KeyType::SNEAK,       1 << 12, 1 << 12); // right

	// Triggers
	JLO_B_PB(KeyType::MOUSE_L,     1 << 6,  1 << 6); // lt
	JLO_B_PB(KeyType::MOUSE_R,     1 << 7,  1 << 7); // rt
	JLO_B_PB(KeyType::SCROLL_UP,   1 << 4,  1 << 4); // lb
	JLO_B_PB(KeyType::SCROLL_DOWN, 1 << 5,  1 << 5); // rb

	// D-PAD
	JLO_B_PB(KeyType::ZOOM,        1 << 15, 1 << 15); // up
	JLO_B_PB(KeyType::DROP,        1 << 13, 1 << 13); // left
	JLO_B_PB(KeyType::SCREENSHOT,  1 << 14, 1 << 14); // right
	JLO_B_PB(KeyType::FREEMOVE,    1 << 16, 1 << 16); // down

	// Movement buttons, important for vessels
	JLO_A_PB(KeyType::FORWARD,  1,  1, 1024);
	JLO_A_PB(KeyType::BACKWARD, 1, -1, 1024);
	JLO_A_PB(KeyType::LEFT,     0,  1, 1024);
	JLO_A_PB(KeyType::RIGHT,    0, -1, 1024);

	return jlo;
}

JoystickController::JoystickController()
{
	m_joystick_id = 0;

	doubling_dtime = g_settings->getFloat("repeat_joystick_button_time");

	for (size_t i = 0; i < KeyType::INTERNAL_ENUM_COUNT; i++) {
		m_past_pressed_time[i] = 0;
	}
	clear();
}

void JoystickController::onJoystickConnect(const std::vector<irr::SJoystickInfo> &joystick_infos)
{
	s32         id     = g_settings->getS32("joystick_id");
	std::string layout = g_settings->get("joystick_type");

	if (id < 0 || (u16)id >= joystick_infos.size()) {
		// TODO: auto detection
		id = 0;
	}

	if (id >= 0 && (u16)id < joystick_infos.size()) {
		if (layout.empty() || layout == "auto")
			setLayoutFromControllerName(joystick_infos[id].Name.c_str());
		else
			setLayoutFromControllerName(layout);
	}

	m_joystick_id = id;
}

void JoystickController::setLayoutFromControllerName(const std::string &name)
{
	if (lowercase(name).find("xbox") != std::string::npos) {
		m_layout = create_xbox_layout();
	} else {
		m_layout = create_default_layout();
	}
}

bool JoystickController::handleEvent(const irr::SEvent::SJoystickEvent &ev)
{
	if (ev.Joystick != m_joystick_id)
		return false;

	m_internal_time = porting::getTimeMs() / 1000.f;

	std::bitset<KeyType::INTERNAL_ENUM_COUNT> keys_pressed;

	// First generate a list of keys pressed

	for (size_t i = 0; i < m_layout.button_keys.size(); i++) {
		if (m_layout.button_keys[i].isTriggered(ev)) {
			keys_pressed.set(m_layout.button_keys[i].key);
		}
	}

	for (size_t i = 0; i < m_layout.axis_keys.size(); i++) {
		if (m_layout.axis_keys[i].isTriggered(ev)) {
			keys_pressed.set(m_layout.axis_keys[i].key);
		}
	}

	// Then update the values

	for (size_t i = 0; i < KeyType::INTERNAL_ENUM_COUNT; i++) {
		if (keys_pressed[i]) {
			if (!m_past_pressed_keys[i] &&
					m_past_pressed_time[i] < m_internal_time - doubling_dtime) {
				m_past_pressed_keys[i] = true;
				m_past_pressed_time[i] = m_internal_time;
			}
		} else if (m_pressed_keys[i]) {
			m_past_released_keys[i] = true;
		}

		m_pressed_keys[i] = keys_pressed[i];
	}

	for (size_t i = 0; i < JA_COUNT; i++) {
		const JoystickAxisLayout &ax_la = m_layout.axes[i];
		m_axes_vals[i] = ax_la.invert * ev.Axis[ax_la.axis_id];
	}

	return true;
}

void JoystickController::clear()
{
	m_pressed_keys.reset();
	m_past_pressed_keys.reset();
	m_past_released_keys.reset();
	memset(m_axes_vals, 0, sizeof(m_axes_vals));
}

s16 JoystickController::getAxisWithoutDead(JoystickAxis axis)
{
	s16 v = m_axes_vals[axis];
	if (((v > 0) && (v < m_layout.axes_dead_border)) ||
			((v < 0) && (v > -m_layout.axes_dead_border)))
		return 0;
	return v;
}


code:
/*
Minetest
Copyright (C) 2016 est31, <MTest31@outlook.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef JOYSTICK_HEADER
#define JOYSTICK_HEADER

#include "irrlichttypes_extrabloated.h"
#include "keys.h"
#include <bitset>
#include <vector>

enum JoystickAxis {
	JA_SIDEWARD_MOVE,
	JA_FORWARD_MOVE,

	JA_FRUSTUM_HORIZONTAL,
	JA_FRUSTUM_VERTICAL,

	// To know the count of enum values
	JA_COUNT,
};

struct JoystickAxisLayout {
	u16 axis_id;
	// -1 if to invert, 1 if to keep it.
	int invert;
};

struct JoystickCombination {

	virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const=0;

	GameKeyType key;
};

struct JoystickButtonCmb : public JoystickCombination {

	JoystickButtonCmb() {}
	JoystickButtonCmb(GameKeyType key, u32 filter_mask, u32 compare_mask) :
		filter_mask(filter_mask),
		compare_mask(compare_mask)
	{
		this->key = key;
	}

	virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const;

	u32 filter_mask;
	u32 compare_mask;
};

struct JoystickAxisCmb : public JoystickCombination {

	JoystickAxisCmb() {}
	JoystickAxisCmb(GameKeyType key, u16 axis_to_compare, int direction, s16 thresh) :
		axis_to_compare(axis_to_compare),
		direction(direction),
		thresh(thresh)
	{
		this->key = key;
	}

	virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const;

	u16 axis_to_compare;

	// if -1, thresh must be smaller than the axis value in order to trigger
	// if  1, thresh must be bigger  than the axis value in order to trigger
	int direction;
	s16 thresh;
};

struct JoystickLayout {
	std::vector<JoystickButtonCmb> button_keys;
	std::vector<JoystickAxisCmb> axis_keys;
	JoystickAxisLayout axes[JA_COUNT];
	s16 axes_dead_border;
};

class JoystickController {

public:
	JoystickController();

	void onJoystickConnect(const std::vector<irr::SJoystickInfo> &joystick_infos);

	bool handleEvent(const irr::SEvent::SJoystickEvent &ev);
	void clear();

	bool wasKeyDown(GameKeyType b)
	{
		bool r = m_past_pressed_keys[b];
		m_past_pressed_keys[b] = false;
		return r;
	}
	bool getWasKeyDown(GameKeyType b)
	{
		return m_past_pressed_keys[b];
	}
	void clearWasKeyDown(GameKeyType b)
	{
		m_past_pressed_keys[b] = false;
	}

	bool wasKeyReleased(GameKeyType b)
	{
		bool r = m_past_released_keys[b];
		m_past_released_keys[b] = false;
		return r;
	}
	bool getWasKeyReleased(GameKeyType b)
	{
		return m_past_pressed_keys[b];
	}
	void clearWasKeyReleased(GameKeyType b)
	{
		m_past_pressed_keys[b] = false;
	}

	bool isKeyDown(GameKeyType b)
	{
		return m_pressed_keys[b];
	}

	s16 getAxis(JoystickAxis axis)
	{
		return m_axes_vals[axis];
	}

	s16 getAxisWithoutDead(JoystickAxis axis);

	f32 doubling_dtime;

private:
	void setLayoutFromControllerName(const std::string &name);

	JoystickLayout m_layout;

	s16 m_axes_vals[JA_COUNT];

	u8 m_joystick_id;

	std::bitset<KeyType::INTERNAL_ENUM_COUNT> m_pressed_keys;

	f32 m_internal_time;

	f32 m_past_pressed_time[KeyType::INTERNAL_ENUM_COUNT];

	std::bitset<KeyType::INTERNAL_ENUM_COUNT> m_past_pressed_keys;
	std::bitset<KeyType::INTERNAL_ENUM_COUNT> m_past_released_keys;
};

#endif


code:
/*
Minetest
Copyright (C) 2016 est31, <MTest31@outlook.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef KEYS_HEADER
#define KEYS_HEADER

#include <list>

class KeyType
{
public:
	enum T
	{
		// Player movement
		FORWARD,
		BACKWARD,
		LEFT,
		RIGHT,
		JUMP,
		SPECIAL1,
		SNEAK,
		AUTORUN,

		ESC,

		// Other
		DROP,
		INVENTORY,
		CHAT,
		CMD,
		CMD_LOCAL,
		CONSOLE,
		MINIMAP,
		FREEMOVE,
		FASTMOVE,
		NOCLIP,
		HOTBAR_PREV,
		HOTBAR_NEXT,
		MUTE,
		INC_VOLUME,
		DEC_VOLUME,
		CINEMATIC,
		SCREENSHOT,
		TOGGLE_HUD,
		TOGGLE_CHAT,
		TOGGLE_FORCE_FOG_OFF,
		TOGGLE_UPDATE_CAMERA,
		TOGGLE_DEBUG,
		TOGGLE_PROFILER,
		CAMERA_MODE,
		INCREASE_VIEWING_RANGE,
		DECREASE_VIEWING_RANGE,
		RANGESELECT,
		ZOOM,

		QUICKTUNE_NEXT,
		QUICKTUNE_PREV,
		QUICKTUNE_INC,
		QUICKTUNE_DEC,

		DEBUG_STACKS,

		// joystick specific keys
		MOUSE_L,
		MOUSE_R,
		SCROLL_UP,
		SCROLL_DOWN,

		// Fake keycode for array size and internal checks
		INTERNAL_ENUM_COUNT

	};
};

typedef KeyType::T GameKeyType;

#endif