Removing spatial from scene graph

I have a mini game that is a first person shooter game. My problem is that when I select a spatial to shoot at with the mouse I get an error whenever I try to remove the spatial object and replace it with a particle effect(fire). Basically my aim is to simulate the effect of shooting a spatial object three times and then removing the spatial from the scene graph. Once the spatial is removed from the scene graph then attach a particle effect(fire) at the location at where the original spatial was located. Is there an efficient way of doing this task?

Secondly is it better to create a scene in a 3D editor(maya,lightwave, etc) versus scene composer?

My next problem is placing spatial object at a location inside scene using the scene composer. I am having a hard time exploring an entire scene inside scene composer editor because I find that up to a certain point I cannot go further into the scene inside scene composer editor.

Thanks

@zer0-g said: My problem is that when I select a spatial to shoot at with the mouse I get an error whenever I try to remove the spatial object and replace it with a particle effect(fire).

What error?

Inside the scene you have a virtual invisible cursor that the camera revolves around, you can only zoom in up to this cursor, zooming doesn’t move the camera forward, it only brings it closer to that cursor. By right-clicking and dragging you kove the cursor.

I get a NullPointException error. fixed the problem by using try except to catch the error. the try…except does nothing after it catches the error. appear it is deleting the spatial object twice but I am still not sure. However I can say that the spatial is attached to a Control object(AbstractControl).

You should not try/catch a NullPointerException. But we cannot help with what we cannot see.

If you do not have the experience to figure out why you have a null pointer then you will have to post some code (and please note which line number the nullpointerexception is indicating and include the stack trace). Otherwise, we are just playing a game of “How long is this piece of string I’m holding?”

moving the cursor by right clicking and dragging the cursor does affect me zooming in and out of a scene using the scene composer. right clicking only moves the scene cursor in the scene composer editor.

Hey I think I got it to work. however I left click instead of right click and zoomed into the new cursor marker.
The left click and drag is rotation. The right click and drag pan the view.

thanks

///GameRunningState.java

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

package mygame.state.state1;

import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.AssetManager;
import com.jme3.asset.plugins.HttpZipLocator;
import com.jme3.asset.plugins.ZipLocator;
import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioRenderer;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.effect.ParticleEmitter;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.input.FlyByCamera;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.Light;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.niftygui.NiftyJmeDisplay;
import com.jme3.renderer.Camera;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.SceneGraphVisitorAdapter;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.CameraControl;
import com.jme3.scene.shape.Sphere;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.elements.Element;
import de.lessvoid.nifty.elements.render.TextRenderer;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
//import mygame.control.BallControl;
import mygame.control.BullEyeControl;

/**
 *
 * @author Nugent
 */

public class GameRunningState extends AbstractAppState implements ScreenController{
    
    private AudioNode shotAudio,
     //                 //flameAudio;//,
                       backAudio;
    
    private int score =1;
    private Nifty nifty;
    private Screen screen;
    private BetterCharacterControl playerPhy;
    private Spatial scene;
    private Node  rootNode,
                  guiNode,
                  hud,
                  playerNode,
                  worldNode,
                  immobile,
                  lightNode,
                  sceneNode,
                  FireEffectNode;
           
    private Camera cam;
    private FlyByCamera flyCam;
    private SimpleApplication app;
    private ViewPort viewPort;
    private BulletAppState bulletAppState;
    private AppStateManager stateManager;
    private InputManager inputManager;
    private AssetManager assetManager; 
    private AudioRenderer audioRenderer;
    private ViewPort guiViewPort;
    
    public boolean rot_left=false,
                   rot_right=false,
                   forward=false,
                   back=false;
    
    private final static String 
                   FWD_MAPPING = "FORWARD",
                   BACK_MAPPING = "BACK",
                   ROT_LEFT_MAPPING = "ROTATE LEFT",
                   ROT_RIGHT_MAPPING = "ROTATE RIGHT",
                   JUMP_MAPPING = "JUMP";
    
    private final static KeyTrigger 
                       fwd_trigger = new KeyTrigger(KeyInput.KEY_W),
                       back_trigger = new KeyTrigger(KeyInput.KEY_S),
                       rot_left_trigger = new KeyTrigger(KeyInput.KEY_A),
                       rot_right_trigger = new KeyTrigger(KeyInput.KEY_D),
                       jump_trigger = new KeyTrigger(KeyInput.KEY_SPACE);
    
    private final static String SHOOT_MAPPING = "SHOOT";
    private final static MouseButtonTrigger Shoot_Trigger = 
            new MouseButtonTrigger(MouseInput.BUTTON_LEFT);
    
    private Vector3f walkDirection = new Vector3f(0,0,0);
    private Vector3f viewDirection = new Vector3f(0,0,1f);
    
    private CameraNode camNode;
    
    private float speed = 15f;
    private int targetsNum;
    
    private ActionListener actionListener = new ActionListener() {

        public void onAction(String str, boolean isPressed, float tpf) {
            
            if(str.equals(SHOOT_MAPPING) && !isPressed){
                shotAudio.playInstance();
                CollisionResults results = new CollisionResults();
                Ray ray = new Ray();
                Vector2f click2f = inputManager.getCursorPosition();
                Vector3f click3f = cam.getWorldCoordinates(new Vector2f(click2f.x,click2f.y), 0).clone();
                Vector3f tmp = cam.getWorldCoordinates(new Vector2f(click2f.x, click2f.y), 0.00006f);
                Vector3f dir = tmp.subtractLocal(click3f).normalize();
                
                
                ray.setDirection(dir);
                ray.setOrigin(click3f);
                
                immobile.collideWith(ray, results);
                
                if(results.size()>0){
                    
                   
                    CollisionResult result = results.getClosestCollision(); 
                    //try{
                        
                    Spatial tmp_spatialbull = (Spatial)result.getGeometry();
                    
                    
                GameRunningState tmp_gamerunningstate = stateManager.getState(GameRunningState.class);
                    
                BullEyeControl tmp_control = tmp_gamerunningstate.immobile.getChild(tmp_spatialbull.getParent().getName()).getControl(BullEyeControl.class);
               
                tmp_control.countHits();
                             
                ///test if hit more than 1
                   if(tmp_control.getHits()< 3){
                  Spatial tmp_spatialball = makePaintBall();
                       
                       
                  tmp_spatialball.setLocalTranslation(result.getContactPoint());
                       
                  int count = tmp_control.getHits()-1;
                  tmp_control.addBall(count,tmp_spatialball.getName());
                  immobile.attachChild(tmp_spatialball);
                  
                  
                  int temp_hits = immobile.getUserData("Hits");
                  //int temp_points = immobile.getUserData("Points");
                  
                  immobile.setUserData("Hits", temp_hits+tmp_control.getHits());
                  immobile.setUserData("Points", temp_hits*2 );
                  
                  Element txtPoint = screen.findElementByName("txt-point");
                  TextRenderer txtPointRender = txtPoint.getRenderer(TextRenderer.class);
                  int temp_value = immobile.getUserData("Hits")  ;
                  temp_value*=2;
                  
                  txtPointRender.setText(""+temp_value);
                    //System.out.println(""+count);
                   }
                   else{ 
                    
                    targetsNum-=1;
                    FireEffectNode = getFireEffect();
                    FireEffectNode.setLocalTranslation(result.getGeometry().getWorldTranslation());
                    immobile.attachChild(FireEffectNode);
                    //immobile.attachChild(flameAudio);
                    //flameAudio.play();
                    tmp_control.cleanup();
                    tmp_gamerunningstate = null;
                    tmp_control = null;
                    tmp_spatialbull= null;
                    result.getGeometry().removeFromParent();
                }//}catch(NullPointerException ex){result=null;return;}
                     
                    }else System.out.println("No Mesh");
            }
        
            ////player node
        if(str.equals(FWD_MAPPING)){
            forward = isPressed;
        }else if(str.equals(BACK_MAPPING)){
            back = isPressed;
        }else if(str.equals(ROT_LEFT_MAPPING)){
            rot_left = isPressed;
        }else if(str.equals(ROT_RIGHT_MAPPING)){
            rot_right =  isPressed;
        }else if(str.equals(JUMP_MAPPING)){
            playerPhy.jump();
        }
        }
    };
    
    @Override
    public void update(float tpf){
            
        Vector3f v = viewPort.getCamera().getLocation();
        viewPort.setBackgroundColor(new ColorRGBA(v.getX()/10, v.getY()/10, v.getZ()/10, 1.0f));
         //playerNode.getChild("Box").rotate(tpf, tpf, tpf);
        ////simpleUpdate code here
     
        ///player node camera
    camNode = new CameraNode("CamNode", cam);
    camNode.setControlDir(CameraControl.ControlDirection.SpatialToCamera);
    camNode.setLocalTranslation(0, 4f, -6f);
    Quaternion quat = new Quaternion();
    quat.lookAt(Vector3f.UNIT_Z, Vector3f.UNIT_Y);
    camNode.setLocalRotation(quat);
    
    playerNode.attachChild(camNode);
    camNode.setEnabled(true);
    flyCam.setEnabled(false);
    
    ////physics-aware calcuation
    Vector3f modelForwardDir = new Vector3f(playerNode.getWorldRotation().mult(Vector3f.UNIT_Z));
    //Vector3f modelLeftDir = new Vector3f(playerNode.getWorldRotation().mult(Vector3f.UNIT_X));
    walkDirection.set(Vector3f.ZERO);
    
    
    if(forward){
        walkDirection.addLocal(modelForwardDir.mult(speed));
        
    }else if(back){
        walkDirection.addLocal(modelForwardDir.mult(speed).negate());
    }
    playerPhy.setWalkDirection(walkDirection);
    if(rot_left){
        Quaternion rotateL = new Quaternion().fromAngleAxis(FastMath.PI*tpf, Vector3f.UNIT_Y);
        rotateL.multLocal(viewDirection); 
        playerPhy.setViewDirection(viewDirection);
    }else if(rot_right){
        Quaternion rotateR = new Quaternion().fromAngleAxis(-FastMath.PI*tpf, Vector3f.UNIT_Y);
        rotateR.multLocal(viewDirection);
        playerPhy.setViewDirection(viewDirection);
    }
    playerPhy.setViewDirection(viewDirection);
    
    
    if(targetsNum==0){
        ///go to next level
        immobile.detachAllChildren();
        System.out.println("targetsNum:"+targetsNum);
        reInsertModel();
    }
    }
    
    @Override
    public void cleanup(){
        guiNode.detachChild(hud);
        rootNode.detachChild(worldNode);
        rootNode.detachAllChildren();
        System.out.println("Detaching GameRunningState");
        ///cleanup code here e.g. detach Node object 
    }
    
    @Override
    public void initialize(AppStateManager appStateMan,Application app){
    ///initialize method setup the initial models and more when the 
    ///game startups for the first time

    super.initialize(appStateMan, app);
    this.app = (SimpleApplication) app;
    this.cam = this.app.getCamera();
    this.flyCam = this.app.getFlyByCamera();
    this.rootNode = this.app.getRootNode();
    this.guiNode = this.app.getGuiNode();
    this.assetManager = this.app.getAssetManager();
    this.viewPort = this.app.getViewPort();
    this.guiViewPort = this.app.getGuiViewPort();
    this.audioRenderer = this.app.getAudioRenderer();
    this.stateManager = this.app.getStateManager();
    this.inputManager = this.app.getInputManager();
    this.bulletAppState = new BulletAppState();
    this.shotAudio = new AudioNode(assetManager, "Sounds/gun shot/Gun.ogg");
    //this.flameAudio = new AudioNode(assetManager, "Sounds/fireplace/fireplace.ogg",true);
    //this.flameAudio.setPositional(false);
    //this.flameAudio.setLooping(true);
    
    this.backAudio = new AudioNode(assetManager, "Sounds/background/video-game-7.wav");
    this.backAudio.setPositional(false);
    this.backAudio.setLooping(true);
    
    ////Startup or simpleInit() code here
    this.stateManager.attach(bulletAppState);
    initKeys();
    initNifty();
    ///setting up model or scene
    
    playerNode = new Node("Player");
    
    sceneNode = new Node("Scene");
    sceneNode.setLocalTranslation(Vector3f.ZERO);
    scene = makeLevel(0);//makeScene();
    sceneNode.attachChild(scene);
    
    immobile = new Node("Immoblie Player");
    
    
            
    //immobile.attachChild(flameAudio);
   // immobile.attachChild(shotAudio);
    immobile.setUserData("Hits", 0);
    immobile.setUserData("Points", 0);
    //Node tmp_node[] = new Node[10];
    for(int i =0;i<2;i++){
    Spatial tmp_spatial = makeBullTarget();
    tmp_spatial.setLocalTranslation((float)FastMath.nextRandomInt(-15, 15),
            tmp_spatial.getLocalTranslation().y,
            tmp_spatial.getLocalTranslation().z );
    //tmp_node[i] = (Node)tmp_spatial;
    immobile.attachChild(tmp_spatial);
    
    }
    targetsNum = immobile.getChildren().size();
    
    //////lighting source
    lightNode = new Node("All Lights");
    //lightNode.addLight(new AmbientLight());
        
    
    ///physics
    {
        //scene physics
    RigidBodyControl tmp_phy = new RigidBodyControl(0f);
    scene.addControl(tmp_phy);
    bulletAppState.getPhysicsSpace().add(tmp_phy);
    }
    
    for(Spatial tmp_spatial : immobile.getChildren()){
        //bull eye target physics
       RigidBodyControl tmp_phy = new RigidBodyControl(15f);
       tmp_spatial.addControl(tmp_phy);
       bulletAppState.getPhysicsSpace().add(tmp_phy);
       
   }
    ///player control
    playerPhy = new BetterCharacterControl (1.5f,3f,30f);
    playerPhy.setJumpForce(new Vector3f(0, 200f, 0));
    playerPhy.setGravity(new Vector3f(0, -10f, 0));
    playerNode.addControl(playerPhy);
    bulletAppState.getPhysicsSpace().add(playerPhy);
    
    ////setup game world node
    worldNode = new Node("World");
    worldNode.attachChild(playerNode);
    worldNode.attachChild(immobile);
    worldNode.attachChild(sceneNode);
    worldNode.attachChild(lightNode);
    
    hud = new Node("Hud");
    hud.attachChild((BitmapText)GetBitmapText());
    
    
    
    System.out.println("attaching GameRunningState");
    }
    
    @Override
    public void setEnabled(boolean enabled){
        super.setEnabled(enabled);
        
        if(enabled){
            guiNode.detachAllChildren();
            rootNode.detachAllChildren();
            //guiNode.attachChild(hud);
            rootNode.attachChild(worldNode);
            
           // immobile.attachChild(backAudio);
            backAudio.play();
            
            for (Light lt : lightNode.getLocalLightList())
                rootNode.addLight(lt);
            nifty.gotoScreen("ingame");
            ///unpause do this
            System.out.println("Unpausing GameRunningState");
        }else{
            backAudio.stop();
            nifty.gotoScreen("start");
            //removeKeys();
            //guiNode.detachChild(hud);
            //rootNode.detachChild(playerNode);
            ///pause equal true
            System.out.println("Pausing GameRunningState");
        }
        
    }
       
    public Object GetBitmapText(){
        BitmapFont guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        BitmapText displayTxt = new BitmapText(guiFont);
        displayTxt.setSize(guiFont.getCharSet().getRenderedSize());
        displayTxt.move(10f, displayTxt.getLineHeight()+(20*30), 0);
        displayTxt.setText("Game Running press  BACKSPACE to pause and return to Start");
        
        return displayTxt;
    }
    
    public Object getLevel(int i){
        
        switch(i){
           case 1:
                return (Node) assetManager.loadModel("Models/BullsEyeTarget.mesh.j3o");
           default :    
                return (Node) assetManager.loadModel("Models/BullsEyeTarget.mesh.j3o");
        }
    }
    
    public Spatial makeScene(){
    Spatial tmp_Scene = (Spatial)modelHelperLoc("town.zip", "main.scene", 0);
    tmp_Scene.setLocalTranslation(Vector3f.ZERO);
    tmp_Scene.addLight(new AmbientLight());
    return tmp_Scene;
    }
    ///model helper locator
    public Object modelHelperLoc(String strLoc,String strModelName, int locKey){
        
        switch(locKey){
            case 1:
                assetManager.registerLocator(strLoc, HttpZipLocator.class);
                return (Spatial) assetManager.loadModel(strModelName);
            
            case 0:
            default:
                assetManager.registerLocator(strLoc, ZipLocator.class);
                return (Spatial) assetManager.loadModel(strModelName);
                
        }
        
    }
    
    public void initKeys(){
        //flyCam.setDragToRotate(true);
        //flyCam.setMoveSpeed(100f);
        inputManager.addMapping(SHOOT_MAPPING, Shoot_Trigger);
        inputManager.addListener(actionListener, SHOOT_MAPPING);
        
        inputManager.addMapping(FWD_MAPPING, fwd_trigger);
        inputManager.addMapping(BACK_MAPPING, back_trigger);
        inputManager.addMapping(ROT_LEFT_MAPPING, rot_left_trigger);
        inputManager.addMapping(ROT_RIGHT_MAPPING, rot_right_trigger);
        inputManager.addMapping(JUMP_MAPPING, jump_trigger);
  
        inputManager.addListener(actionListener,new String[]{FWD_MAPPING,BACK_MAPPING,ROT_LEFT_MAPPING,ROT_RIGHT_MAPPING,JUMP_MAPPING} );
  
    }
    
    public void removeKeys(){
        inputManager.removeListener(actionListener);
        
        inputManager.deleteTrigger(SHOOT_MAPPING, Shoot_Trigger);
        inputManager.deleteMapping(SHOOT_MAPPING);
    }
    
    public Geometry makePaintBall(){
        Sphere mesh = new Sphere(32,32,0.15f);
        Geometry meshGeo = new Geometry("Ball", mesh);
        //meshGeo.addControl(new BallControl(rootNode, cam));
        
        Material meshMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        meshMat.setColor("Color", ColorRGBA.randomColor());
        
        meshGeo.setName("Ball"+meshGeo.hashCode());
        meshGeo.setMaterial(meshMat);
        return meshGeo;
    }
    
    public void reInsertModel(){
        //Node tmp_node[] = new Node[10];
    for(int i =0;i<2;i++){
    Spatial tmp_spatial = makeBullTarget();
    tmp_spatial.setLocalTranslation((float)FastMath.nextRandomInt(-15, 15),
            tmp_spatial.getLocalTranslation().y,
            tmp_spatial.getLocalTranslation().z );
        immobile.attachChild(tmp_spatial);
    
    }
    
    targetsNum = immobile.getChildren().size();
    
    for(Spatial tmp_spatial : immobile.getChildren()){
        //bull eye target physics
       RigidBodyControl tmp_phy = new RigidBodyControl(15f);
       tmp_spatial.addControl(tmp_phy);
       bulletAppState.getPhysicsSpace().add(tmp_phy);
       
   }
        
    }
    
    public Spatial makeBullTarget(){
        Spatial bullTarget = assetManager.loadModel("Models/BullsEyeTarget.j3o");
        bullTarget.scale(0.35f);
        bullTarget.setName("BullEyeMesh"+bullTarget.hashCode());
        bullTarget.setLocalTranslation(new Vector3f(scene.getLocalTranslation().x,scene.getLocalTranslation().y+.2f,scene.getLocalTranslation().z));
        bullTarget.addControl(new BullEyeControl(rootNode, cam));
        bullTarget.addLight(new AmbientLight());
        return bullTarget;
    }
    
    public Node getFireEffect(){
   
    SceneGraphVisitorAdapter myEmitterVisitor = new SceneGraphVisitorAdapter(){
    
    public void visit(Geometry geom){
        super.visit(geom);
        getEmitter(geom);
        
    }
    
    public void visit(Node node){
        super.visit(node);
        getEmitter(node);
    }
    
    public void getEmitter(Spatial spatial){
    
        if(spatial instanceof ParticleEmitter && spatial.getName().equals("FireEffect")){
        System.out.println("Emitter in " + spatial.getName());
        ///modify the found node
        ((ParticleEmitter) spatial).setEnabled(true);
        
    } 
        }
            };
    
    Node tmp = (Node) assetManager.loadModel("Models/FireEffect.j3o");
    tmp.depthFirstTraversal(myEmitterVisitor);
    tmp.setLocalScale(0.05f);
    
    return tmp;
    }

    public void initNifty(){
   NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,
                                                          inputManager,
                                                          audioRenderer,
                                                          guiViewPort);
        nifty = niftyDisplay.getNifty();
        nifty.fromXml("Interface/nifty/start.xml", "start", this);

        // attach the nifty display to the gui view port as a processor
        guiViewPort.addProcessor(niftyDisplay);

        // disable the fly cam
        flyCam.setEnabled(false);
        flyCam.setDragToRotate(true);
        inputManager.setCursorVisible(true);
}
    public void bind(Nifty nifty, Screen screen) {
        this.nifty = nifty;
        this.screen = screen;
    }

    public void onStartScreen() {}

    public void onEndScreen() {}
    
    public void startNewGame(){
        
        GameRunningState tmp_gamerunningstate = stateManager.getState(GameRunningState.class);
        tmp_gamerunningstate.initialized=false;
        tmp_gamerunningstate.setEnabled(true);
    }
    
    public void tutorialScreen(){
        nifty.gotoScreen("tutorial");
    }
    
    public void aboutScreen(){
        nifty.gotoScreen("about");
    }
    
    public void homeScreen(){
        nifty.gotoScreen("start");
    }
    
    public void quitGame(){
        this.app.stop();
    }
    
    public String getTutorialDoc(){
        return "W key - To Move forward\n"+
                "S key - To Move backward\n"+
                "A key - To Turn left\n"+
                "D key - To Turn right\n"+
                "Left Mouse Click - To Fire at Bulls Eye Target";
    }
    
    public String getAboutDoc(){
        return "game objective:\n"+
                "shot all the bulls eye targets in the game scene.\n"+
                "The Bulls eye targets are identified by an object with colored\n"+
                "with a red dot in the center.";
    }
    
    public Spatial makeLevel(int level){
      Spatial tmp_Scene;
      
        switch(level){
            ///case 1:
            //return model
            
            
                case 0:
                default:
                tmp_Scene = assetManager.loadModel("Scenes/town/main.j3o");
                tmp_Scene.setLocalTranslation(Vector3f.ZERO);
                tmp_Scene.addLight(new AmbientLight());
                return tmp_Scene;
        }
    }
}

Stack Trace
GameRunningState.java
Line 158 “BullEyeControl tmp_control = tmp_gamerunningstate.immobile.getChild(tmp_spatialbull.getParent().getName()).getControl(BullEyeControl.class);”

SEVERE: Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]
java.lang.NullPointerException
at mygame.state.state1.GameRunningState$1.onAction(GameRunningState.java:158)

One of these things is null:

Some basic debugging will tell you which (either printlns or stepping through in the debugger).

That’s one big problem with monster lines like that. In addition to being horribly ugly and wrapping off the edge of the screen, any exceptions get lost in the alphabet soup. Furthermore, it makes it harder to debug.

Step 1: split the line up.

Step 2: find out what’s null that’s not supposed to be.