Add and Remove Joysticks mid-game

@pspeed: I agree that it’s not trivial. When is this release that’s being mentioned planned to happen (just a ballpark)? Just out of curiosity, I’m not really keeping up with Engine news.

@Pixelapp: Sorry, I keep thinking that your additions only benefit those doing Android development. As @pspeed pointed out, I’m not hugely familiar with the engine code, or that sort of code in general.

Anyway, it’s good to know people are planning (or are) doing something about this, I’m just not sure if this will ever actually help me or not. I appreciate the discussion, though.

@Inferno630 Yes, It’ll only affect Android development.

@Pixelapp: Ah, then, while that’s an awesome thing to hear someone working on; as I mentioned, it’s not particular helpful for my case. :confused:

@Pixelapp said: @Inferno630 Sure you would be able to use it. I the sample jmonkey projects everyday without changing them so you don't even have to know any changes were ever made by me.

@pspeed I’m not counting on anybody to try this patch. But you are more than welcome to use it if you or the JMonkey community find it useful. I’m sure I’ll use it, it’s a freaking awesome patch!

Yeah, your patch is cool but not really important to this conversation since it has nothing to do with the OP’s request, I guess. I’m not an android guy so I won’t be applying it but I know others are keeping an eye on it.

I was talking to the OP since he was interested in diving in and fixing the problems on desktop… which is what this thread is about.

If OP can add new code to Jmonkey to then go ahead. Forget I was here. >.>

@Pixelapp: It’s my fault for not specifying it was a computer game. Sorry for dragging you into this.

@pspeed: I don’t think I have the skill to contribute any actual code to the progress, but I believe I found the source of the trouble, and it’s purely in JInput (provided the version JME uses is the one I looked at, which I’m pretty sure it is). After doing a bit of searching, there’s a point where JInputJoyInput initializes the Joysticks and such using ControllerEnvironment.getDefaultEnvironment(). The trouble with this, however, is that the Default Environment is static, and initialized once; which means the list is never updated. I’m hoping to make an extension of some sort to JInputJoyInput to try to add a new instantiation (as someone seems to have done here http://www.java-gaming.org/index.php?topic=23964.0 [well, they adjusted the actual Environment, so I plan on diverting the adjusted JInputJoyInput to an adjusted version of the Environment]). So the list never changes, no matter what you do. I don’t believe the devs here really mess with JInput at all, but I could be wrong. I don’t know if that fact will help you with anything at all or not, but that’s what I’ve found, and I plan on playing around a little bit longer to see if I can at least get it to work on my game (without completely destroying something). I have no plans to directly modify any source code directly, because I really have no clue how I’d ever do that, or even if I could.

I have no idea if JInput is still being modified, but here are the ControllerEnvironment.java and DefaultControllerEnvironment.java classes, respectively.

https://java.net/projects/jinput/sources/svn/content/trunk/coreAPI/src/java/net/java/games/input/ControllerEnvironment.java?rev=252

https://java.net/projects/jinput/sources/svn/content/trunk/coreAPI/src/java/net/java/games/input/DefaultControllerEnvironment.java?rev=252

And let me point out that I by no means am expecting you to actually do anything with this information, I just figured I’d say it. :stuck_out_tongue:

If it’s a JInput problem/limitation then that would kind of suck pretty hard. I don’t have time to look through their code right now but these links will be useful to me later.

EDIT:

@pspeed: Well, it can be done, but as of yet, I can’t actually use it for my purposes. Altering the Input Manager stops me from being able to use Nifty (due to the NiftyJmeDisplay becoming outdated). So unless someone knows how to get that to work again, I’m out of luck. :confused: (apparently you can’t just remove the processor for it, create a new instance, and add the processor for the new instance). So I can either get a blank screen or a useless screen as of yet.

Anyway, here’re the dark deeds that seem to have been successful.

You need all of these things to do this. All of them are barely modified from their original versions, but still modified.

AdjustedControllerEnvironment.java

[java]package mygame;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import net.java.games.input.Controller;
import net.java.games.input.ControllerEnvironment;
import net.java.games.input.ControllerEvent;
import net.java.games.input.ControllerListener;

public abstract class AdaptedControllerEnvironment extends ControllerEnvironment
{
public static AdaptedControllerEnvironment defaultEnvironment = null;

protected final ArrayList controllerListeners = new ArrayList();

static void logln(String msg)
{
log(msg + “\n”);
}

static void log(String msg) {
System.out.print(msg);
}

public abstract Controller[] getControllers();

@Override
public void addControllerListener(ControllerListener l)
{
assert (l != null);
this.controllerListeners.add(l);
}

public abstract boolean isSupported();

@Override
public void removeControllerListener(ControllerListener l)
{
assert (l != null);
this.controllerListeners.remove(l);
}

@Override
protected void fireControllerAdded(Controller c)
{
ControllerEvent ev = new ControllerEvent©;
Iterator it = this.controllerListeners.iterator();
while (it.hasNext())
((ControllerListener)it.next()).controllerAdded(ev);
}

@Override
protected void fireControllerRemoved(Controller c)
{
ControllerEvent ev = new ControllerEvent©;
Iterator it = this.controllerListeners.iterator();
while (it.hasNext())
((ControllerListener)it.next()).controllerRemoved(ev);
}

public static AdaptedControllerEnvironment getDefaultEnvironment()
{
if (defaultEnvironment == null)
{
defaultEnvironment = new AdaptedDefaultControllerEnvironment();
}
return defaultEnvironment;
}

public static void resetEnvironment()
{
defaultEnvironment = null;
}
}[/java]

AdaptedDefaultControllerEnvironment.java

[java]package mygame;

import java.io.File;
import java.io.PrintStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.StringTokenizer;
import net.java.games.input.Controller;
import net.java.games.input.ControllerEnvironment;
import net.java.games.util.plugins.Plugins;

public class AdaptedDefaultControllerEnvironment extends AdaptedControllerEnvironment
{
static String libPath;
private ArrayList controllers;
private Collection loadedPlugins = new ArrayList();

static void loadLibrary(final String lib_name)
{
AccessController.doPrivileged(new PrivilegedAction() {

  public final Object run() { String lib_path = System.getProperty("net.java.games.input.librarypath");
    if (lib_path != null)
      System.load(lib_path + File.separator + System.mapLibraryName(lib_name));
    else
      System.loadLibrary(lib_name);
    return null; }
});

}

static String getPrivilegedProperty(final String property)
{
return (String)AccessController.doPrivileged(new PrivilegedAction() {

  public Object run() { return System.getProperty(property); }

});

}

static String getPrivilegedProperty(final String property, final String default_value)
{
return (String)AccessController.doPrivileged(new PrivilegedAction() {

  public Object run() { return System.getProperty(property, default_value); }

});

}

public Controller[] getControllers()
{
if (this.controllers == null)
{
this.controllers = new ArrayList();
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
AdaptedDefaultControllerEnvironment.this.scanControllers();
return null;
}
});
String pluginClasses = getPrivilegedProperty(“jinput.plugins”, “”) + " " + getPrivilegedProperty(“net.java.games.input.plugins”, “”);
if ((!getPrivilegedProperty(“jinput.useDefaultPlugin”, “true”).toLowerCase().trim().equals(“false”)) && (!getPrivilegedProperty(“net.java.games.input.useDefaultPlugin”, “true”).toLowerCase().trim().equals(“false”))) {
String osName = getPrivilegedProperty(“os.name”, “”).trim();
if (osName.equals(“Linux”)) {
pluginClasses = pluginClasses + " net.java.games.input.LinuxEnvironmentPlugin";
} else if (osName.equals(“Mac OS X”)) {
pluginClasses = pluginClasses + " net.java.games.input.OSXEnvironmentPlugin";
} else if ((osName.equals(“Windows XP”)) || (osName.equals(“Windows Vista”))) {
pluginClasses = pluginClasses + " net.java.games.input.DirectAndRawInputEnvironmentPlugin";
} else if ((osName.equals(“Windows 98”)) || (osName.equals(“Windows 2000”))) {
pluginClasses = pluginClasses + " net.java.games.input.DirectInputEnvironmentPlugin";
} else if (osName.startsWith(“Windows”)) {
System.out.println(“WARNING: Found unknown Windows version: " + osName);
System.out.println(“Attempting to use default windows plug-in.”);
System.out.flush();
pluginClasses = pluginClasses + " net.java.games.input.DirectAndRawInputEnvironmentPlugin”;
} else {
System.out.println(“Trying to use default plugin, OS name " + osName + " not recognised”);
}
}

  StringTokenizer pluginClassTok = new StringTokenizer(pluginClasses, " \t\n\r\f,;:");
  while (pluginClassTok.hasMoreTokens()) {
    String className = pluginClassTok.nextToken();
    try {
      if (!this.loadedPlugins.contains(className)) {
        System.out.println("Loading: " + className);
        Class ceClass = Class.forName(className);
        ControllerEnvironment ce = (ControllerEnvironment)ceClass.newInstance();
        if (ce.isSupported()) {
          addControllers(ce.getControllers());
          this.loadedPlugins.add(ce.getClass().getName());
        } else {

// logln(ceClass.getName() + " is not supported");
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
Controller[] ret = new Controller[this.controllers.size()];
Iterator it = this.controllers.iterator();
int i = 0;
while (it.hasNext()) {
ret[i] = ((Controller)it.next());
i++;
}
return ret;
}

private void scanControllers()
{
String pluginPathName = getPrivilegedProperty(“jinput.controllerPluginPath”);
if (pluginPathName == null) {
pluginPathName = “controller”;
}

scanControllersAt(getPrivilegedProperty("java.home") + File.separator + "lib" + File.separator + pluginPathName);

scanControllersAt(getPrivilegedProperty("user.dir") + File.separator + pluginPathName);

}

private void scanControllersAt(String path)
{
File file = new File(path);
if (!file.exists())
return;
try
{
Plugins plugins = new Plugins(file);
Class[] envClasses = plugins.getExtends(ControllerEnvironment.class);
for (int i = 0; i < envClasses.length; i++)
try {
// ControllerEnvironment.logln("ControllerEnvironment " + envClasses[i].getName() + " loaded by " + envClasses[i].getClassLoader());

      ControllerEnvironment ce = (ControllerEnvironment)envClasses[i].newInstance();

      if (ce.isSupported()) {
        addControllers(ce.getControllers());
        this.loadedPlugins.add(ce.getClass().getName());
      } else {

// logln(envClasses[i].getName() + " is not supported");
}
} catch (Throwable e) {
e.printStackTrace();
}
}
catch (Exception e) {
e.printStackTrace();
}
}

private void addControllers(Controller[] c)
{
for (int i = 0; i < c.length; i++)
this.controllers.add(c[i]);
}

public boolean isSupported()
{
return true;
}
}[/java]

ShowdownJoyInput.java

[java]package mygame;

import com.jme3.input.AbstractJoystick;
import com.jme3.input.DefaultJoystickAxis;
import com.jme3.input.DefaultJoystickButton;
import com.jme3.input.InputManager;
import com.jme3.input.JoyInput;
import com.jme3.input.Joystick;
import com.jme3.input.JoystickAxis;
import com.jme3.input.JoystickButton;
import com.jme3.input.JoystickCompatibilityMappings;
import com.jme3.input.RawInputListener;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.java.games.input.*;
import net.java.games.input.Component.Identifier;
import net.java.games.input.Component.Identifier.Axis;
import net.java.games.input.Component.Identifier.Button;
import net.java.games.input.Component.POV;

public class ShowdownJoyInput implements JoyInput {

private static final Logger logger = Logger.getLogger(InputManager.class.getName());

private boolean inited = false;
private JInputJoystick[] joysticks;
private RawInputListener listener;

private Map&lt;Controller, JInputJoystick&gt; joystickIndex = new HashMap&lt;Controller, JInputJoystick&gt;();

public void setJoyRumble(int joyId, float amount){

    if( joyId &gt;= joysticks.length )        
        throw new IllegalArgumentException();
        
    Controller c = joysticks[joyId].controller;
    for (Rumbler r : c.getRumblers()){
        r.rumble(amount);
    }
}

public Joystick[] loadJoysticks(InputManager inputManager){
    AdaptedControllerEnvironment ce =
        AdaptedControllerEnvironment.getDefaultEnvironment();

    Controller[] cs = ce.getControllers();
    
    List&lt;Joystick&gt; list = new ArrayList&lt;Joystick&gt;();
    for( Controller c : ce.getControllers() ) {
        if (c.getType() == Controller.Type.KEYBOARD
         || c.getType() == Controller.Type.MOUSE)
            continue;

        logger.log(Level.INFO, "Attempting to create joystick for: \"{0}\"", c);        

        // Try to create it like a joystick
        JInputJoystick stick = new JInputJoystick(inputManager, this, c, list.size(),
                                                  c.getName()); 
        for( Component comp : c.getComponents() ) {
            stick.addComponent(comp);                   
        }

        // If it has no axes then we'll assume it's not
        // a joystick
        if( stick.getAxisCount() == 0 ) {
            logger.log(Level.INFO, "Not a joystick: {0}", c);
            continue;
        }

        joystickIndex.put(c, stick);
        list.add(stick);                      
    }

    joysticks = list.toArray( new JInputJoystick[list.size()] );
    
    return joysticks;
}

public void initialize() {
    inited = true;
}


public void update() {
    AdaptedControllerEnvironment ce =
        AdaptedControllerEnvironment.getDefaultEnvironment();

    Controller[] cs = ce.getControllers();
    Event e = new Event();
    for (int i = 0; i &lt; cs.length; i++){
        Controller c = cs[i];
        
        JInputJoystick stick = joystickIndex.get(c);
        if( stick == null )
            continue;
            
        if( !c.poll() )
            continue;
    
        int joyId = stick.getJoyId();
                
        EventQueue q = c.getEventQueue();
        while (q.getNextEvent(e)){
            Identifier id = e.getComponent().getIdentifier();
            if (id == Identifier.Axis.POV){
                float x = 0, y = 0;
                float v = e.getValue();

                if (v == POV.CENTER){
                    x = 0; y = 0;
                }else if (v == POV.DOWN){
                    x = 0; y = -1f;
                }else if (v == POV.DOWN_LEFT){
                    x = -1f; y = -1f;
                }else if (v == POV.DOWN_RIGHT){
                    x = 1f; y = -1f;
                }else if (v == POV.LEFT){
                    x = -1f; y = 0;
                }else if (v == POV.RIGHT){
                    x = 1f; y = 0;
                }else if (v == POV.UP){
                    x = 0; y = 1f;
                }else if (v == POV.UP_LEFT){
                    x = -1f; y = 1f;
                }else if (v == POV.UP_RIGHT){
                    x = 1f; y = 1f;
                }

                JoyAxisEvent evt1 = new JoyAxisEvent(stick.povX, x);
                JoyAxisEvent evt2 = new JoyAxisEvent(stick.povY, y);
                listener.onJoyAxisEvent(evt1);
                listener.onJoyAxisEvent(evt2);
            }else if (id instanceof Axis){
                float value = e.getValue();
                
                JoystickAxis axis = stick.axisIndex.get(e.getComponent());
                JoyAxisEvent evt = new JoyAxisEvent(axis, value);
                listener.onJoyAxisEvent(evt);
            }else if (id instanceof Button){
                
                JoystickButton button = stick.buttonIndex.get(e.getComponent());                    
                JoyButtonEvent evt = new JoyButtonEvent(button, e.getValue() == 1f);
                listener.onJoyButtonEvent(evt);
            }
        }                             
    }
}

public void destroy() {
    inited = false;
}

public boolean isInitialized() {
    return inited;
}

public void setInputListener(RawInputListener listener) {
    this.listener = listener;
}

public long getInputTimeNanos() {
    return 0;
}

protected class JInputJoystick extends AbstractJoystick {

    private JoystickAxis nullAxis;
    private Controller controller;    
    private JoystickAxis xAxis;
    private JoystickAxis yAxis;
    private JoystickAxis povX;
    private JoystickAxis povY;
    private Map&lt;Component, JoystickAxis&gt; axisIndex = new HashMap&lt;Component, JoystickAxis&gt;();
    private Map&lt;Component, JoystickButton&gt; buttonIndex = new HashMap&lt;Component, JoystickButton&gt;();

    public JInputJoystick( InputManager inputManager, JoyInput joyInput, Controller controller, 
                           int joyId, String name ) {
        super( inputManager, joyInput, joyId, name );
        
        this.controller = controller;
        
        this.nullAxis = new DefaultJoystickAxis( getInputManager(), this, -1, 
                                                 "Null", "null", false, false, 0 );
        this.xAxis = nullAxis;                                                     
        this.yAxis = nullAxis;                                                     
        this.povX = nullAxis;
        this.povY = nullAxis;                                                     
    }

    protected void addComponent( Component comp ) {
        
        Identifier id = comp.getIdentifier();
        if( id instanceof Button ) {
            addButton(comp);
        } else if( id instanceof Axis ) {
            addAxis(comp);
        } else {
            logger.log(Level.INFO, "Ignoring: \"{0}\"", comp);
        }
    }

    protected void addButton( Component comp ) {
    
        logger.log(Level.INFO, "Adding button: \"{0}\" id:" + comp.getIdentifier(), comp);
        
        Identifier id = comp.getIdentifier();            
        if( !(id instanceof Button) ) {
            throw new IllegalArgumentException( "Component is not an axis:" + comp );
        }

        String name = comp.getName();
        String original = id.getName();
        String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original );
        if( name != original ) {
            logger.log(Level.INFO, "Remapped:" + original + " to:" + logicalId);
        }

        JoystickButton button = new DefaultJoystickButton( getInputManager(), this, getButtonCount(),
                                                           name, logicalId );
        addButton(button);                                                               
        buttonIndex.put( comp, button );
    }
    
    protected void addAxis( Component comp ) {

        logger.log(Level.INFO, "Adding axis: \"{0}\" id:" + comp.getIdentifier(), comp );
                        
        Identifier id = comp.getIdentifier();
        if( !(id instanceof Axis) ) {
            throw new IllegalArgumentException( "Component is not an axis:" + comp );
        }
        
        String name = comp.getName();
        String original = id.getName();
        String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original );
        if( name != original ) {
            logger.log(Level.INFO, "Remapped:" + original + " to:" + logicalId);
        }
        
        JoystickAxis axis = new DefaultJoystickAxis( getInputManager(), 
                                                     this, getAxisCount(), name, logicalId,
                                                     comp.isAnalog(), comp.isRelative(), 
                                                     comp.getDeadZone() );
        addAxis(axis);                                                          
        axisIndex.put( comp, axis );
                   
        // Support the X/Y axis indexes
        if( id == Axis.X ) {
            xAxis = axis;
        } else if( id == Axis.Y ) {
            yAxis = axis;
        } else if( id == Axis.POV ) {
            
            // Add two fake axes for the JME provided convenience
            // axes: AXIS_POV_X, AXIS_POV_Y
            povX = new DefaultJoystickAxis( getInputManager(), 
                                            this, getAxisCount(), JoystickAxis.POV_X, 
                                            id.getName() + "_x",
                                            comp.isAnalog(), comp.isRelative(), comp.getDeadZone() );
            logger.log(Level.INFO, "Adding axis: \"{0}\" id:" + id.getName() + "_x", povX.getName() );
            addAxis(povX);
            povY = new DefaultJoystickAxis( getInputManager(), 
                                            this, getAxisCount(), JoystickAxis.POV_Y, 
                                            id.getName() + "_y",
                                            comp.isAnalog(), comp.isRelative(), comp.getDeadZone() );
            logger.log(Level.INFO, "Adding axis: \"{0}\" id:" + id.getName() + "_y", povY.getName() );
            addAxis(povY);
        }
        
    }

    @Override
    public JoystickAxis getXAxis() {
        return xAxis;
    }     

    @Override
    public JoystickAxis getYAxis() {
        return yAxis;
    }     

    @Override
    public JoystickAxis getPovXAxis() {
        return povX;
    }     

    @Override
    public JoystickAxis getPovYAxis() {
        return povY;
    }     

    @Override
    public int getXAxisIndex(){
        return xAxis.getAxisId();
    }

    @Override
    public int getYAxisIndex(){
        return yAxis.getAxisId();
    }
}    

}
[/java]

And lastly (sort of; there’s also the business of making sure all your files use the right input manager now), the following method in the extension of a SimpleApplication. The calling of this method should be the first thing in SimpleInitApp():

[java]public void rebuildInputManager()
{
if (AdaptedControllerEnvironment.defaultEnvironment != null)
{
AdaptedControllerEnvironment.resetEnvironment();
}
this.joyInput = new ShowdownJoyInput();
System.out.println("JOY to the world: " + joyInput.loadJoysticks(inputManager).length);
this.joyInput.initialize();
inputManager = new InputManager(this.mouseInput, this.keyInput, this.joyInput, this.touchInput);
if (inputManager != null)
{

        if (context.getType() == Type.Display) {
            inputManager.addMapping(INPUT_MAPPING_EXIT, new KeyTrigger(KeyInput.KEY_ESCAPE));
        }

        if (stateManager.getState(StatsAppState.class) != null) {
            inputManager.addMapping(INPUT_MAPPING_HIDE_STATS, new KeyTrigger(KeyInput.KEY_F5));
            inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS);            
        }
        
        inputManager.addListener(actionListener, INPUT_MAPPING_EXIT);            
    }
    
    System.out.println("Input Check");
    System.out.println(inputManager);
    System.out.println(this.getInputManager());
    
    //if (niftyDisplay != null)
   // {
    //    guiViewPort.removeProcessor(niftyDisplay);
    //    niftyDisplay = new NiftyJmeDisplay(assetManager, inputManager, audioRenderer, guiViewPort);
    //    nifty = niftyDisplay.getNifty();
   //     guiViewPort.addProcessor(niftyDisplay);
  //  }

// initJoy();
}[/java]

The commented parts are just my attempt at getting nifty to work again. This method needs to be called every time you want to check for a change in input devices.

Anyway, probably not the best solution, but it does seem to work; provided you’re not using Nifty at all. I haven’t been able to test whether input maps properly, but a RawInputListener catches all the new controller’s movements, so I’d assume it would.

Well that’s what I’ve got; I’m still hoping to find a way around the Nifty issue, but even if I can’t, hopefully someone else can find some use from this.

Sorry for the Double Post, I just figure this should be in it’s own post rather than edited into the one above or something (given how many times I’ve edited it).

Alright, I have a solution, I believe:

Most of the stuff above is the same, up until using it via SimpleApplication. Rather than keeping a single Input Manager, I created a pair of them, one which, like normal, manages all Mouse and Key events, and another (which I named joyManager), which manages all Joystick events. The latter is a modified version of the original InputManager, with only a few tweaks.

ShowdownInputManager.java

[java]/*

  • Copyright © 2009-2012 jMonkeyEngine
  • All rights reserved.
  • Redistribution and use in source and binary forms, with or without
  • modification, are permitted provided that the following conditions are
  • met:
    • Redistributions of source code must retain the above copyright
  • notice, this list of conditions and the following disclaimer.
    • Redistributions in binary form must reproduce the above copyright
  • notice, this list of conditions and the following disclaimer in the
  • documentation and/or other materials provided with the distribution.
    • Neither the name of ‘jMonkeyEngine’ nor the names of its contributors
  • may be used to endorse or promote products derived from this software
  • without specific prior written permission.
  • THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  • “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  • TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  • PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  • CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  • EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  • PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  • PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  • LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  • NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  • SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    */
    package mygame;

import com.jme3.app.Application;
import com.jme3.cursors.plugins.JmeCursor;
import com.jme3.input.InputManager;
import com.jme3.input.JoyInput;
import com.jme3.input.Joystick;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.RawInputListener;
import com.jme3.input.TouchInput;
import com.jme3.input.controls.;
import com.jme3.input.event.
;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.util.IntMap;
import com.jme3.util.IntMap.Entry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**

  • The InputManager is responsible for converting input events

  • received from the Key, Mouse and Joy Input implementations into an

  • abstract, input device independent representation that user code can use.

  • <p>

  • By default an InputManager is included with every Application instance for use

  • in user code to query input, unless the Application is created as headless

  • or with input explicitly disabled.

  • <p>

  • The input manager has two concepts, a {@link Trigger} and a mapping.

  • A trigger represents a specific input trigger, such as a key button,

  • or a mouse axis. A mapping represents a link onto one or several triggers,

  • when the appropriate trigger is activated (e.g. a key is pressed), the

  • mapping will be invoked. Any listeners registered to receive an event

  • from the mapping will have an event raised.

  • <p>

  • There are two types of events that {@link InputListener input listeners}

  • can receive, one is {@link ActionListener#onAction(java.lang.String, boolean, float) action}

  • events and another is {@link AnalogListener#onAnalog(java.lang.String, float, float) analog}

  • events.

  • <p>

  • onAction events are raised when the specific input

  • activates or deactivates. For a digital input such as key press, the onAction()

  • event will be raised with the isPressed argument equal to true,

  • when the key is released, onAction is called again but this time

  • with the isPressed argument set to false.

  • For analog inputs, the onAction method will be called any time

  • the input is non-zero, however an exception to this is for joystick axis inputs,

  • which are only called when the input is above the {@link InputManager#setAxisDeadZone(float) dead zone}.

  • <p>

  • onAnalog events are raised every frame while the input is activated.

  • For digital inputs, every frame that the input is active will cause the

  • onAnalog method to be called, the argument value

  • argument will equal to the frame’s time per frame (TPF) value but only

  • for digital inputs. For analog inputs however, the value argument

  • will equal the actual analog value.
    */
    public class ShowdownInputManager extends InputManager implements RawInputListener {

    private static final Logger logger = Logger.getLogger(InputManager.class.getName());
    private final KeyInput keys;
    private final MouseInput mouse;
    private final JoyInput joystick;
    private final TouchInput touch;
    private float frameTPF;
    private long lastLastUpdateTime = 0;
    private long lastUpdateTime = 0;
    private long frameDelta = 0;
    private long firstTime = 0;
    private boolean eventsPermitted = false;
    private boolean mouseVisible = true;
    private boolean safeMode = false;
    private float axisDeadZone = 0.05f;
    private Vector2f cursorPos = new Vector2f();
    private Joystick[] joysticks;
    private final IntMap<ArrayList<Mapping>> bindings = new IntMap<ArrayList<Mapping>>();
    private final HashMap<String, Mapping> mappings = new HashMap<String, Mapping>();
    private final IntMap<Long> pressedButtons = new IntMap<Long>();
    private final IntMap<Float> axisValues = new IntMap<Float>();
    private ArrayList<RawInputListener> rawListeners = new ArrayList<RawInputListener>();
    private RawInputListener[] rawListenerArray = null;
    private ArrayList<InputEvent> inputQueue = new ArrayList<InputEvent>();

    private static class Mapping {

     private final String name;
     private final ArrayList&lt;Integer&gt; triggers = new ArrayList&lt;Integer&gt;();
     private final ArrayList&lt;InputListener&gt; listeners = new ArrayList&lt;InputListener&gt;();
    
     public Mapping(String name) {
         this.name = name;
     }
    

    }

    /**

    • Initializes the InputManager.

    • <p>This should only be called internally in {@link Application}.

    • @param mouse

    • @param keys

    • @param joystick

    • @param touch

    • @throws IllegalArgumentException If either mouseInput or keyInput are null.
      */
      public ShowdownInputManager(MouseInput mouse, KeyInput keys, JoyInput joystick, TouchInput touch) {
      super(mouse, keys, joystick, touch);
      if (keys == null || mouse == null) {
      throw new NullPointerException(“Mouse or keyboard cannot be null”);
      }

      this.keys = keys;
      this.mouse = mouse;
      this.joystick = joystick;
      this.touch = touch;

      keys.setInputListener(this);
      mouse.setInputListener(this);
      if (joystick != null) {
      joystick.setInputListener(this);
      joysticks = joystick.loadJoysticks(this);
      }
      if (touch != null) {
      touch.setInputListener(this);
      }

      firstTime = keys.getInputTimeNanos();
      }

    private void invokeActions(int hash, boolean pressed) {
    ArrayList<Mapping> maps = bindings.get(hash);
    if (maps == null) {
    return;
    }

     int size = maps.size();
     for (int i = size - 1; i &gt;= 0; i--) {
         Mapping mapping = maps.get(i);
         ArrayList&lt;InputListener&gt; listeners = mapping.listeners;
         int listenerSize = listeners.size();
         for (int j = listenerSize - 1; j &gt;= 0; j--) {
             InputListener listener = listeners.get(j);
             if (listener instanceof ActionListener) {
                 ((ActionListener) listener).onAction(mapping.name, pressed, frameTPF);
             }
         }
     }
    

    }

    private float computeAnalogValue(long timeDelta) {
    if (safeMode || frameDelta == 0) {
    return 1f;
    } else {
    return FastMath.clamp((float) timeDelta / (float) frameDelta, 0, 1);
    }
    }

    private void invokeTimedActions(int hash, long time, boolean pressed) {
    if (!bindings.containsKey(hash)) {
    return;
    }

     if (pressed) {
         pressedButtons.put(hash, time);
     } else {
         Long pressTimeObj = pressedButtons.remove(hash);
         if (pressTimeObj == null) {
             return; // under certain circumstances it can be null, ignore
         }                        // the event then.
    
         long pressTime = pressTimeObj;
         long lastUpdate = lastLastUpdateTime;
         long releaseTime = time;
         long timeDelta = releaseTime - Math.max(pressTime, lastUpdate);
    
         if (timeDelta &gt; 0) {
             invokeAnalogs(hash, computeAnalogValue(timeDelta), false);
         }
     }
    

    }

    private void invokeUpdateActions() {
    for (Entry<Long> pressedButton : pressedButtons) {
    int hash = pressedButton.getKey();

         long pressTime = pressedButton.getValue();
         long timeDelta = lastUpdateTime - Math.max(lastLastUpdateTime, pressTime);
    
         if (timeDelta &gt; 0) {
             invokeAnalogs(hash, computeAnalogValue(timeDelta), false);
         }
     }
    
     for (Entry&lt;Float&gt; axisValue : axisValues) {
         int hash = axisValue.getKey();
         float value = axisValue.getValue();
         invokeAnalogs(hash, value * frameTPF, true);
     }
    

    }

    private void invokeAnalogs(int hash, float value, boolean isAxis) {
    ArrayList<Mapping> maps = bindings.get(hash);
    if (maps == null) {
    return;
    }

     if (!isAxis) {
         value *= frameTPF;
     }
    
     int size = maps.size();
     for (int i = size - 1; i &gt;= 0; i--) {
         Mapping mapping = maps.get(i);
         ArrayList&lt;InputListener&gt; listeners = mapping.listeners;
         int listenerSize = listeners.size();
         for (int j = listenerSize - 1; j &gt;= 0; j--) {
             InputListener listener = listeners.get(j);
             if (listener instanceof AnalogListener) {
                 // NOTE: multiply by TPF for any button bindings
                 ((AnalogListener) listener).onAnalog(mapping.name, value, frameTPF);
             }
         }
     }
    

    }

    private void invokeAnalogsAndActions(int hash, float value, boolean applyTpf) {
    if (value < axisDeadZone) {
    invokeAnalogs(hash, value, !applyTpf);
    return;
    }

     ArrayList&lt;Mapping&gt; maps = bindings.get(hash);
     if (maps == null) {
         return;
     }
    
     boolean valueChanged = !axisValues.containsKey(hash);
     if (applyTpf) {
         value *= frameTPF;
     }
    
     int size = maps.size();
     for (int i = size - 1; i &gt;= 0; i--) {
         Mapping mapping = maps.get(i);
         ArrayList&lt;InputListener&gt; listeners = mapping.listeners;
         int listenerSize = listeners.size();
         for (int j = listenerSize - 1; j &gt;= 0; j--) {
             InputListener listener = listeners.get(j);
    
             if (listener instanceof ActionListener &amp;&amp; valueChanged) {
                 ((ActionListener) listener).onAction(mapping.name, true, frameTPF);
             }
    
             if (listener instanceof AnalogListener) {
                 ((AnalogListener) listener).onAnalog(mapping.name, value, frameTPF);
             }
    
         }
     }
    

    }

    /**

    • Callback from RawInputListener. Do not use.
      */
      public void beginInput() {
      }

    /**

    • Callback from RawInputListener. Do not use.
      */
      public void endInput() {
      }

    private void onJoyAxisEventQueued(JoyAxisEvent evt) {
    // for (int i = 0; i < rawListeners.size(); i++){
    // rawListeners.get(i).onJoyAxisEvent(evt);
    // }

     int joyId = evt.getJoyIndex();
     int axis = evt.getAxisIndex();
     float value = evt.getValue();
     if (value &lt; axisDeadZone &amp;&amp; value &gt; -axisDeadZone) {
         int hash1 = JoyAxisTrigger.joyAxisHash(joyId, axis, true);
         int hash2 = JoyAxisTrigger.joyAxisHash(joyId, axis, false);
    
         Float val1 = axisValues.get(hash1);
         Float val2 = axisValues.get(hash2);
    
         if (val1 != null &amp;&amp; val1.floatValue() &gt; axisDeadZone) {
             invokeActions(hash1, false);
         }
         if (val2 != null &amp;&amp; val2.floatValue() &gt; axisDeadZone) {
             invokeActions(hash2, false);
         }
    
         axisValues.remove(hash1);
         axisValues.remove(hash2);
    
     } else if (value &lt; 0) {
         int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, true);
         int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, false);
         
         // Clear the reverse direction's actions in case we
         // crossed center too quickly            
         Float otherVal = axisValues.get(otherHash);
         if (otherVal != null &amp;&amp; otherVal.floatValue() &gt; axisDeadZone) {
             invokeActions(otherHash, false);
         }
         
         invokeAnalogsAndActions(hash, -value, true);
         axisValues.put(hash, -value);
         axisValues.remove(otherHash);
     } else {
         int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, false);
         int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, true);
         
         // Clear the reverse direction's actions in case we
         // crossed center too quickly            
         Float otherVal = axisValues.get(otherHash);
         if (otherVal != null &amp;&amp; otherVal.floatValue() &gt; axisDeadZone) {
             invokeActions(otherHash, false);
         }
         
         invokeAnalogsAndActions(hash, value, true);
         axisValues.put(hash, value);
         axisValues.remove(otherHash);
     }
    

    }

    /**

    • Callback from RawInputListener. Do not use.
      */
      public void onJoyAxisEvent(JoyAxisEvent evt) {
      if (!eventsPermitted) {
      throw new UnsupportedOperationException(“JoyInput has raised an event at an illegal time.”);
      }

      inputQueue.add(evt);
      }

    private void onJoyButtonEventQueued(JoyButtonEvent evt) {
    // for (int i = 0; i < rawListeners.size(); i++){
    // rawListeners.get(i).onJoyButtonEvent(evt);
    // }

     int hash = JoyButtonTrigger.joyButtonHash(evt.getJoyIndex(), evt.getButtonIndex());
     invokeActions(hash, evt.isPressed());
     invokeTimedActions(hash, evt.getTime(), evt.isPressed());
    

    }

    /**

    • Callback from RawInputListener. Do not use.
      */
      public void onJoyButtonEvent(JoyButtonEvent evt) {
      if (!eventsPermitted) {
      throw new UnsupportedOperationException(“JoyInput has raised an event at an illegal time.”);
      }

      inputQueue.add(evt);
      }

    private void onMouseMotionEventQueued(MouseMotionEvent evt) {
    // for (int i = 0; i < rawListeners.size(); i++){
    // rawListeners.get(i).onMouseMotionEvent(evt);
    // }

     if (evt.getDX() != 0) {
         float val = Math.abs(evt.getDX()) / 1024f;
         invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_X, evt.getDX() &lt; 0), val, false);
     }
     if (evt.getDY() != 0) {
         float val = Math.abs(evt.getDY()) / 1024f;
         invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_Y, evt.getDY() &lt; 0), val, false);
     }
     if (evt.getDeltaWheel() != 0) {
         float val = Math.abs(evt.getDeltaWheel()) / 100f;
         invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_WHEEL, evt.getDeltaWheel() &lt; 0), val, false);
     }
    

    }

    /**

    • Sets the mouse cursor image or animation.
    • Set cursor to null to show default system cursor.
    • To hide the cursor completely, use {@link #setCursorVisible(boolean) }.
    • @param jmeCursor The cursor to set, or null to reset to system cursor.
    • @see JmeCursor
      */
      public void setMouseCursor(JmeCursor jmeCursor) {
      mouse.setNativeCursor(jmeCursor);
      }

    /**

    • Callback from RawInputListener. Do not use.
      */
      public void onMouseMotionEvent(MouseMotionEvent evt) {
      if (!eventsPermitted) {
      throw new UnsupportedOperationException(“MouseInput has raised an event at an illegal time.”);
      }

      cursorPos.set(evt.getX(), evt.getY());
      inputQueue.add(evt);
      }

    private void onMouseButtonEventQueued(MouseButtonEvent evt) {
    int hash = MouseButtonTrigger.mouseButtonHash(evt.getButtonIndex());
    invokeActions(hash, evt.isPressed());
    invokeTimedActions(hash, evt.getTime(), evt.isPressed());
    }

    /**

    • Callback from RawInputListener. Do not use.
      */
      public void onMouseButtonEvent(MouseButtonEvent evt) {
      if (!eventsPermitted) {
      throw new UnsupportedOperationException(“MouseInput has raised an event at an illegal time.”);
      }
      //updating cursor pos on click, so that non android touch events can properly update cursor position.
      cursorPos.set(evt.getX(), evt.getY());
      inputQueue.add(evt);
      }

    private void onKeyEventQueued(KeyInputEvent evt) {
    if (evt.isRepeating()) {
    return; // repeat events not used for bindings
    }

     int hash = KeyTrigger.keyHash(evt.getKeyCode());
     invokeActions(hash, evt.isPressed());
     invokeTimedActions(hash, evt.getTime(), evt.isPressed());
    

    }

    public void simulateEvent( InputEvent evt ) {
    inputQueue.add(evt);
    }

    /**

    • Callback from RawInputListener. Do not use.
      */
      public void onKeyEvent(KeyInputEvent evt) {
      if (!eventsPermitted) {
      throw new UnsupportedOperationException(“KeyInput has raised an event at an illegal time.”);
      }

      inputQueue.add(evt);
      }

    /**

    • Set the deadzone for joystick axes.
    • <p>{@link ActionListener#onAction(java.lang.String, boolean, float) }
    • events will only be raised if the joystick axis value is greater than
    • the deadZone.
    • @param deadZone the deadzone for joystick axes.
      */
      public void setAxisDeadZone(float deadZone) {
      this.axisDeadZone = deadZone;
      }

    /**

    • Returns the deadzone for joystick axes.
    • @return the deadzone for joystick axes.
      */
      public float getAxisDeadZone() {
      return axisDeadZone;
      }

    /**

    • Adds a new listener to receive events on the given mappings.
    • <p>The given InputListener will be registered to receive events
    • on the specified mapping names. When a mapping raises an event, the
    • listener will have its appropriate method invoked, either
    • {@link ActionListener#onAction(java.lang.String, boolean, float) }
    • or {@link AnalogListener#onAnalog(java.lang.String, float, float) }
    • depending on which interface the listener implements.
    • If the listener implements both interfaces, then it will receive the
    • appropriate event for each method.
    • @param listener The listener to register to receive input events.
    • @param mappingNames The mapping names which the listener will receive
    • events from.
    • @see InputManager#removeListener(com.jme3.input.controls.InputListener)
      */
      public void addListener(InputListener listener, String… mappingNames) {
      for (String mappingName : mappingNames) {
      Mapping mapping = mappings.get(mappingName);
      if (mapping == null) {
      mapping = new Mapping(mappingName);
      mappings.put(mappingName, mapping);
      }
      if (!mapping.listeners.contains(listener)) {
      mapping.listeners.add(listener);
      }
      }
      }

    /**

    • Removes a listener from receiving events.
    • <p>This will unregister the listener from any mappings that it
    • was previously registered with via
    • {@link InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[]) }.
    • @param listener The listener to unregister.
    • @see InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[])
      */
      public void removeListener(InputListener listener) {
      for (Mapping mapping : mappings.values()) {
      mapping.listeners.remove(listener);
      }
      }

    /**

    • Create a new mapping to the given triggers.

    • <p>

    • The given mapping will be assigned to the given triggers, when

    • any of the triggers given raise an event, the listeners

    • registered to the mappings will receive appropriate events.

    • @param mappingName The mapping name to assign.

    • @param triggers The triggers to which the mapping is to be registered.

    • @see InputManager#deleteMapping(java.lang.String)
      */
      public void addMapping(String mappingName, Trigger… triggers) {
      Mapping mapping = mappings.get(mappingName);
      if (mapping == null) {
      mapping = new Mapping(mappingName);
      mappings.put(mappingName, mapping);
      }

      for (Trigger trigger : triggers) {
      int hash = trigger.triggerHashCode();
      ArrayList<Mapping> names = bindings.get(hash);
      if (names == null) {
      names = new ArrayList<Mapping>();
      bindings.put(hash, names);
      }
      if (!names.contains(mapping)) {
      names.add(mapping);
      mapping.triggers.add(hash);
      } else {
      logger.log(Level.WARNING, “Attempted to add mapping “{0}” twice to trigger.”, mappingName);
      }
      }
      }

    /**

    • Returns true if this InputManager has a mapping registered
    • for the given mappingName.
    • @param mappingName The mapping name to check.
    • @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[])
    • @see InputManager#deleteMapping(java.lang.String)
      */
      public boolean hasMapping(String mappingName) {
      return mappings.containsKey(mappingName);
      }

    /**

    • Deletes a mapping from receiving trigger events.

    • <p>

    • The given mapping will no longer be assigned to receive trigger

    • events.

    • @param mappingName The mapping name to unregister.

    • @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[])
      */
      public void deleteMapping(String mappingName) {
      Mapping mapping = mappings.remove(mappingName);
      if (mapping == null) {
      throw new IllegalArgumentException("Cannot find mapping: " + mappingName);
      }

      ArrayList<Integer> triggers = mapping.triggers;
      for (int i = triggers.size() - 1; i >= 0; i–) {
      int hash = triggers.get(i);
      ArrayList<Mapping> maps = bindings.get(hash);
      maps.remove(mapping);
      }
      }

    /**

    • Deletes a specific trigger registered to a mapping.

    • <p>

    • The given mapping will no longer receive events raised by the

    • trigger.

    • @param mappingName The mapping name to cease receiving events from the

    • trigger.

    • @param trigger The trigger to no longer invoke events on the mapping.
      */
      public void deleteTrigger(String mappingName, Trigger trigger) {
      Mapping mapping = mappings.get(mappingName);
      if (mapping == null) {
      throw new IllegalArgumentException("Cannot find mapping: " + mappingName);
      }

      ArrayList<Mapping> maps = bindings.get(trigger.triggerHashCode());
      maps.remove(mapping);

    }

    /**

    • Clears all the input mappings from this InputManager.
    • Consequently, also clears all of the
    • InputListeners as well.
      */
      public void clearMappings() {
      mappings.clear();
      bindings.clear();
      reset();
      }

    /**

    • Do not use.
    • Called to reset pressed keys or buttons when focus is restored.
      */
      public void reset() {
      pressedButtons.clear();
      axisValues.clear();
      }

    /**

    • Returns whether the mouse cursor is visible or not.
    • <p>By default the cursor is visible.
    • @return whether the mouse cursor is visible or not.
    • @see InputManager#setCursorVisible(boolean)
      */
      public boolean isCursorVisible() {
      return mouseVisible;
      }

    /**

    • Set whether the mouse cursor should be visible or not.
    • @param visible whether the mouse cursor should be visible or not.
      */
      public void setCursorVisible(boolean visible) {
      if (mouseVisible != visible) {
      mouseVisible = visible;
      mouse.setCursorVisible(mouseVisible);
      }
      }

    /**

    • Returns the current cursor position. The position is relative to the
    • bottom-left of the screen and is in pixels.
    • @return the current cursor position
      */
      public Vector2f getCursorPosition() {
      return cursorPos;
      }

    /**

    • Returns an array of all joysticks installed on the system.
    • @return an array of all joysticks installed on the system.
      */
      public Joystick[] getJoysticks() {
      if (AdaptedControllerEnvironment.defaultEnvironment != null)
      {
      AdaptedControllerEnvironment.resetEnvironment();
      }
      return joystick.loadJoysticks(this);
      }

    /**

    • Adds a {@link RawInputListener} to receive raw input events.
    • <p>
    • Any raw input listeners registered to this InputManager
    • will receive raw input events first, before they get handled
    • by the InputManager itself. The listeners are
    • each processed in the order they were added, e.g. FIFO.
    • <p>
    • If a raw input listener has handled the event and does not wish
    • other listeners down the list to process the event, it may set the
    • {@link InputEvent#setConsumed() consumed flag} to indicate the
    • event was consumed and shouldn’t be processed any further.
    • The listener may do this either at each of the event callbacks
    • or at the {@link RawInputListener#endInput() } method.
    • @param listener A listener to receive raw input events.
    • @see RawInputListener
      */
      public void addRawInputListener(RawInputListener listener) {
      rawListeners.add(listener);
      rawListenerArray = null;
      }

    /**

    • Removes a {@link RawInputListener} so that it no longer
    • receives raw input events.
    • @param listener The listener to cease receiving raw input events.
    • @see InputManager#addRawInputListener(com.jme3.input.RawInputListener)
      */
      public void removeRawInputListener(RawInputListener listener) {
      rawListeners.remove(listener);
      rawListenerArray = null;
      }

    /**

    • Clears all {@link RawInputListener}s.
    • @see InputManager#addRawInputListener(com.jme3.input.RawInputListener)
      */
      public void clearRawInputListeners() {
      rawListeners.clear();
      rawListenerArray = null;
      }

    private RawInputListener[] getRawListenerArray() {
    if (rawListenerArray == null)
    rawListenerArray = rawListeners.toArray(new RawInputListener[rawListeners.size()]);
    return rawListenerArray;
    }

    /**

    • Enable simulation of mouse events. Used for touchscreen input only.
    • @param value True to enable simulation of mouse events
      /
      public void setSimulateMouse(boolean value) {
      if (touch != null) {
      touch.setSimulateMouse(value);
      }
      }
      /
      *
    • Returns state of simulation of mouse events. Used for touchscreen input only.

    */
    public boolean getSimulateMouse() {
    if (touch != null) {
    return touch.getSimulateMouse();
    } else {
    return false;
    }
    }

    /**

    • Enable simulation of keyboard events. Used for touchscreen input only.
    • @param value True to enable simulation of keyboard events
      */
      public void setSimulateKeyboard(boolean value) {
      if (touch != null) {
      touch.setSimulateKeyboard(value);
      }
      }

    private void processQueue() {
    int queueSize = inputQueue.size();
    RawInputListener[] array = getRawListenerArray();

     for (RawInputListener listener : array) {
         listener.beginInput();
    
         for (int j = 0; j &lt; queueSize; j++) {
             InputEvent event = inputQueue.get(j);
             if (event.isConsumed()) {
                 continue;
             }
    
             if (event instanceof MouseMotionEvent) {
                 listener.onMouseMotionEvent((MouseMotionEvent) event);
             } else if (event instanceof KeyInputEvent) {
                 listener.onKeyEvent((KeyInputEvent) event);
             } else if (event instanceof MouseButtonEvent) {
                 listener.onMouseButtonEvent((MouseButtonEvent) event);
             } else if (event instanceof JoyAxisEvent) {
                 listener.onJoyAxisEvent((JoyAxisEvent) event);
             } else if (event instanceof JoyButtonEvent) {
                 listener.onJoyButtonEvent((JoyButtonEvent) event);
             } else if (event instanceof TouchEvent) {
                 listener.onTouchEvent((TouchEvent) event);
             } else {
                 assert false;
             }
         }
    
         listener.endInput();
     }
    
     for (int i = 0; i &lt; queueSize; i++) {
         InputEvent event = inputQueue.get(i);
         if (event.isConsumed()) {
             continue;
         }
    
         if (event instanceof MouseMotionEvent) {
             onMouseMotionEventQueued((MouseMotionEvent) event);
         } else if (event instanceof KeyInputEvent) {
             onKeyEventQueued((KeyInputEvent) event);
         } else if (event instanceof MouseButtonEvent) {
             onMouseButtonEventQueued((MouseButtonEvent) event);
         } else if (event instanceof JoyAxisEvent) {
             onJoyAxisEventQueued((JoyAxisEvent) event);
         } else if (event instanceof JoyButtonEvent) {
             onJoyButtonEventQueued((JoyButtonEvent) event);
         } else if (event instanceof TouchEvent) {
             onTouchEventQueued((TouchEvent) event);
         } else {
             assert false;
         }
         // larynx, 2011.06.10 - flag event as reusable because
         // the android input uses a non-allocating ringbuffer which
         // needs to know when the event is not anymore in inputQueue
         // and therefor can be reused.
         event.setConsumed();
     }
    
     inputQueue.clear();
    

    }

    /**

    • Updates the InputManager.

    • This will query current input devices and send

    • appropriate events to registered listeners.

    • @param tpf Time per frame value.
      */
      public void update(float tpf) {
      frameTPF = tpf;

      // Activate safemode if the TPF value is so small
      // that rounding errors are inevitable
      safeMode = tpf < 0.015f;

      long currentTime = keys.getInputTimeNanos();
      frameDelta = currentTime - lastUpdateTime;

      eventsPermitted = true;

      keys.update();
      mouse.update();
      if (joystick != null) {
      joystick.update();
      }
      if (touch != null) {
      touch.update();
      }

      eventsPermitted = false;

      processQueue();
      invokeUpdateActions();

      lastLastUpdateTime = lastUpdateTime;
      lastUpdateTime = currentTime;
      }

    /**

    • Dispatches touch events to touch listeners

    • @param evt The touch event to be dispatched to all onTouch listeners
      */
      public void onTouchEventQueued(TouchEvent evt) {
      ArrayList<Mapping> maps = bindings.get(TouchTrigger.touchHash(evt.getKeyCode()));
      if (maps == null) {
      return;
      }

      int size = maps.size();
      for (int i = size - 1; i >= 0; i–) {
      Mapping mapping = maps.get(i);
      ArrayList<InputListener> listeners = mapping.listeners;
      int listenerSize = listeners.size();
      for (int j = listenerSize - 1; j >= 0; j–) {
      InputListener listener = listeners.get(j);
      if (listener instanceof TouchListener) {
      ((TouchListener) listener).onTouch(mapping.name, evt, frameTPF);
      }
      }
      }
      }

    /**

    • Callback from RawInputListener. Do not use.
      */
      @Override
      public void onTouchEvent(TouchEvent evt) {
      if (!eventsPermitted) {
      throw new UnsupportedOperationException(“TouchInput has raised an event at an illegal time.”);
      }
      inputQueue.add(evt);
      }
      }
      [/java]

The following is at the beginning of SimpleInitApp:
[java]
joyManager = new ShowdownInputManager(this.mouseInput, this.keyInput, new ShowdownJoyInput(), this.touchInput);
inputManager = new InputManager(this.mouseInput, this.keyInput, this.joyInput, this.touchInput);
defaultMapInputManager();

    compoundManager = new CompoundInputManager(inputManager, joyManager);
    [/java]

The reconstruction of the inputManager is because when you initialize ShowdownInputManager, it uses it’s parent’s constructor, which will set the MouseInput and KeyInput to have it as an InputListener (I tried commenting out the code on the child version, but it didn’t work, so it must have been using the parent).

The defaultMapInputManager() just maps the Esc keys, etc. back to how they would be if you weren’t doing crazy stuff:
[java]
public void defaultMapInputManager()
{
if (inputManager != null)
{

        if (context.getType() == Type.Display) {
            inputManager.addMapping(INPUT_MAPPING_EXIT, new KeyTrigger(KeyInput.KEY_ESCAPE));
        }

        if (stateManager.getState(StatsAppState.class) != null) {
            inputManager.addMapping(INPUT_MAPPING_HIDE_STATS, new KeyTrigger(KeyInput.KEY_F5));
            inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS);            
        }
        
        inputManager.addListener(actionListener, INPUT_MAPPING_EXIT);            
    }
}

[/java]

It would also be a good idea to create a getCompoundManager() method to the extension of SimpleApplication, so you can actually use both of them more easily.

Lastly, here is what is currently being used for CompoundInputManager:

[java]/*

  • To change this template, choose Tools | Templates
  • and open the template in the editor.
    */
    package mygame;

import com.jme3.cursors.plugins.JmeCursor;
import com.jme3.input.InputManager;
import com.jme3.input.Joystick;
import com.jme3.input.controls.InputListener;
import com.jme3.input.controls.JoyAxisTrigger;
import com.jme3.input.controls.JoyButtonTrigger;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.input.controls.Trigger;
import com.jme3.input.event.InputEvent;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import com.jme3.math.Vector2f;
import menu.KeyMapListener;

/**
*

  • @author Dell Notebook
    */
    public class CompoundInputManager
    {
    private InputManager inputManager;
    private InputManager joyManager;

    public CompoundInputManager(InputManager inputManager, InputManager joyManager)
    {
    this.inputManager = inputManager;
    this.joyManager = joyManager;
    }

    public InputManager getInputManager()
    {
    return inputManager;
    }

    public InputManager getJoyManager()
    {
    return joyManager;
    }

    public void addMapping(String mappingName, Trigger… triggers)
    {
    for (Trigger trigger : triggers)
    {
    if (trigger instanceof MouseButtonTrigger || trigger instanceof MouseAxisTrigger || trigger instanceof KeyTrigger)
    {
    inputManager.addMapping(mappingName, trigger);
    }
    else if (trigger instanceof JoyAxisTrigger || trigger instanceof JoyButtonTrigger)
    {
    joyManager.addMapping(mappingName, trigger);
    }
    }
    }

    public void addListener(InputListener listener, String… mappingNames)
    {
    inputManager.addListener(listener, mappingNames);
    joyManager.addListener(listener, mappingNames);
    }

    public void removeListener(InputListener listener)
    {
    inputManager.removeListener(listener);
    joyManager.removeListener(listener);
    }

    public Joystick[] getJoysticks()
    {
    return joyManager.getJoysticks();
    }

    public float getAxisDeadZone()
    {
    return joyManager.getAxisDeadZone();
    }

    public void setAxisDeadZone(float deadzone)
    {
    inputManager.setAxisDeadZone(deadzone);
    joyManager.setAxisDeadZone(deadzone);
    }

    public void addRawInputListener(KeyMapListener listener)
    {
    inputManager.addRawInputListener(listener);
    joyManager.addRawInputListener(listener);
    }

    public void removeRawInputListener(KeyMapListener listener)
    {
    inputManager.removeRawInputListener(listener);
    joyManager.removeRawInputListener(listener);
    }

    public void update(float tpf)
    {
    inputManager.update(tpf);
    joyManager.update(tpf);
    }

    public void clearMappings()
    {
    inputManager.clearMappings();
    joyManager.clearMappings();
    }

    public void clearRawInputListeners()
    {
    inputManager.clearRawInputListeners();
    joyManager.clearRawInputListeners();
    }

    public void deleteMapping(String mappingName)
    {
    if (inputManager.hasMapping(mappingName))
    {
    inputManager.deleteMapping(mappingName);
    }
    if (joyManager.hasMapping(mappingName))
    {
    joyManager.deleteMapping(mappingName);
    }
    }

    public void deleteTrigger(String mappingName, Trigger trigger)
    {
    if (trigger instanceof MouseButtonTrigger || trigger instanceof MouseAxisTrigger || trigger instanceof KeyTrigger)
    {
    inputManager.deleteTrigger(mappingName, trigger);
    }
    else if (trigger instanceof JoyAxisTrigger || trigger instanceof JoyButtonTrigger)
    {
    joyManager.deleteTrigger(mappingName, trigger);
    }
    }

    public boolean hasMapping(String mappingName)
    {
    if (inputManager.hasMapping(mappingName) || joyManager.hasMapping(mappingName))
    {
    return true;
    }
    return false;
    }

    public Vector2f getCursorPosition()
    {
    return inputManager.getCursorPosition();
    }

    public boolean getSimulateMouse()
    {
    return inputManager.getSimulateMouse();
    }

    public boolean isCursorVisible()
    {
    return inputManager.isCursorVisible();
    }

    public void reset()
    {
    inputManager.reset();
    joyManager.reset();
    }

    public void setCursorVisible(boolean visible)
    {
    inputManager.setCursorVisible(visible);
    joyManager.setCursorVisible(visible);
    }

    public void setMouseCursor(JmeCursor jmeCursor)
    {
    inputManager.setMouseCursor(jmeCursor);
    joyManager.setMouseCursor(jmeCursor);
    }

    public void setSimulateKeyboard(boolean value)
    {
    inputManager.setSimulateKeyboard(value);
    }

    public void setSimulateMouse(boolean value)
    {
    inputManager.setSimulateMouse(value);
    }

    public void beginInput()
    {
    inputManager.beginInput();
    joyManager.beginInput();
    }

    public void endInput()
    {
    inputManager.endInput();
    joyManager.endInput();
    }

    public void onJoyAxisEvent(JoyAxisEvent evt)
    {
    joyManager.onJoyAxisEvent(evt);
    }

    public void onJoyButtonEvent(JoyButtonEvent evt)
    {
    joyManager.onJoyButtonEvent(evt);
    }

    public void onKeyEvent(KeyInputEvent evt)
    {
    inputManager.onKeyEvent(evt);
    }

    public void onMouseButtonEvent(MouseButtonEvent evt)
    {
    inputManager.onMouseButtonEvent(evt);
    }

    public void onMouseMotionEvent(MouseMotionEvent evt)
    {
    inputManager.onMouseMotionEvent(evt);
    }

    public void onTouchEvent(TouchEvent evt)
    {
    inputManager.onTouchEvent(evt);
    }

    public void onTouchEventQueued(TouchEvent evt)
    {
    inputManager.onTouchEventQueued(evt);
    }

    public void simulateEvent(InputEvent evt)
    {
    if (evt instanceof MouseButtonEvent || evt instanceof MouseMotionEvent || evt instanceof KeyInputEvent)
    {
    inputManager.simulateEvent(evt);
    }
    else if (evt instanceof JoyAxisEvent || evt instanceof JoyButtonEvent)
    {
    joyManager.simulateEvent(evt);
    }
    }

}

[/java]

Also, you will need to set joyManager’s axis deadzone every time you add a new set of controllers (I haven’t tested this with more than one, but I assume it would work the same), at least in the version I have now. If you get the joysticks again, it is not a good idea to do so in the update loop, as the framerate drops like a rock (because it needs to verify old information over and over, and logs a bunch of stuff).

I don’t believe I’m missing anything major, but it’s possible. I have to note that this IS NOT recommended to be used, but if the user needs to have this functionality, it’s a solution, at least; albeit contrived and possibly full of error.

@pspeed: Of course, if you’re able to modify the source code, not all of these steps need to be done; particularly not the part where you create multiple InputManagers. Chances are that if JInputJoyInput is changed to be similar to ShowdownJoyInput above, and a few modifications are made to the InputManager, this could be done without impacting users. However, I’m not entirely sure of the dangers of rechecking for Controllers multiple times, based on endolf’s post here http://www.java-gaming.org/index.php?topic=23964.0 (this link was also posted in one of my other posts); I don’t know enough about the system to say, and I’m not sure how to remove device handles.

Regardless, I hope this helps someone else out; and I really hope I didn’t make a mistake somewhere so it won’t work later. I guess time will tell on that front.