package com.nilunder.bdx.inputs;
import java.util.*;
import javax.vecmath.*;
import com.badlogic.gdx.controllers.*;
import com.badlogic.gdx.utils.*;
import com.nilunder.bdx.utils.Named;
public class Gamepad implements Named {
public static class Axis{
public int code;
public float deadZone;
public float value;
public Axis(int code, float deadZone){
this.code = code;
this.deadZone = deadZone;
}
public Axis(int code){
this(code, 0.25f);
}
}
public static class Stick{
public Axis x;
public Axis y;
public Stick(Axis x, Axis y){
this.x = x;
this.y = y;
}
public Vector3f pos(){
return new Vector3f(x.value, -y.value, 0);
}
public void deadZone(float dz){
x.deadZone = dz;
y.deadZone = dz;
}
}
public static class Profile{
public static class FnProcessAxis{
public float[] eval(int axis, float value){
return null;
}
}
public FnProcessAxis processAxis;
public String name;
public HashMap<String,Integer> btnToCode;
public HashMap<Integer,GdxProcessor.UpDownLog> codeToLog;
public HashMap<String,Axis> axes;
public HashMap<Integer,Axis> codeToAxis;
public HashMap<String,Stick> sticks;
public Profile(String name){
this.name = name;
btnToCode = new HashMap<String,Integer>();
axes = new HashMap<String,Axis>();
sticks = new HashMap<String,Stick>();
}
public GdxProcessor.UpDownLog btnLog(String btn){
return codeToLog.get(btnToCode.get(btn));
}
}
public Controller controller;
public Profile profile;
public HashMap<String,Axis> axes;
public HashMap<String,Stick> sticks;
public HashMap<String,Profile> profiles;
private int index;
public Gamepad(int i){
index = i;
Array controllers = Controllers.getControllers();
if (controllers.size > index)
controller = (Controller)controllers.get(index);
else
throw new IndexOutOfBoundsException("ERROR: There is no gamepad connected at index " + index);
controller.addListener(new GdxProcessor.GamepadAdapter(this));
profiles = new HashMap<String,Profile>();
Profile p = new Profile("XBOX");
p.btnToCode.put("x", 3);
p.btnToCode.put("y", 4);
p.btnToCode.put("a", 0);
p.btnToCode.put("b", 1);
p.btnToCode.put("white", 5);
p.btnToCode.put("black", 2);
p.btnToCode.put("back", 6);
p.btnToCode.put("start", 7);
p.btnToCode.put("rs", 8);
p.btnToCode.put("ls", 9);
p.axes.put("lx", new Axis(0));
p.axes.put("ly", new Axis(1));
p.axes.put("rx", new Axis(3));
p.axes.put("ry", new Axis(4));
p.axes.put("lt", new Axis(2));
p.axes.put("rt", new Axis(5));
p.sticks.put("left", new Stick(p.axes.get("lx"), p.axes.get("ly")));
p.sticks.put("right", new Stick(p.axes.get("rx"), p.axes.get("ry")));
// Each profile has a processAxis reference, which can be
// set to a new FnProcessAxis function object, to convert
// incoming axis codes and values before they're actually set.
// Here, we use it to convert awkward xbox trigger axis values
// (-1 when released, 1 when fully pressed) to something more
// sensible (0 when released, 1 when fully pressed):
//
p.processAxis = new Profile.FnProcessAxis(){
public float[] eval(int axis, float value){
if (axis == 2 || axis == 5){ // "LT" or "RT"
value = (value + 1) / 2;
}
return new float[]{axis, value};
}
};
// The system will "buttonize" the dpad to 1XX button codes,
// which can be mapped like this:
//
p.btnToCode.put("left", 100 + PovDirection.west.ordinal());
p.btnToCode.put("right", 100 + PovDirection.east.ordinal());
p.btnToCode.put("up", 100 + PovDirection.north.ordinal());
p.btnToCode.put("down", 100 + PovDirection.south.ordinal());
// Similarly for available axes, but with +/- 2XX button codes:
//
p.btnToCode.put("ls-left", -200 - p.axes.get("lx").code);
p.btnToCode.put("ls-right", 200 + p.axes.get("lx").code);
p.btnToCode.put("ls-up", -200 - p.axes.get("ly").code);
p.btnToCode.put("ls-down", 200 + p.axes.get("ly").code);
p.btnToCode.put("rs-left", -200 - p.axes.get("rx").code);
p.btnToCode.put("rs-right", 200 + p.axes.get("rx").code);
p.btnToCode.put("rs-up", -200 - p.axes.get("ry").code);
p.btnToCode.put("rs-down", 200 + p.axes.get("ry").code);
p.btnToCode.put("rt", 200 + p.axes.get("rt").code);
p.btnToCode.put("lt", 200 + p.axes.get("lt").code);
profiles.put(p.name, p);
p = new Profile("XBOX360");
p.btnToCode = new HashMap<String,Integer>(profiles.get("XBOX").btnToCode);
p.btnToCode.remove("white");
p.btnToCode.remove("black");
p.btnToCode.put("x", 2);
p.btnToCode.put("y", 3);
p.btnToCode.put("lb", 4);
p.btnToCode.put("rb", 5);
p.btnToCode.put("ls", 8);
p.btnToCode.put("rs", 9);
String os = System.getProperty("os.name");
if (os.contains("Linux")) {
p.btnToCode.put("ls", 9);
p.btnToCode.put("rs", 10);
}
p.axes.put("lx", new Axis(1));
p.axes.put("ly", new Axis(0));
if (os.contains("Linux")) {
p.axes.put("lx", new Axis(0));
p.axes.put("ly", new Axis(1));
}
p.axes.put("rx", new Axis(3));
p.axes.put("ry", new Axis(2));
if (os.contains("Linux")) {
p.axes.put("rx", new Axis(3));
p.axes.put("ry", new Axis(4));
}
p.axes.put("lt", new Axis(4));
p.axes.put("rt", new Axis(5));
if (os.contains("Linux")) {
p.axes.put("lt", new Axis(2));
p.axes.put("rt", new Axis(5));
}
p.sticks.put("left", new Stick(p.axes.get("lx"), p.axes.get("ly")));
p.sticks.put("right", new Stick(p.axes.get("rx"), p.axes.get("ry")));
p.btnToCode.put("ls-left", -200 - p.axes.get("lx").code);
p.btnToCode.put("ls-right", 200 + p.axes.get("lx").code);
p.btnToCode.put("ls-up", -200 - p.axes.get("ly").code);
p.btnToCode.put("ls-down", 200 + p.axes.get("ly").code);
p.btnToCode.put("rs-left", -200 - p.axes.get("rx").code);
p.btnToCode.put("rs-right", 200 + p.axes.get("rx").code);
p.btnToCode.put("rs-up", -200 - p.axes.get("ry").code);
p.btnToCode.put("rs-down", 200 + p.axes.get("ry").code);
p.btnToCode.put("rt", 200 + p.axes.get("rt").code);
p.btnToCode.put("lt", 200 + p.axes.get("lt").code);
// Unlike the original xbox, which uses one axis per trigger,
// xbox360 gamepads use a single axis for both triggers, where RT
// values are positive, while LT values are negative. We convert
// to effectively place RT on a different axis index (5), which enables us
// to use positive values for both triggers.
//
p.processAxis = new Profile.FnProcessAxis(){
public float[] eval(int axis, float value){
String os = System.getProperty("os.name"); // Gotta do it again here for HTML to build
if (os.contains("Windows")) {
if (axis == 4 && value < 0) {
axis = 5; // RT
value = -value;
}
}
else if ((os.contains("Linux")) && (axis == axes.get("lt").code || axis == axes.get("rt").code)) { // On Linux, each trigger goes from -1 (released) to 1 (fully pressed)
value += 1;
value /= 2;
value = Math.min(Math.max(value, 0), 1); // For some reason, the maximum range of the trigger grows when adding one to the value
}
return new float[]{axis, value};
}
};
profiles.put(p.name, p);
p = new Profile("Generic");
for (int a = 0; a < 32; a++)
p.btnToCode.put("b" + String.valueOf(a), a);
for (int a = 0; a < 16; a++) {
String axisName = "a" + String.valueOf(a);
Axis axis = new Axis(a);
p.axes.put(axisName, axis);
p.btnToCode.put(axisName + "-", -200 - axis.code);
p.btnToCode.put(axisName + "+", 200 + axis.code);
}
for (int a = 0; a < 8; a++) {
Stick s = new Stick(p.axes.get("a" + String.valueOf(a * 2)),
p.axes.get("a" + String.valueOf(a * 2 + 1)));
p.sticks.put("s" + String.valueOf(a), s);
}
p.btnToCode.put("left", 100 + PovDirection.west.ordinal());
p.btnToCode.put("right", 100 + PovDirection.east.ordinal());
p.btnToCode.put("up", 100 + PovDirection.north.ordinal());
p.btnToCode.put("down", 100 + PovDirection.south.ordinal());
profiles.put(p.name, p);
profile("XBOX360"); // probably most common, so it's the default
}
public void profile(String name){
profile = profiles.get(name);
if (profile.codeToLog == null){
profile.codeToLog = new HashMap<>();
for (Integer code : profile.btnToCode.values())
profile.codeToLog.put(code, new GdxProcessor.UpDownLog());
profile.codeToAxis = new HashMap<>();
for (Axis axis : profile.axes.values())
profile.codeToAxis.put(axis.code, axis);
axes = profile.axes;
sticks = profile.sticks;
}
}
public boolean btnHit(String btn){
GdxProcessor.UpDownLog b = profile.btnLog(btn);
return b.hit == GdxProcessor.currentTick;
}
public boolean btnDown(String btn){
GdxProcessor.UpDownLog b = profile.btnLog(btn);
return b.hit > b.up;
}
public boolean btnUp(String btn){
GdxProcessor.UpDownLog b = profile.btnLog(btn);
return b.up == GdxProcessor.currentTick;
}
public int index(){
return index;
}
public String name(){
return controller.getName();
}
public String toString(){
return name();
}
public ArrayList<Integer> downButtons() {
ArrayList<Integer> buttons = new ArrayList<Integer>();
for (int index : profile.btnToCode.values()) {
if (controller.getButton(index))
buttons.add(index);
}
return buttons;
}
public ArrayList<ArrayList<Integer>> downAxes(float deadZone){
ArrayList<ArrayList<Integer>> axes = new ArrayList<ArrayList<Integer>>();
for (Axis axis : profile.axes.values()) {
if (Math.abs(axis.value) > deadZone) {
ArrayList<Integer> ar = new ArrayList<Integer>();
ar.add(axis.code);
ar.add((int) Math.signum(axis.value));
axes.add(ar);
}
}
return axes;
}
public ArrayList<ArrayList<Integer>> downAxes() {
return downAxes(0.2f);
}
public ArrayList<String> downInputs(){
ArrayList<String> inputs = new ArrayList<String>();
if (profile != null) {
for (String s : profile.btnToCode.keySet()) {
if (btnDown(s))
inputs.add(s);
}
}
return inputs;
}
public ArrayList<String> hitInputs(){
ArrayList<String> inputs = new ArrayList<String>();
if (profile != null) {
for (String s : profile.btnToCode.keySet()) {
if (btnHit(s))
inputs.add(s);
}
}
return inputs;
}
}