package complexion.server;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import complexion.common.Utils;
import complexion.common.Verb;
import complexion.network.message.AtomVerbs;
import complexion.network.message.VerbParameter;
import complexion.network.message.VerbSignature;
import complexion.resource.Cache;
import complexion.resource.Sprite;
/**
* Server-side representation of a game Atom.
*
* This includes game-logic stuff, such as atom-interaction(Bump, Enter, etc.),
* verbs, user-defined functions and so on.
*/
public class Atom {
/// OR'd into this.outdated if the sprite or sprite_state needs to be resent
static int spriteOutdated = 1;
/// OR'd into this.outdated if the name or any of the verbs are outdated.
/// It's called textOudated because full strings have to be resent.
static int textOutdated = 2;
/// OR'd into this.outdated if the position(x,y,z,pixel_x,pixel_y),
/// the layer or the direction changed
static int positionOutdated = 4;
/// This is a bitfield that describes which parts of the Atom have changed since
/// the last tick and need to be resent to all clients.
int outdated = 0;
/// Unique ID of the object, generated by the server.
private Integer UID;
/// Last UID that has been generated, incremented each time a new
/// UID is given out.
private static int lastUID;
/// Sprite the object is currently associated with.
private Sprite sprite;
/// Determines whether the object will be rendered above or
/// below other objects
private int layer;
/// Each sprite has multiple states, which are more or less
/// sprites of their own. sprite_state determines which state
/// is used.
private String sprite_state;
/// A package-private list of all the contents of the object
List<Atom> contents = new ArrayList<Atom>();
/** Fetch the filename associated with the atom's current sprite.
*/
public String getSprite() {
if(sprite == null) return null;
return sprite.filename;
}
/** Set the atom's sprite to the given filename.
* @param sprite Filename of the sprite to use, e.g. mask.dmi
*/
public void setSprite(String sprite) {
this.sprite = Cache.getSprite(sprite);
this.outdated |= Atom.spriteOutdated;
}
/// A sprite can define different appearances depending on the
/// direction. This variable should be one of the constants defined
/// in complexion.Directions
private int direction;
/// What this is located inside
/// If this is at the bottom of the tile then it should not be assigned to anything
private Atom location;
public Atom()
{
// Assign the atom a unique UID
lastUID++;
this.UID = lastUID;
}
/**
* @return The UID of this atom.
*/
public int getUID()
{
return UID;
}
/**
* @param new_state The new sprite state of the atom.
*/
public void setSpriteState(String new_state)
{
sprite_state = new_state;
this.outdated |= Atom.spriteOutdated;
}
/**
* @return The current sprite state string.
*/
public String getSpriteState()
{
return sprite_state;
}
/**
* @param new_dir The new facing of the object.
*/
public void setDirection(int new_dir)
{
direction = new_dir;
this.outdated |= Atom.positionOutdated;
}
/**
* @return The current facing of the object.
*/
public int getDirection()
{
return direction;
}
/**
* @return Whatever this is directly inside of.
*/
public Atom getLoc()
{
if(location != null)
return location;
else
return null;
}
/**
* For directly moving this inside something else
*
* @param new_loc The new location of the atom.
*/
public void setLoc(Atom new_loc)
{
if(location != null)
{
location.contents.remove(this);
}
location = new_loc;
this.outdated |= Atom.positionOutdated;
if(new_loc != null)
{
new_loc.contents.add(this);
}
}
/**
* @return The tile at the bottom of whatever this is inside.
*/
public Tile getTile()
{
Atom loc = getLoc();
if(loc == null) return null;
return loc.getTile();
}
/**
* @return The x location of the tile the atom is in.
*/
public int getX()
{
return this.getTile().getX();
}
/**
* @return The y location of the tile the atom is in.
*/
public int getY()
{
return this.getTile().getY();
}
/**
* @return A value representing at which layer(below or above other objects)
* this atom should be rendered.
*/
public int getLayer() {
return layer;
}
/**
* @param layer Sets a value that determines whether this object will be rendered above
* or below other atoms.
*/
public void setLayer(int layer) {
this.layer = layer;
this.outdated |= Atom.positionOutdated;
}
/**
* @return A list of all the atoms contained directly in the tile. Does not include contents of the contents.
* Modifying this list will not affect the atom.
*/
public List<Atom> getContents()
{
// Copy the list to make sure it's not modified.
return new ArrayList<Atom>(contents);
}
/** Function which is periodically called to process this atom.
*/
public void Tick()
{
}
/** Event handler evoked when the user clicks this Atom.
*
* This function is called by the network handler when the client clicks an object
* with the mouse.
*
* If any verbs are registered for this mouse button/key combination, Clicked() will not be called,
* but instead a verb list will be sent to the client.
*
* @param mouseButton LWJGL representation of the mouse button that was used to click the object.
* @param keyboardKey LWJGL representation of a key that was held down while clicking, or 0 if none.
*/
public void Clicked(int mouseButton, int keyboardKey)
{
}
/** Event handler evoked when the user drags another atom onto this atom.
*
* @param from The atom from which the mouse was dragged.
*/
public void Dragged(Atom from)
{
}
/** Retrieve a list of all atom verbs and their arguments, accessible by player.
*
* @param player The player atom for whom we will check whether they can access the verbs.
**/
public AtomVerbs getVerbs(Movable player)
{
AtomVerbs rval = new AtomVerbs();
rval.atomUID = this.UID;
rval.verbs = new ArrayList<VerbSignature>();
// Check all the class' methods
for(Method m : this.getClass().getMethods())
{
// TODO: Check if this verb is accessible by the the player atom
// Check if it's a verb
if(m.getAnnotation(Verb.class) != null)
{
// Create a new verb to add to our list
VerbSignature sig = new VerbSignature();
sig.verbName = m.getName();
sig.parameters = new ArrayList<VerbParameter>();
// Go through the parameters and add their type one by one.
// Note that later for lists, we'll need to check the method's annotations,
// e.g. "@param(position = 1, type = "List")
for(Class type : m.getParameterTypes())
{
// TODO: add list support
VerbParameter param = new VerbParameter();
if(type == String.class)
{
param.type = VerbParameter.Type.STRING;
}
else if(type == Integer.class)
{
param.type = VerbParameter.Type.INTEGER;
}
else
{
throw new RuntimeException("Verb "+this.getClass().getCanonicalName()+":"+
m.getName()+" has parameter of invalid type.");
}
sig.parameters.add(param);
}
rval.verbs.add(sig);
}
}
return rval;
}
/**
* Call an atom verb with the given name(key) and arguments.
*
* @param key
* Name of the function/verb to be called.
* @param args
* A list of objects to be passed as args. These objects will be
* type-checked before being passed to the function.
* @return true on success, false on failure
*/
@SuppressWarnings("rawtypes")
public boolean callVerb(String key, Object[] args)
{
// convert our args to Class
Class[] classes = Utils.toClasses(args);
Method func;
// Attempt to get the method specified.
try
{
func = this.getClass().getMethod(key, classes);
}
catch (SecurityException e)
{
e.printStackTrace();
return false;
}
catch (NoSuchMethodException e)
{
e.printStackTrace();
return false;
}
// if @Verb does not exist fail nicely and don't allow it to be called.
if (func.getAnnotation(Verb.class) == null)
{
System.err.println(key+ " method is not in the verbs list!.. error at line 80 in Atom.callVerb");
return false;
}
// Try to call the function.
try
{
func.invoke(this, args);
}
catch (IllegalArgumentException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
catch (IllegalAccessException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
catch (InvocationTargetException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
return true;
}
}