lsnes/generic/controller.cpp

360 lines
9.7 KiB
C++
Raw Normal View History

2011-09-16 06:13:33 +03:00
#include "controller.hpp"
#include "lsnes.hpp"
#include "mainloop.hpp"
#include <map>
#include <sstream>
#include "window.hpp"
#include "command.hpp"
#include "framebuffer.hpp"
2011-09-16 06:13:33 +03:00
#include <snes/snes.hpp>
#include <ui-libsnes/libsnes.hpp>
#define BUTTON_LEFT 0 //Gamepad
#define BUTTON_RIGHT 1 //Gamepad
#define BUTTON_UP 2 //Gamepad
#define BUTTON_DOWN 3 //Gamepad
#define BUTTON_A 4 //Gamepad
#define BUTTON_B 5 //Gamepad
#define BUTTON_X 6 //Gamepad
#define BUTTON_Y 7 //Gamepad
#define BUTTON_L 8 //Gamepad & Mouse
#define BUTTON_R 9 //Gamepad & Mouse
#define BUTTON_SELECT 10 //Gamepad
#define BUTTON_START 11 //Gamepad & Justifier
#define BUTTON_TRIGGER 12 //Superscope.
#define BUTTON_CURSOR 13 //Superscope & Justifier
#define BUTTON_PAUSE 14 //Superscope
#define BUTTON_TURBO 15 //Superscope
2011-09-16 06:13:33 +03:00
namespace
{
porttype_t porttypes[2];
int analog_indices[3] = {-1, -1, -1};
bool analog_is_mouse[3];
//Current controls.
controls_t curcontrols;
controls_t autoheld_controls;
std::vector<controls_t> autofire_pattern;
2011-09-16 06:13:33 +03:00
2011-09-17 00:06:20 +03:00
void update_analog_indices() throw()
2011-09-16 06:13:33 +03:00
{
int i = 0;
for(unsigned j = 0; j < sizeof(analog_indices) / sizeof(analog_indices[0]); j++)
analog_indices[j] = -1;
for(unsigned j = 0; j < 8; j++) {
devicetype_t d = controller_type_by_logical(j);
switch(d) {
case DT_NONE:
case DT_GAMEPAD:
break;
case DT_MOUSE:
analog_is_mouse[i] = true;
analog_indices[i++] = j;
break;
case DT_SUPERSCOPE:
case DT_JUSTIFIER:
analog_is_mouse[i] = false;
analog_indices[i++] = j;
break;
}
}
}
std::map<std::string, std::pair<unsigned, unsigned>> buttonmap;
const char* buttonnames[] = {
"left", "right", "up", "down", "A", "B", "X", "Y", "L", "R", "select", "start", "trigger", "cursor",
"pause", "turbo"
};
void init_buttonmap()
{
static int done = 0;
if(done)
return;
for(unsigned i = 0; i < 8; i++)
for(unsigned j = 0; j < sizeof(buttonnames) / sizeof(buttonnames[0]); j++) {
std::ostringstream x;
x << (i + 1) << buttonnames[j];
buttonmap[x.str()] = std::make_pair(i, j);
}
done = 1;
}
//Do button action.
void do_button_action(unsigned ui_id, unsigned button, short newstate, bool do_xor, controls_t& c)
{
enum devicetype_t p = controller_type_by_logical(ui_id);
int x = controller_index_by_logical(ui_id);
int bid = -1;
switch(p) {
case DT_NONE:
window::out() << "No such controller #" << (ui_id + 1) << std::endl;
return;
case DT_GAMEPAD:
switch(button) {
case BUTTON_UP: bid = SNES_DEVICE_ID_JOYPAD_UP; break;
case BUTTON_DOWN: bid = SNES_DEVICE_ID_JOYPAD_DOWN; break;
case BUTTON_LEFT: bid = SNES_DEVICE_ID_JOYPAD_LEFT; break;
case BUTTON_RIGHT: bid = SNES_DEVICE_ID_JOYPAD_RIGHT; break;
case BUTTON_A: bid = SNES_DEVICE_ID_JOYPAD_A; break;
case BUTTON_B: bid = SNES_DEVICE_ID_JOYPAD_B; break;
case BUTTON_X: bid = SNES_DEVICE_ID_JOYPAD_X; break;
case BUTTON_Y: bid = SNES_DEVICE_ID_JOYPAD_Y; break;
case BUTTON_L: bid = SNES_DEVICE_ID_JOYPAD_L; break;
case BUTTON_R: bid = SNES_DEVICE_ID_JOYPAD_R; break;
case BUTTON_SELECT: bid = SNES_DEVICE_ID_JOYPAD_SELECT; break;
case BUTTON_START: bid = SNES_DEVICE_ID_JOYPAD_START; break;
default:
window::out() << "Invalid button for gamepad" << std::endl;
return;
};
break;
case DT_MOUSE:
switch(button) {
case BUTTON_L: bid = SNES_DEVICE_ID_MOUSE_LEFT; break;
case BUTTON_R: bid = SNES_DEVICE_ID_MOUSE_RIGHT; break;
default:
window::out() << "Invalid button for mouse" << std::endl;
return;
};
break;
case DT_JUSTIFIER:
switch(button) {
case BUTTON_START: bid = SNES_DEVICE_ID_JUSTIFIER_START; break;
case BUTTON_TRIGGER: bid = SNES_DEVICE_ID_JUSTIFIER_TRIGGER; break;
default:
window::out() << "Invalid button for justifier" << std::endl;
return;
};
break;
case DT_SUPERSCOPE:
switch(button) {
case BUTTON_TRIGGER: bid = SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER; break;
case BUTTON_CURSOR: bid = SNES_DEVICE_ID_SUPER_SCOPE_CURSOR; break;
case BUTTON_PAUSE: bid = SNES_DEVICE_ID_SUPER_SCOPE_PAUSE; break;
case BUTTON_TURBO: bid = SNES_DEVICE_ID_SUPER_SCOPE_TURBO; break;
default:
window::out() << "Invalid button for superscope" << std::endl;
return;
};
break;
};
if(do_xor)
c((x & 4) ? 1 : 0, x & 3, bid) ^= newstate;
else
c((x & 4) ? 1 : 0, x & 3, bid) = newstate;
}
//Do button action.
void do_button_action(unsigned ui_id, unsigned button, short newstate, bool do_xor = false)
{
if(do_xor)
do_button_action(ui_id, button, newstate, do_xor, autoheld_controls);
else
do_button_action(ui_id, button, newstate, do_xor, curcontrols);
}
function_ptr_command<tokensplitter&> autofire("autofire", "Set autofire pattern",
"Syntax: autofire <buttons|->...\nSet autofire pattern\n",
[](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
if(!t)
throw std::runtime_error("Need at least one frame for autofire");
std::vector<controls_t> new_autofire_pattern;
init_buttonmap();
while(t) {
std::string fpattern = t;
if(fpattern == "-")
new_autofire_pattern.push_back(controls_t());
else {
controls_t c;
while(fpattern != "") {
size_t split = fpattern.find_first_of(",");
std::string button = fpattern;
std::string rest;
if(split < fpattern.length()) {
button = fpattern.substr(0, split);
rest = fpattern.substr(split + 1);
}
if(!buttonmap.count(button)) {
std::ostringstream x;
x << "Invalid button '" << button << "'";
throw std::runtime_error(x.str());
}
auto g = buttonmap[button];
do_button_action(g.first, g.second, 1, false, c);
fpattern = rest;
}
new_autofire_pattern.push_back(c);
}
}
autofire_pattern = new_autofire_pattern;
});
class button_action : public command
{
public:
button_action(const std::string& cmd, int _type, unsigned _controller, std::string _button)
throw(std::bad_alloc)
: command(cmd)
{
commandn = cmd;
type = _type;
controller = _controller;
button = _button;
}
~button_action() throw() {}
void invoke(const std::string& args) throw(std::bad_alloc, std::runtime_error)
{
if(args != "")
throw std::runtime_error("This command does not take parameters");
init_buttonmap();
if(!buttonmap.count(button))
return;
auto i = buttonmap[button];
do_button_action(i.first, i.second, (type != 1) ? 1 : 0, (type == 2));
update_movie_state();
window::notify_screen_update();
}
std::string get_short_help() throw(std::bad_alloc)
{
return "Press/Unpress button";
}
std::string get_long_help() throw(std::bad_alloc)
{
return "Syntax: " + commandn + "\n"
"Presses/Unpresses button\n";
}
std::string commandn;
unsigned controller;
int type;
std::string button;
};
class button_action_helper
{
public:
button_action_helper()
{
for(size_t i = 0; i < sizeof(buttonnames) / sizeof(buttonnames[0]); ++i)
for(int j = 0; j < 3; ++j)
for(unsigned k = 0; k < 8; ++k) {
std::ostringstream x, y;
switch(j) {
case 0:
x << "+controller";
break;
case 1:
x << "-controller";
break;
case 2:
x << "controllerh";
break;
};
x << (k + 1);
x << buttonnames[i];
y << (k + 1);
y << buttonnames[i];
new button_action(x.str(), j, k, y.str());
}
}
} bah;
2011-09-16 06:13:33 +03:00
}
2011-09-17 00:06:20 +03:00
int controller_index_by_logical(unsigned lid) throw()
2011-09-16 06:13:33 +03:00
{
bool p1multitap = (porttypes[0] == PT_MULTITAP);
unsigned p1devs = port_types[porttypes[0]].devices;
unsigned p2devs = port_types[porttypes[1]].devices;
if(lid >= p1devs + p2devs)
return -1;
if(!p1multitap)
if(lid < p1devs)
return lid;
else
return 4 + lid - p1devs;
else
if(lid == 0)
return 0;
else if(lid < 5)
return lid + 3;
else
return lid - 4;
}
2011-09-17 00:06:20 +03:00
int controller_index_by_analog(unsigned aid) throw()
2011-09-16 06:13:33 +03:00
{
if(aid > 2)
return -1;
return analog_indices[aid];
}
2011-09-17 00:06:20 +03:00
bool controller_ismouse_by_analog(unsigned aid) throw()
2011-09-16 06:13:33 +03:00
{
if(aid > 2)
return false;
return analog_is_mouse[aid];
}
2011-09-17 00:06:20 +03:00
devicetype_t controller_type_by_logical(unsigned lid) throw()
2011-09-16 06:13:33 +03:00
{
int x = controller_index_by_logical(lid);
if(x < 0)
return DT_NONE;
enum porttype_t rawtype = porttypes[x >> 2];
if((x & 3) < port_types[rawtype].devices)
return port_types[rawtype].dtype;
else
return DT_NONE;
}
2011-09-17 00:06:20 +03:00
void controller_set_port_type(unsigned port, porttype_t ptype, bool set_core) throw()
2011-09-16 06:13:33 +03:00
{
if(set_core && ptype != PT_INVALID)
snes_set_controller_port_device(port != 0, port_types[ptype].bsnes_type);
porttypes[port] = ptype;
update_analog_indices();
}
controls_t get_current_controls(uint64_t frame)
{
if(autofire_pattern.size())
return curcontrols ^ autoheld_controls ^ autofire_pattern[frame % autofire_pattern.size()];
else
return curcontrols ^ autoheld_controls;
}
void send_analog_input(int32_t x, int32_t y, unsigned index)
{
if(controller_ismouse_by_analog(index)) {
x -= (framebuffer.width / 2);
y -= (framebuffer.height / 2);
} else {
auto g = get_scale_factors(framebuffer.width, framebuffer.height);
x /= g.first;
y /= g.second;
}
int aindex = controller_index_by_analog(index);
if(aindex < 0) {
window::out() << "No analog controller in slot #" << (index + 1) << std::endl;
return;
}
curcontrols(aindex >> 2, aindex & 3, 0) = x;
curcontrols(aindex >> 2, aindex & 3, 1) = y;
}
void set_curcontrols_reset(int32_t delay)
{
if(delay >= 0) {
curcontrols(CONTROL_SYSTEM_RESET) = 1;
curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI) = delay / 10000;
curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO) = delay % 10000;
} else {
curcontrols(CONTROL_SYSTEM_RESET) = 0;
curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI) = 0;
curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO) = 0;
}
}