Weird crash at multiplayer

Hi guys!

So, me and a friend decided to make a basic FPS game. So, I decided to learn how to code with spidermonkey, so I created a server. After 1 hour, I set up everyting (messages, listeners, seralization, etc). And it works. However, for only a few seconds. When only one person is connected, there’s no problems at all. When the other person connects, his cube (i’m using cubes for testing) moves and rotates as I expected. Then, I have sort of a render bug, getting a NullPointerException. I did some tests, and i figured out that enemy.getMaterial().getActiveTechnique().getDef().getForcedRenderState() method returns a null. And at the moment of crash, enemy.getMaterial().getActiveTechnique() now returns null (enemy is a Geometry).

This is very frustrating for me. I hope someone can help me.

The exception:

`Mai 06, 2015 3:20:42 PM com.jme3.app.Application handleError
SEVERE: Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]
java.lang.NullPointerException
at com.jme3.renderer.RenderManager.renderGeometry(RenderManager.java:502)
at com.jme3.renderer.queue.RenderQueue.renderGeometryList(RenderQueue.java:322)
at com.jme3.renderer.queue.RenderQueue.renderQueue(RenderQueue.java:374)
at com.jme3.renderer.RenderManager.renderViewPortQueues(RenderManager.java:763)
at com.jme3.post.filters.BloomFilter.postQueue(BloomFilter.java:213)
at com.jme3.post.FilterPostProcessor.postQueue(FilterPostProcessor.java:215)
at com.jme3.renderer.RenderManager.renderViewPort(RenderManager.java:979)
at com.jme3.renderer.RenderManager.render(RenderManager.java:1029)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:252)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:151)
at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:185)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:228)
at java.lang.Thread.run(Thread.java:744)``

Thanks,
Ev1lbl0w

PS: If you want the source code, ask me. I’m on the phone, and I can only go to pc afterwards.

Well you have to find out what accesses enemys’ Material.

I wouldn’t serialize it btw, only the pos/rot and load the Material locally like you would in singleplayer.

Hi, and thanks for your answer.

I commented the section of the message that changes the material, and it doesn’t crash. :wink: Now I know the problem is the material. Thank you!

So, I think I better paste the source code, now that I am on the pc (I know that this code is very bal structured. This is just a test)

Main.class (Client)

`public class Main extends SimpleApplication implements MessageListener{

private TimeOfDay tempo;
private Node jogadorNode;
private Node inimigoNode;
private AudioNode tiros;
private Client cliente;
private ColorRGBA corJogador;
private Geometry inimigo;
private Material matI;

private Vector3f cuboPos;
private Quaternion cuboDir;

private String nome;

private String MAPPING_DISPARAR = "Disparar";
private Trigger TRIGGER_DISPARAR = new MouseButtonTrigger(MouseInput.BUTTON_LEFT);

public static void main(String[] args) {
    Main app = new Main();
    app.start();
}

@Override
public void simpleInitApp() {
    try {
        cliente = Network.connectToServer("5.249.10.183", 25565);
        cliente.start();
    } catch(IOException e) {
        System.out.println("Não foi possível ligar ao servidor.");
        e.printStackTrace();
        System.exit(0);
    }
    
    cliente.addMessageListener(this, JogadorMensagem.class);
    
    Serializer.registerClass(JogadorMensagem.class);
    
    //inputManager.addMapping(MAPPING_DISPARAR, TRIGGER_DISPARAR);
    //inputManager.addListener(actionListener, MAPPING_DISPARAR);
    
    jogadorNode = new Node("Jogador");
    Box caixa = new Box(1, 1, 1);
    Geometry jogador = new Geometry("JogadorGeo", caixa);
    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    mat.setColor("Color", ColorRGBA.randomColor());
    jogador.setMaterial(mat);
    jogadorNode.attachChild(jogador);
    rootNode.attachChild(jogadorNode);
    
    inimigoNode = new Node("Inimigo");
    inimigo = new Geometry("InimigoGeo", caixa);
    matI = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    matI.setColor("Color", ColorRGBA.Yellow);
    inimigo.setMaterial(matI);
    inimigoNode.attachChild(inimigo);
    rootNode.attachChild(inimigoNode);
    
    corJogador = ColorRGBA.Black;
    
    Node gw_mountains = (Node) assetManager.loadModel("Scenes/gw_mountains.j3o");
    rootNode.attachChild(gw_mountains);
    flyCam.setMoveSpeed(30);
    viewPort.setBackgroundColor(ColorRGBA.Cyan);
    cam.setLocation(new Vector3f(0, 50, 0));
    
    DirectionalLight sol = new DirectionalLight();
    rootNode.addLight(sol);
    
    AmbientLight ambiente = new AmbientLight();
    rootNode.addLight(ambiente);
    
    SkyControl ceu = new SkyControl(assetManager, cam, .9f, true, true);
    rootNode.addControl(ceu);
    
    ceu.getSunAndStars().setHour(12);
    ceu.getSunAndStars().setObserverLatitude(37.4046f * FastMath.DEG_TO_RAD);
    ceu.getSunAndStars().setSolarLongitude(Calendar.FEBRUARY, 10);
    ceu.setCloudiness(1);
    
    ceu.setEnabled(true);
    
    ceu.setCloudYOffset(.4f);
    
    for(Light luz : rootNode.getLocalLightList()) {
        if(luz.getType().equals(Type.Ambient)) {
            ceu.getUpdater().setAmbientLight((AmbientLight)luz);
        } else if(luz.getType().equals(Type.Directional)) {
            ceu.getUpdater().setMainLight((DirectionalLight) luz);
        }
    }
    
    BloomFilter bloom = new BloomFilter(GlowMode.Objects);
    bloom.setBlurScale(2.5f);
    bloom.setExposurePower(1);
    Misc.getFpp(viewPort, assetManager).addFilter(bloom);
    ceu.getUpdater().addBloomFilter(bloom);
    
    tempo = new TimeOfDay(12);
    stateManager.attach(tempo);
    tempo.setRate(800);
    
    tiros = new AudioNode(assetManager, "Sounds/Bang.wav", false);
    tiros.setPositional(true);
    tiros.setLocalTranslation(jogadorNode.getLocalTranslation());
    tiros.setLooping(false);
    tiros.setVolume(50);
    
    setPauseOnLostFocus(false);
    cuboPos = new Vector3f(0, 0, 0);
    cuboDir = new Quaternion();
}

@Override
public void simpleUpdate(float tpf) {
    JogadorMensagem posicao = new JogadorMensagem(jogadorNode.getLocalTranslation(), jogadorNode.getLocalRotation(), corJogador);
    cliente.send(posicao);
    listener.setLocation(jogadorNode.getLocalTranslation());
    listener.setRotation(jogadorNode.getLocalRotation());
    jogadorNode.setLocalTranslation(cam.getLocation());
    jogadorNode.setLocalRotation(cam.getRotation());
    float hora = tempo.getHour();
    rootNode.getControl(SkyControl.class).getSunAndStars().setHour(hora);
    inimigo.setLocalTranslation(cuboPos);
    inimigo.setLocalRotation(cuboDir);
    matI.setColor("Color", corJogador);
    inimigo.setMaterial(matI);
}

@Override
public void simpleRender(RenderManager rm) {
    //TODO: add render code
}

/*private ActionListener actionListener = new ActionListener() {
    public void onAction(String nome, boolean pressionado, float tpf) {
        if(nome.equals(MAPPING_DISPARAR) && pressionado) {
            tiros.stop();
            tiros.play();
            CollisionResults resultados = new CollisionResults();
            Ray raio = new Ray(cam.getLocation(), cam.getDirection());
            rootNode.collideWith(raio, resultados);
            if(resultados.size() > 0) {
                if(resultados.getClosestCollision().getGeometry().getName().equals("JogadorGeo")) {
                    
                } else {
                    
                }
            }
        }
    }
};*/

@Override
public void messageReceived(Client fonte, Message mensagem) {
    if(mensagem instanceof JogadorMensagem) {
        JogadorMensagem posicao = (JogadorMensagem) mensagem;
        cuboPos = posicao.getLoc();
        cuboDir = posicao.getDir();
        corJogador = posicao.getCor();
    }
}

@Override
public void destroy() {
    cliente.close();
    super.destroy();
}

@Override
public void start() {
    nome = JOptionPane.showInputDialog(null, "Escreva o nome de utilizador para entrar.", "The War Games - Login", 1);
    while(nome.equals("")) {
        nome = JOptionPane.showInputDialog(null, "Escreva o nome de utilizador para entrar.", "The War Games - Login", 1);
    }
    super.start();
}

}`

ServidorMain.class (Server)

`public class ServidorMain extends SimpleApplication implements MessageListener {

private TimeOfDay tempo;
private Server servidor;

public static void main(String[] args) {
    ServidorMain app = new ServidorMain();
    app.start(JmeContext.Type.Headless);
}

@Override
public void simpleInitApp() {
    try {
        servidor = Network.createServer(25565);
        servidor.start();
    } catch(IOException e) {
        System.out.println("Não foi possível criar o servidor!");
        e.printStackTrace();
    }
    
    servidor.addMessageListener(this, JogadorMensagem.class);
    
    Serializer.registerClass(JogadorMensagem.class);
    
    Node gw_mountains = (Node) assetManager.loadModel("Scenes/gw_mountains.j3o");
    rootNode.attachChild(gw_mountains);
    
    tempo = new TimeOfDay(12);
    stateManager.attach(tempo);
    tempo.setRate(800);
}

@Override
public void simpleUpdate(float tpf) {
    //System.out.println(servidor.getConnections().size());
}

@Override
public void simpleRender(RenderManager rm) {
    //TODO: add render code
}

@Override
public void messageReceived(HostedConnection fonte, Message mensagem) {
    if(mensagem instanceof JogadorMensagem) {
        servidor.broadcast(Filters.notEqualTo(fonte), mensagem);
    }
}

@Override
public void destroy() {
    servidor.close();
    super.destroy();
}

}`

I hope someone can help me.

I didn’t find the Material part in your Code but disregarding that it does not transmit properly (possibly because its texture is missing or is not really implemented at all), you shouldn’t do that.

If you think of Performance and cost, it’s best to transmit as less Information as possible.

Every Player has the. J3o file and the corresponding Material so there is no need to stream/sync it.

If you have multiple characters only sync the string (AssetPath) and load it from the local asset loader.

So far you’d only Transfer the Position and the Rotation. That is 7 floats + Offset, 28 Byte per Tick and Player.

Imagine you would transmit a 10MiB texture file each frame…

Thanks! I’m syncing the materials because I am setting a random color for the cubes. And know I think I’m quite dumb :slight_smile: . Thank you so much for your help!