package net.cis.client.game.ctrl.ai;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import net.cis.client.game.common.ctrl.IDispatcher;
import net.cis.client.game.common.ctrl.IGameApplication;
import net.cis.client.game.common.ctrl.IObjectController;
import net.cis.client.game.common.ctrl.TimedCallbackController;
import net.cis.client.game.common.engine.SimpleBoundingVolumeFactory;
import net.cis.client.game.common.model.ai.impl.simple.SimpleRotator;
import net.cis.client.game.common.model.ai.impl.simple.SimpleWobble;
import net.cis.client.game.common.model.co.AbstractControlledObject;
import net.cis.client.game.common.model.co.ControlledSpaceObject;
import net.cis.client.game.common.model.event.AbstractEventListener;
import net.cis.client.game.common.model.event.ObjectEvent;
import net.cis.client.game.common.threading.ICallback;
import net.cis.client.game.common.util.GlobalObjectStore;
import net.cis.client.game.ctrl.threading.AbstractInterruptableRunnable;
import net.cis.client.game.scenery.ctrl.SpatialUpdater;
import net.cis.common.model.spaceobject.SpaceObject;
import net.cis.common.model.spaceobject.SpaceobjectFactory;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Line;
import com.jme3.scene.shape.Sphere;
/**
*
* @author Omega
*
*/
public class AIController extends AbstractInterruptableRunnable
{
protected final IGameApplication gameApplication;
protected boolean initialized = false;
protected TimedCallbackController tcc = new TimedCallbackController();
protected ConcurrentHashMap<Integer, AbstractControlledObject<?,?>> smartObjects = new ConcurrentHashMap<Integer, AbstractControlledObject<?,?>>();
public AIController(IGameApplication gameApp)
{
this.gameApplication = gameApp;
}
/**
* Loads the AI structure from X to memory
*/
protected boolean loadAIDefinition()
{
return true;
}
/**
* Fetches the appropriate AI for the {@link AbstractControlledObject} and
* attaches it, after detaching any existing AI.
*
*/
protected boolean attachAI(AbstractControlledObject<?, ?> obj)
{
log.trace("Attaching to " + obj);
if (obj.getAINode() != null && !detachAI(obj))
{
log.error("Failed to detach AI from " + obj);
return false;
}
if (obj.dataObject == null)
{
log.error("No data object set for " + obj);
return false;
}
int objID = obj.dataObject.getId();
//TODO: switch to external data defined
if(objID == 2) //2 == Asteroid
obj.setAINode(new SimpleRotator());
else if(objID >= 100000) //debug objects
obj.setAINode(new SimpleWobble());
if(obj.getAINode() != null)
smartObjects.put(objID, obj);
return obj.getAINode() != null;
}
/**
* Detaches the AI from an {@link AbstractControlledObject}, making it
* non-interactive.
*/
protected boolean detachAI(AbstractControlledObject<?, ?> obj)
{
log.trace("Detaching from " + obj);
obj.setAINode(null);
if (obj.dataObject == null)
{
log.error("No data object set for " + obj);
return false;
}
smartObjects.remove(obj.dataObject.getId());
return true;
}
@Override
protected int getSleepTime()
{
// This will most likely be dynamic depending on actual workload
return 100;
}
@Override
protected void doRun(float timeSinceLastRun)
{
tcc.update(timeSinceLastRun);
for (AbstractControlledObject<?,?> co : smartObjects.values())
{
co.getAINode().Execute(co, null, timeSinceLastRun);
}
}
@Override
protected boolean initialize()
{
// just be careful
if (initialized) log.warn("Already initialized");
// load entire ai to memory
if (!loadAIDefinition())
{
log.error("Failed to load AI definitions");
initialized = false;
return initialized;
}
log.info("AI definitions loaded");
// attach ai to existing objects
int preExistingObjectCount = 0;
int objectsAlreadyWithAI = 0;
Iterator<AbstractControlledObject<?, ?>> ite = GlobalObjectStore.<IObjectController> getObject(IObjectController.class).getAllObjects(null);
while (ite.hasNext())
{
++preExistingObjectCount;
AbstractControlledObject<?, ?> obj = ite.next();
if (obj.getAINode() != null)
{
++objectsAlreadyWithAI;
continue;
}
if (!attachAI(obj)) log.error("Failed to attach AI to " + obj);
}
log.info("Found " + preExistingObjectCount + " objects; " + objectsAlreadyWithAI + " objects already had AI");
// attach object listeners for future objects
GlobalObjectStore.<IDispatcher> getObject(IDispatcher.class).addCallee(new AbstractEventListener<ObjectEvent>("ObjectAdded")
{
@Override
public void onEvent(ObjectEvent event)
{
if (!attachAI(event.value)) log.error("Attaching AI failed: " + event.value);
}
});
GlobalObjectStore.<IDispatcher> getObject(IDispatcher.class).addCallee(new AbstractEventListener<ObjectEvent>("ObjectDestroyed")
{
@Override
public void onEvent(ObjectEvent event)
{
if (!detachAI(event.value)) log.error("Detaching AI failed: " + event.value);
}
});
initDebugObjects();
initialized = true;
log.info("Initialisation complete");
return initialized;
}
protected static int nextID = 100000;
protected void initDebugObjects()
{
log.trace("Population scene with debug objects");
//friend
final Material friendMat = new Material(GlobalObjectStore.<AssetManager>getObject(AssetManager.class), "Common/MatDefs/Misc/Unshaded.j3md");
friendMat.setColor("Color", new ColorRGBA(0, .5f, 0, 1));
final Material friendMat2 = new Material(GlobalObjectStore.<AssetManager>getObject(AssetManager.class), "Common/MatDefs/Misc/Unshaded.j3md");
friendMat2.setColor("Color", new ColorRGBA(.5f, .99f, .5f, 1));
//foe
final Material foeMat = new Material(GlobalObjectStore.<AssetManager>getObject(AssetManager.class), "Common/MatDefs/Misc/Unshaded.j3md");
foeMat.setColor("Color", new ColorRGBA(.5f, 0, 0, 1));
final Material foeMat2 = new Material(GlobalObjectStore.<AssetManager>getObject(AssetManager.class), "Common/MatDefs/Misc/Unshaded.j3md");
foeMat2.setColor("Color", new ColorRGBA(.99f, .5f, .5f, 1));
for(int i = 0; i< 10; ++i)
createDebugFighter("Friend " + i,friendMat, friendMat2, new Vector3f((float)Math.random()*10, (float)Math.random()*10, (float)Math.random() * 10 + 15));
for(int i = 0; i< 10; ++i)
createDebugFighter("Foe " + i,foeMat, foeMat2, new Vector3f((float)Math.random()*-10, (float)Math.random()*-10, (float)Math.random() * 10 + 15));
}
protected void createDebugFighter(final String name, final Material main, final Material scnd, final Vector3f pos)
{
final IObjectController objCtrl = GlobalObjectStore.getObject(IObjectController.class);
tcc.AddTimedCallback(new ICallback()
{
@Override
public void execute()
{
SpaceObject so = SpaceobjectFactory.eINSTANCE.createAsteroid();
so.setId(nextID++);
so.setLocation(pos);
so.setName(name);
so.setRotation(new Quaternion());
so.setLinearVelocity(new Vector3f());
so.setAngularVelocity(new Quaternion());
Node fighter = new Node(name);
ControlledSpaceObject cso = new ControlledSpaceObject(so, fighter);
cso.setDebugDisplay(name);
cso.setGameThreadCallback(new SpatialUpdater(cso));
Geometry gSphere = new Geometry("Sphere", new Sphere(10,10,0.25f));
Geometry gDirection = new Geometry("Direction-Line", new Line(new Vector3f(0,0,0), new Vector3f(0,0,2)));
gSphere.setMaterial(main);
gDirection.setMaterial(scnd);
fighter.attachChild(gSphere);
fighter.attachChild(gDirection);
SimpleBoundingVolumeFactory.createBestBoundingVolume(fighter);
fighter.setLocalTranslation(pos);
objCtrl.addSpaceObject(cso, true);
}
}, (float) (Math.random() * 5));
}
@Override
protected void shutdown()
{
log.debug("Beginning shutdown");
// remove event listeners
// ...
// save all AI states to X
// ...
// detach all AI states
// ...
// free resources
// ...
log.debug("Shutdown complete");
this.initialized = false;
}
}