Semiprivate Objects or multiple Zones

Help
mahe
2011-11-26
2013-06-06
  • mahe
    mahe
    2011-11-26

    Hello,

    I intend to use marauroa for a game. It's not a RPG but the zones concept seems to fit my needs and i wouldn't have to bother about network stuff at all using marauroa.

    In my game I have a fixed amount of players in one zone (like a room) and also a amount of other objects the players interact with. The problem I don't have a solution yet is that the objects have some public information and should also have private information which only the current holder (a player) should see. The holder can change over time.

    Originally I wanted to have the public and private information in separate objects and an additional zone for each player. Then the private objects could be in the players private zone and moved to another zone when the holder changes. This way I easily could have changed the single holder concept to 'holding groups' like alliances.

    But as far as i know a player can only be in one zone at a time. So my next idea was to put the private objects into the player objects. But supporting alliances this way wouldn't be easy.

    Of course I could just keep all the information in one object and hide them on the client but that would make cheating easier.

    Is there an elegant solution for my problem?
    Am I right, that an object can only be in one zone at a time?
    Or could I change it to have a player join more than one zone?
    (didn't read much of the source yet)

    Thanks in Advance!

     
  • Hello maheee,

    an object (including players) can only be in one zone at a time. I think Javy wanted to work on allowing players to see into other zones as audience watching a fight.

    Private attributes are for objects, that are contained in a player object, only. You can keep attributes completely server side by declaring them as hidden.

    An approach that might work for your case is to use RPEvents to keep the "owner" of a world object informed about the private information. The client may need to store the data from the events on its own.

    Hendrik

     
  • Sounds more of a design issue to me. One object can hold both private and public information. Getting public info is built-in, you'll need to design the private part by adding logic for that.

    Having "copies"  will make programming had and error prone. I suggest staying away from that. Like Hendrick says use RPEvent for getting private stuff but you'll need to store information on the server.

     
  • mahe
    mahe
    2011-11-27

    Hey, thanks a lot for your answers.

    Sending information with RPEvents would be possible but that would make logic at client side necessary.

    After a few tests today I'm pretty shure the easiest solution is to just change marauroa to allow players to be in more than one zone. I changed a few classes to be able to create zone trees. So a zone can have a parent zone and players get all objects of their zone and all the parent zones. (Maybe I change it further to be able to select individual zones for each player without restrictions but I'm not yet sure if i need this.)

    I now have problems with visibility of players to each other (players at higher nodes don't see players at lower ones) and propagating information about the structure of the zone tree itself to the players but I still think it is easier to solve these problems than building something with RPEvents.

     
  • I'm glad it worked but here's my advice: avoid changing Marauroa code. Why? If you do you'll need to manually merge newer versions of the code and it can easily get out of hand. Been there, done that. Trust me, you'll avoid lots of issues. Plus if you customize your code you lose the amazing support from Marauroa developers.

    If you want to actually change it propose a patch to it.

    I don't understand your point about modifying the client, you need to create a client anyway. Just my two cents…

     
  • While I cannot promise a solution, I am interested in your changes. Perhaps I can give some pointers into which directions to look. Subzones are on the long term todo list, so this is interesting for us.

     
  • This is a good change to push your features. Send nhnb a patch with your changes for subzoning or zone trees as you call them.

     
  • mahe
    mahe
    2011-11-28

    Ok, my current solution works as follows. I added a parent attribute to IRPZone/MarauroaRPZone, changed RPServerManager so that it sends perceptions for all the zones a player can see (current zone + all parent zones) and modified PerceptionHandler to hold more than one zone at a time.

    This kind of works but I don't really like it. Right now the client can't know in which zones he is and the PerceptionHandler keeps zones and objects even though a player might have left them. So I need a way to pass these information to the client.

    I guess it would be smarter to blow up MessageS2CPerception to contain the perceptions for all the zones of a player and also information about zones added to or removed from a player. Then events for zone changes could be added to IPerceptionListener.

    What are the plans for marauroa, should a player be in more than one zone at a time or should he only be in one zone but able to see other zones? If he is in a subzone, should he also be visible in the parent zone or just see the parent zone?

    Actually I only wanted to add this functionality 'quick and dirty' and start with the real project but if it helps marauroa I might as well do it the right way. After having some problems at the beginning with marauroa I really started to like it.

     
  • In Stendhal we have two issues, that we want to fix in the future, although we are not working on them right now:

    1. The client knows everything that is a zone, even if it is outside the screen of the user. So a modified client can have a larger view window.

    2. Zone changes are visible. You walk towards the end of the current zone and then enter the next one. If a client could see into the neighbouring zones, we could make zone changes invisible.

    The first issues presses towards small zones and the second one towards large zone.

     
  • mahe
    mahe
    2011-12-03

    Yeah, I see the problem there. So the possibility to see other zones would solve this issue.

    At the moment I have a working version of marauroa with the following additional features:
    - Zones with parents (optional). When a player is in a zone he sees all the parent zones too.
    - Server checks the player object for a slot named "zoneids". The player sees all the zones mentioned in the slot (+parents).

    The player gets a perception for every zone he is able to see but they are combined in one message.

    I'm still not sure if I need to add additional information to the message about the zones (added, removed, parent of, …). It should work without because when there is no perception for a zone the player can assume, he isn't allowed to see the zone anymore (btw. I still need to implement this).

    I didn't do much testing and probably need to refactor some parts but I guess thats as far as I get this weekend. If anybody is interessted in what I did so far I uploaded the patch:
    http://134.0.24.7/~mahe/marauroa/

     
  • mahe
    mahe
    2011-12-04

    It seems to work pretty good. I added events to IPerceptionListener for zone changes and added the information about zone parents to the perceptions. Except for bugfixes I'm not gonna change much more and work with that.

     
  • It sounds really interesting, I was trying to do something like that. I suggest sharing a patch for evaluation.

     
  • After some evaluation here are my initial comments (this is my evaluation, not to be taken on behalf of the Marauroa team):

    1) ClientRPWorld is not needed, you can retrieve the object via the public RPObject get(RPObject.ID id) method.
    2) PerceptionHandler: couldn't figure out the need for public void apply(MessageS2CPerceptions message, RPWorld w)
    3) Perception changes are unnecesary IMO since you have the parent already defined in the zone.

    Don't misunderstand me, I'm just seeing some stuff that is redundant when is already available. I'll keep looking at it and will come back with a refined version.

     
  • Here're my proposed changes:

    build.xml                                       |    4 +-
     src/marauroa/client/net/PerceptionHandler.java  |  707 ++++++++++---------
     src/marauroa/common/game/IRPZone.java           |  483 +++++++------
     src/marauroa/server/game/rp/MarauroaRPZone.java |  867 ++++++++++++-----------
     src/marauroa/server/game/rp/RPWorld.java        |   47 +-
     5 files changed, 1123 insertions(+), 985 deletions(-)
    diff --git a/build.xml b/build.xml
    index 7af21ab..89af79d 100644
    --- a/build.xml
    +++ b/build.xml
    @@ -44,7 +44,7 @@
                        byline="true"/>
    
            <!-- Compile it -->
    -       <javac srcdir="${src}" source="1.5" target="1.5" destdir="${buildroot}" debug="true" debuglevel="source,lines" includes="**/marauroa/**" excludes="${exclude.python};${exclude.junit}">
    +       <javac srcdir="${src}" source="1.5" target="1.5" destdir="${buildroot}" debug="true" debuglevel="source,lines" includes="**/marauroa/**" excludes="${exclude.python};${exclude.junit}" includeantruntime="false">
                <classpath>
                    <pathelement path="${log4j_jar}"/>
                    <pathelement path="${junit_jar}"/>
    @@ -68,7 +68,7 @@
            <mkdir dir="${buildroot_client}"/>
    
            <!-- Compile it -->
    -       <javac srcdir="${src}" source="1.5" target="1.5" destdir="${buildroot_client}" debug="true" debuglevel="source,lines">
    +       <javac srcdir="${src}" source="1.5" target="1.5" destdir="${buildroot_client}" debug="true" debuglevel="source,lines" includeantruntime="false">
                <include name="marauroa/client/**"/>
                <include name="marauroa/common/**"/>
                <classpath>
    diff --git a/src/marauroa/client/net/PerceptionHandler.java b/src/marauroa/client/net/PerceptionHandler.java
    index 778f65d..f8c52e4 100644
    --- a/src/marauroa/client/net/PerceptionHandler.java
    +++ b/src/marauroa/client/net/PerceptionHandler.java
    @@ -1,327 +1,380 @@
    -/* $Id: PerceptionHandler.java,v 1.24 2010/11/10 21:50:39 nhnb Exp $ */
    -/***************************************************************************
    - *                      (C) Copyright 2003 - Marauroa                      *
    - ***************************************************************************
    - ***************************************************************************
    - *                                                                         *
    - *   This program is free software; you can redistribute it and/or modify  *
    - *   it under the terms of the GNU General Public License as published by  *
    - *   the Free Software Foundation; either version 2 of the License, or     *
    - *   (at your option) any later version.                                   *
    - *                                                                         *
    - ***************************************************************************/
    -package marauroa.client.net;
    -
    -import java.util.Iterator;
    -import java.util.LinkedList;
    -import java.util.List;
    -import java.util.Map;
    -
    -import marauroa.common.Log4J;
    -import marauroa.common.game.Perception;
    -import marauroa.common.game.RPObject;
    -import marauroa.common.game.RPObject.ID;
    -import marauroa.common.game.RPObjectNotFoundException;
    -import marauroa.common.net.message.MessageS2CPerception;
    -
    -
    -/**
    - * The PerceptionHandler class is in charge of applying correctly the
    - * perceptions to the world. You should always use this class because it is a
    - * complex task that is easy to do in the wrong way.
    - */
    -public class PerceptionHandler {
    -
    -   /** the logger instance. */
    -   private static final marauroa.common.Logger logger = Log4J.getLogger(PerceptionHandler.class);
    -
    -   /** This is the class that interpret the perception contents. */
    -   private IPerceptionListener listener;
    -
    -   /** A list of previous perceptions that are still waiting for being applied. */
    -   private List<MessageS2CPerception> previousPerceptions;
    -
    -   /** the timestamp of last sucessfully applied perception */
    -   private int previousTimestamp;
    -
    -   /** This is true if we are synced with server representation. */
    -   private boolean synced;
    -
    -   /**
    -    * Constructor
    -    * 
    -    */
    -   public PerceptionHandler() {
    -       synced = false;
    -       previousTimestamp = -1;
    -       previousPerceptions = new LinkedList<MessageS2CPerception>();
    -   }
    -
    -   /**
    -    * Constructor
    -    * 
    -    * @param listener
    -    *            the listener that will give meaning to perception handler.
    -    */
    -   public PerceptionHandler(IPerceptionListener listener) {
    -       this();
    -       this.listener = listener;
    -   }
    -
    -   /**
    -    * Apply a perception to a world instance.
    -    * 
    -    * @param message
    -    *            the perception message
    -    * @param world_instance
    -    *            a map representing objects stored in a zone.
    -    * @throws Exception
    -    */
    -   public void apply(MessageS2CPerception message, Map<RPObject.ID, RPObject> world_instance)
    -           throws Exception {
    -       listener.onPerceptionBegin(message.getPerceptionType(), message.getPerceptionTimestamp());
    -       
    -       /*
    -        * We want to clear previous delta^2 info in the objects.
    -        * Delta^2 is only useful in server for getting changes done to the object.
    -        */
    -       for(RPObject obj: world_instance.values()) {
    -           obj.resetAddedAndDeleted();
    -       }
    -
    -       /*
    -        * When we get a sync perception, we set sync flag to true and clear the
    -        * stored data to renew it.
    -        */
    -       if (message.getPerceptionType() == Perception.SYNC) {
    -           try {
    -               /** OnSync: Keep processing */
    -               previousTimestamp = message.getPerceptionTimestamp();
    -               previousPerceptions.clear();
    -
    -               applyPerceptionAddedRPObjects(message, world_instance);
    -               applyPerceptionMyRPObject(message, world_instance);
    -
    -               if (!synced) {
    -                   synced = true;
    -                   listener.onSynced();
    -               }
    -           } catch (Exception e) {
    -               listener.onException(e, message);
    -           }
    -           /*
    -            * When we get a delta perception, we need to check that it is the
    -            * one that we are expecting.
    -            */
    -       } else if (message.getPerceptionType() == Perception.DELTA
    -               && previousTimestamp + 1 == message.getPerceptionTimestamp()) {
    -           try {
    -               /** OnSync: Keep processing */
    -               previousTimestamp = message.getPerceptionTimestamp();
    -
    -               applyPerceptionDeletedRPObjects(message, world_instance);
    -               applyPerceptionModifiedRPObjects(message, world_instance);
    -               applyPerceptionAddedRPObjects(message, world_instance);
    -               applyPerceptionMyRPObject(message, world_instance);
    -           } catch (Exception e) {
    -               listener.onException(e, message);
    -           }
    -           /*
    -            * In any other case, store the perception and check if it helps
    -            * applying any of the still to be applied perceptions.
    -            */
    -       } else {
    -           previousPerceptions.add(message);
    -
    -           for (Iterator<MessageS2CPerception> it = previousPerceptions.iterator(); it.hasNext();) {
    -               MessageS2CPerception previousmessage = it.next();
    -               if (previousTimestamp + 1 == previousmessage.getPerceptionTimestamp()) {
    -                   try {
    -                       /** OnSync: Keep processing */
    -                       previousTimestamp = previousmessage.getPerceptionTimestamp();
    -
    -                       applyPerceptionDeletedRPObjects(previousmessage, world_instance);
    -                       applyPerceptionModifiedRPObjects(previousmessage, world_instance);
    -                       applyPerceptionAddedRPObjects(previousmessage, world_instance);
    -                       applyPerceptionMyRPObject(previousmessage, world_instance);
    -                   } catch (Exception e) {
    -                       listener.onException(e, message);
    -                   }
    -                   it.remove();
    -               }
    -           }
    -
    -           /* If there are no preceptions that means we are synced */
    -           if (previousPerceptions.isEmpty()) {
    -               synced = true;
    -               listener.onSynced();
    -           } else {
    -               synced = false;
    -               listener.onUnsynced();
    -           }
    -       }
    -
    -       /* Notify the listener that the perception is applied */
    -       listener.onPerceptionEnd(message.getPerceptionType(), message.getPerceptionTimestamp());
    -   }
    -
    -   /**
    -    * This method applys perceptions addedto the Map<RPObject::ID,RPObject>
    -    * passed as argument. It clears the map if this is a sync perception
    -    * 
    -    * @param message
    -    *            the perception message
    -    * @param world
    -    *            the container of objects
    -    */
    -   private void applyPerceptionAddedRPObjects(MessageS2CPerception message,
    -           Map<RPObject.ID, RPObject> world) throws RPObjectNotFoundException {
    -       try {
    -           /*
    -            * If the perception is Sync, we clear the contents of the
    -            * container.
    -            */
    -           if (message.getPerceptionType() == Perception.SYNC) {
    -               if (!listener.onClear()) {
    -                   world.clear();
    -               }
    -           }
    -
    -           /* Now add the objects to the container. */
    -           for (RPObject object : message.getAddedRPObjects()) {
    -               if (!listener.onAdded(object)) {
    -                   world.put(object.getID(), object);
    -               }
    -           }
    -       } catch (Exception e) {
    -           logger.error("error in applyPerceptionAddedRPObjects", e);
    -           throw new RPObjectNotFoundException(RPObject.INVALID_ID);
    -       }
    -   }
    -
    -   /**
    -    * This method applys perceptions deleted to the Map<RPObject::ID,RPObject>
    -    * passed as argument.
    -    * 
    -    * @param message
    -    *            the perception message
    -    * @param world
    -    *            the container of objects
    -    */
    -   private void applyPerceptionDeletedRPObjects(MessageS2CPerception message,
    -           Map<RPObject.ID, RPObject> world) throws RPObjectNotFoundException {
    -       try {
    -           for (RPObject object : message.getDeletedRPObjects()) {
    -               if (!listener.onDeleted(object)) {
    -                   world.remove(object.getID());
    -               }
    -           }
    -       } catch (Exception e) {
    -           logger.error("error in applyPerceptionDeletedRPObjects", e);
    -           throw new RPObjectNotFoundException(RPObject.INVALID_ID);
    -       }
    -   }
    -
    -   /**
    -    * This method applys perceptions modified added and modified deleted to the
    -    * Map<RPObject::ID,RPObject> passed as argument.
    -    * 
    -    * @param message
    -    *            the perception message
    -    * @param world
    -    *            the container of objects
    -    */
    -   private void applyPerceptionModifiedRPObjects(MessageS2CPerception message,
    -           Map<RPObject.ID, RPObject> world) throws RPObjectNotFoundException {
    -       try {
    -           /* First we remove the deleted attributes */
    -           for (RPObject object : message.getModifiedDeletedRPObjects()) {
    -               RPObject w_object = world.get(object.getID());
    -               if (!listener.onModifiedDeleted(w_object, object)) {
    -                   w_object.applyDifferences(null, object);
    -               }
    -           }
    -
    -           /* And then we add the new and modified attributes */
    -           for (RPObject object : message.getModifiedAddedRPObjects()) {
    -               RPObject w_object = world.get(object.getID());
    -               if (w_object == null) {
    -                   logger.warn("Missing base object for modified added RPObject with id " + object.getID());
    -                   continue;
    -               }
    -               if (!listener.onModifiedAdded(w_object, object)) {
    -                   w_object.applyDifferences(object, null);
    -               }
    -           }
    -       } catch (RPObjectNotFoundException e) {
    -           logger.error("error in applyModifiedRPObjects", e);
    -           logger.error("world is [" + world.toString() + "]");
    -           throw e;
    -       } catch (Exception e) {
    -           logger.error("error in applyModifiedRPObjects", e);
    -           logger.error("world is [" + world.toString() + "]");
    -           throw new RPObjectNotFoundException(RPObject.INVALID_ID);
    -       }
    -   }
    -
    -   /**
    -    * This method applys perceptions for our RPObject to the Map<RPObject::ID,RPObject>
    -    * passed as argument.
    -    * 
    -    * @param message
    -    *            the perception message
    -    * @param world
    -    *            the container of objects
    -    */
    -   private void applyPerceptionMyRPObject(MessageS2CPerception message,
    -           Map<RPObject.ID, RPObject> world) throws RPObjectNotFoundException {
    -       try {
    -           RPObject added = message.getMyRPObjectAdded();
    -           RPObject deleted = message.getMyRPObjectDeleted();
    -
    -           addMyRPObjectToWorldIfPrivate(added, world);
    -
    -           if (!listener.onMyRPObject(added, deleted)) {
    -               RPObject.ID id = null;
    -
    -               if (added != null) {
    -                   id = added.getID();
    -               }
    -
    -               if (deleted != null) {
    -                   id = deleted.getID();
    -               }
    -
    -               if (id == null) {
    -                   return;
    -               }
    -
    -               RPObject object = world.get(id);
    -
    -               object.applyDifferences(added, deleted);
    -           }
    -       } catch (Exception e) {
    -           logger.error("error in applyPerceptionMyRPObject", e);
    -           throw new RPObjectNotFoundException(RPObject.INVALID_ID);
    -       }
    -   }
    -
    -   /**
    -    * adds our RPObject to the world in case it was not already added by the public perception.
    -    *
    -    * @param added added changes of my object
    -    * @param world the container of objects
    -    */
    -   private void addMyRPObjectToWorldIfPrivate(RPObject added, Map<ID, RPObject> world) {
    -       if (added == null) {
    -           return;
    -       }
    -       if (world.get(added.getID()) != null) {
    -           return;
    -       }
    -       RPObject object = (RPObject) added.clone();
    -       if (!listener.onAdded(object)) {
    -           world.put(object.getID(), object);
    -       }
    -   }
    -}
    +/* $Id: PerceptionHandler.java,v 1.24 2010/11/10 21:50:39 nhnb Exp $ */
    +/***************************************************************************
    + *                      (C) Copyright 2003 - Marauroa                      *
    + ***************************************************************************
    + ***************************************************************************
    + *                                                                         *
    + *   This program is free software; you can redistribute it and/or modify  *
    + *   it under the terms of the GNU General Public License as published by  *
    + *   the Free Software Foundation; either version 2 of the License, or     *
    + *   (at your option) any later version.                                   *
    + *                                                                         *
    + ***************************************************************************/
    +package marauroa.client.net;
    +
    +import java.util.Iterator;
    +import java.util.LinkedList;
    +import java.util.List;
    +import java.util.Map;
    +import marauroa.common.Log4J;
    +import marauroa.common.game.Perception;
    +import marauroa.common.game.RPObject;
    +import marauroa.common.game.RPObject.ID;
    +import marauroa.common.game.RPObjectNotFoundException;
    +import marauroa.common.net.message.MessageS2CPerception;
    +
    +
    +/**
    + * The PerceptionHandler class is in charge of applying correctly the
    + * perceptions to the world. You should always use this class because it is a
    + * complex task that is easy to do in the wrong way.
    + */
    +public class PerceptionHandler {
    +
    +   /** the logger instance. */
    +   private static final marauroa.common.Logger logger = Log4J.getLogger(PerceptionHandler.class);
    +
    +   /** This is the class that interpret the perception contents. */
    +   private IPerceptionListener listener;
    +
    +   /** A list of previous perceptions that are still waiting for being applied. */
    +   private List<MessageS2CPerception> previousPerceptions;
    +
    +   /** the timestamp of last successfully applied perception */
    +   private int previousTimestamp;
    +
    +   /** This is true if we are synced with server representation. */
    +   private boolean synced;
    +
    +   /**
    +    * Constructor
    +    * 
    +    */
    +   public PerceptionHandler() {
    +       synced = false;
    +       previousTimestamp = -1;
    +       previousPerceptions = new LinkedList<MessageS2CPerception>();
    +   }
    +
    +   /**
    +    * Constructor
    +    * 
    +    * @param listener
    +    *            the listener that will give meaning to perception handler.
    +    */
    +   public PerceptionHandler(IPerceptionListener listener) {
    +       this();
    +       this.listener = listener;
    +   }
    +
    +// public void apply(MessageS2CPerceptions message, RPWorld world) throws Exception {
    +//     
    +//     
    +//     /*
    +//      * if timestamp fits, execute all perceptions
    +//      */
    +//     if (previousTimestamp + 1 == message.getPerceptionTimestamp()) {
    +//         applyPerceptions(message, world);
    +//         previousTimestamp = message.getPerceptionTimestamp();
    +//         
    +//         //try if there are previous messages we can now use
    +//         boolean useless = false;
    +//         while (!useless) {
    +//             useless = true;
    +//             for (MessageS2CPerceptions m : previousHierarchyPerceptions) {
    +//                 if (previousTimestamp + 1 == m.getPerceptionTimestamp()) {
    +//                     applyPerceptions(m, world);
    +//                     previousTimestamp = m.getPerceptionTimestamp();
    +//                     useless = false;
    +//                 }
    +//             }
    +//         }
    +//         if (synced == false && previousPerceptions.isEmpty()) {
    +//             synced = true;
    +//             listener.onSynced();
    +//         }
    +//     /*
    +//      * else, store message
    +//      */
    +//     } else {
    +//         synced = false;
    +//         listener.onUnsynced();
    +//     }
    +// }
    +   
    +// private void applyPerceptions(MessageS2CPerceptions message, RPWorld world) throws Exception {
    +//     listener.onPerceptionBegin(/*message.getPerceptionType()*/(byte)0, message.getPerceptionTimestamp());
    +//     
    +//     for (MessageS2CPerception p : message.getPerceptions()) {
    +//         IRPZone.ID zoneId = p.getRPZoneID();
    +//         if (world.getRPZone(zoneId) == null) {
    +//             world.addRPZone(new ClientRPZone(world, zoneId));
    +//             listener.onZoneAdded(zoneId);
    +//         }
    +//         ClientRPZone zone = (ClientRPZone)world.getRPZone(zoneId);
    +//         zone.setUpdateTimestamp(message.getPerceptionTimestamp());
    +//         applyPerception(p, zone, message.getPerceptionType());
    +//     }
    +//     
    +//     applyPerceptionMyRPObject(message, world);
    +//     
    +//     listener.onPerceptionEnd(/*message.getPerceptionType()*/(byte)0, message.getPerceptionTimestamp());
    +// }
    +   
    +   /**
    +    * Apply a perception to a world instance.
    +    * 
    +    * @param message
    +    *            the perception message
    +    * @param world_instance
    +    *            a map representing objects stored in a zone.
    +    * @throws Exception
    +    */
    +   public void apply(MessageS2CPerception message, Map<RPObject.ID, RPObject> world_instance)
    +            throws Exception {
    +        listener.onPerceptionBegin(message.getPerceptionType(), message.getPerceptionTimestamp());
    +
    +       /*
    +        * We want to clear previous delta^2 info in the objects.
    +        * Delta^2 is only useful in server for getting changes done to the object.
    +        */
    +       for(RPObject obj: world_instance.values()) {
    +                obj.resetAddedAndDeleted();
    +            }
    +
    +       /*
    +        * When we get a sync perception, we set sync flag to true and clear the
    +        * stored data to renew it.
    +        */
    +       if (message.getPerceptionType() == Perception.SYNC) {
    +           try {
    +               /** OnSync: Keep processing */
    +               previousTimestamp = message.getPerceptionTimestamp();
    +               previousPerceptions.clear();
    +
    +               applyPerceptionAddedRPObjects(message, world_instance);
    +               applyPerceptionMyRPObject(message, world_instance);
    +
    +               if (!synced) {
    +                   synced = true;
    +                   listener.onSynced();
    +               }
    +           } catch (Exception e) {
    +               listener.onException(e, message);
    +           }
    +           /*
    +            * When we get a delta perception, we need to check that it is the
    +            * one that we are expecting.
    +            */
    +       } else if (message.getPerceptionType() == Perception.DELTA
    +               && previousTimestamp + 1 == message.getPerceptionTimestamp()) {
    +           try {
    +               /** OnSync: Keep processing */
    +               previousTimestamp = message.getPerceptionTimestamp();
    +
    +               applyPerceptionDeletedRPObjects(message, world_instance);
    +               applyPerceptionModifiedRPObjects(message, world_instance);
    +               applyPerceptionAddedRPObjects(message, world_instance);
    +               applyPerceptionMyRPObject(message, world_instance);
    +           } catch (Exception e) {
    +               listener.onException(e, message);
    +           }
    +           /*
    +            * In any other case, store the perception and check if it helps
    +            * applying any of the still to be applied perceptions.
    +            */
    +       } else {
    +           previousPerceptions.add(message);
    +
    +           for (Iterator<MessageS2CPerception> it = previousPerceptions.iterator(); it.hasNext();) {
    +               MessageS2CPerception previousmessage = it.next();
    +               if (previousTimestamp + 1 == previousmessage.getPerceptionTimestamp()) {
    +                   try {
    +                       /** OnSync: Keep processing */
    +                       previousTimestamp = previousmessage.getPerceptionTimestamp();
    +
    +                       applyPerceptionDeletedRPObjects(previousmessage, world_instance);
    +                       applyPerceptionModifiedRPObjects(previousmessage, world_instance);
    +                       applyPerceptionAddedRPObjects(previousmessage, world_instance);
    +                       applyPerceptionMyRPObject(previousmessage, world_instance);
    +                   } catch (Exception e) {
    +                       listener.onException(e, message);
    +       }
    +                   it.remove();
    +               }
    +           }
    +
    +           /* If there are no preceptions that means we are synced */
    +           if (previousPerceptions.isEmpty()) {
    +               synced = true;
    +               listener.onSynced();
    +           } else {
    +               synced = false;
    +               listener.onUnsynced();
    +   }
    +       }
    +
    +       /* Notify the listener that the perception is applied */
    +       listener.onPerceptionEnd(message.getPerceptionType(), message.getPerceptionTimestamp());
    +   }
    +
    +   /**
    +    * This method applies perceptions added to the Map<RPObject::ID,RPObject>
    +    * passed as argument. It clears the map if this is a sync perception
    +    * 
    +    * @param message
    +    *            the perception message
    +    * @param world
    +    *            the container of objects
    +    */
    +   private void applyPerceptionAddedRPObjects(MessageS2CPerception message,
    +           Map<RPObject.ID, RPObject> world) throws RPObjectNotFoundException {
    +       try {
    +           /*
    +            * If the perception is Sync, we clear the contents of the
    +            * container.
    +            */
    +           if (message.getPerceptionType() == Perception.SYNC) {
    +               if (!listener.onClear()) {
    +                   world.clear();
    +               }
    +           }
    +
    +           /* Now add the objects to the container. */
    +           for (RPObject object : message.getAddedRPObjects()) {
    +               if (!listener.onAdded(object)) {
    +                   world.put(object.getID(), object);
    +               }
    +           }
    +       } catch (Exception e) {
    +           logger.error("error in applyPerceptionAddedRPObjects", e);
    +           throw new RPObjectNotFoundException(RPObject.INVALID_ID);
    +       }
    +   }
    +
    +   /**
    +    * This method applys perceptions deleted to the Map<RPObject::ID,RPObject>
    +    * passed as argument.
    +    * 
    +    * @param message
    +    *            the perception message
    +    * @param world
    +    *            the container of objects
    +    */
    +   private void applyPerceptionDeletedRPObjects(MessageS2CPerception message,
    +           Map<RPObject.ID, RPObject> world) throws RPObjectNotFoundException {
    +       try {
    +           for (RPObject object : message.getDeletedRPObjects()) {
    +               if (!listener.onDeleted(object)) {
    +                   world.remove(object.getID());
    +               }
    +           }
    +       } catch (Exception e) {
    +           logger.error("error in applyPerceptionDeletedRPObjects", e);
    +           throw new RPObjectNotFoundException(RPObject.INVALID_ID);
    +       }
    +   }
    +
    +   /**
    +    * This method applys perceptions modified added and modified deleted to the
    +    * Map<RPObject::ID,RPObject> passed as argument.
    +    * 
    +    * @param message
    +    *            the perception message
    +    * @param world
    +    *            the container of objects
    +    */
    +   private void applyPerceptionModifiedRPObjects(MessageS2CPerception message,
    +           Map<RPObject.ID, RPObject> world) throws RPObjectNotFoundException {
    +       try {
    +           /* First we remove the deleted attributes */
    +           for (RPObject object : message.getModifiedDeletedRPObjects()) {
    +               RPObject w_object = world.get(object.getID());
    +               if (!listener.onModifiedDeleted(w_object, object)) {
    +                   w_object.applyDifferences(null, object);
    +               }
    +           }
    +
    +           /* And then we add the new and modified attributes */
    +           for (RPObject object : message.getModifiedAddedRPObjects()) {
    +               RPObject w_object = world.get(object.getID());
    +               if (w_object == null) {
    +                   logger.warn("Missing base object for modified added RPObject with id " + object.getID());
    +                   continue;
    +               }
    +               if (!listener.onModifiedAdded(w_object, object)) {
    +                   w_object.applyDifferences(object, null);
    +               }
    +           }
    +       } catch (RPObjectNotFoundException e) {
    +           logger.error("error in applyModifiedRPObjects", e);
    +           logger.error("world is [" + world.toString() + "]");
    +           throw e;
    +       } catch (Exception e) {
    +           logger.error("error in applyModifiedRPObjects", e);
    +           logger.error("world is [" + world.toString() + "]");
    +           throw new RPObjectNotFoundException(RPObject.INVALID_ID);
    +       }
    +   }
    +
    +   /**
    +    * This method applys perceptions for our RPObject to the Map<RPObject::ID,RPObject>
    +    * passed as argument.
    +    * 
    +    * @param message
    +    *            the perception message
    +    * @param world
    +    *            the container of objects
    +    */
    +   private void applyPerceptionMyRPObject(MessageS2CPerception message,
    +           Map<RPObject.ID, RPObject> world) throws RPObjectNotFoundException {
    +       try {
    +           RPObject added = message.getMyRPObjectAdded();
    +           RPObject deleted = message.getMyRPObjectDeleted();
    +
    +           addMyRPObjectToWorldIfPrivate(added, world);
    +
    +           if (!listener.onMyRPObject(added, deleted)) {
    +               RPObject.ID id = null;
    +
    +               if (added != null) {
    +                   id = added.getID();
    +               }
    +
    +               if (deleted != null) {
    +                   id = deleted.getID();
    +               }
    +
    +               if (id == null) {
    +                   return;
    +               }
    +
    +               RPObject object = world.get(id);
    +
    +               object.applyDifferences(added, deleted);
    +           }
    +       } catch (Exception e) {
    +           logger.error("error in applyPerceptionMyRPObject", e);
    +           throw new RPObjectNotFoundException(RPObject.INVALID_ID);
    +       }
    +   }
    +
    +   /**
    +    * adds our RPObject to the world in case it was not already added by the public perception.
    +    *
    +    * @param added added changes of my object
    +    * @param world the container of objects
    +    */
    +   private void addMyRPObjectToWorldIfPrivate(RPObject added, Map<ID, RPObject> world) {
    +       if (added == null) {
    +           return;
    +       }
    +       if (world.get(added.getID()) != null) {
    +           return;
    +       }
    +       RPObject object = (RPObject) added.clone();
    +       if (!listener.onAdded(object)) {
    +           world.put(object.getID(), object);
    +       }
    +   }
    +}
    diff --git a/src/marauroa/common/game/IRPZone.java b/src/marauroa/common/game/IRPZone.java
    index 86f7512..455408d 100644
    --- a/src/marauroa/common/game/IRPZone.java
    +++ b/src/marauroa/common/game/IRPZone.java
    @@ -1,234 +1,249 @@
    -/* $Id: IRPZone.java,v 1.25 2007/11/04 11:32:31 nhnb Exp $ */
    -/***************************************************************************
    - *                      (C) Copyright 2003 - Marauroa                      *
    - ***************************************************************************
    - ***************************************************************************
    - *                                                                         *
    - *   This program is free software; you can redistribute it and/or modify  *
    - *   it under the terms of the GNU General Public License as published by  *
    - *   the Free Software Foundation; either version 2 of the License, or     *
    - *   (at your option) any later version.                                   *
    - *                                                                         *
    - ***************************************************************************/
    -package marauroa.common.game;
    -
    -import java.util.Iterator;
    -
    -/**
    - * Interface for managing the objects in a RPZone. An RPZone is a storage for
    - * objects, that has an unique per World associated id. It <b>must</b> provide
    - * at least methods for: * adding * removing * querying * modifying
    - *
    - * It almost provide methods that are called on load and on unload of the zone.
    - * And finally it must provide Perception when asked by
    - *
    - * @see marauroa.server.game.rp.RPServerManager
    - *
    - * @author miguel
    - */
    -public interface IRPZone extends Iterable<RPObject> {
    -
    -   /** An unique ID for this zone */
    -   public static class ID implements marauroa.common.net.Serializable {
    -
    -       private String id;
    -
    -       /**
    -        * Constructor
    -        *
    -        * @param zid
    -        *            the object id
    -        */
    -       public ID(String zid) {
    -           id = zid;
    -       }
    -
    -       /**
    -        * This method returns the object id
    -        *
    -        * @return the object id.
    -        */
    -       public String getID() {
    -           return id;
    -       }
    -
    -       /**
    -        * This method returns true of both ids are equal.
    -        *
    -        * @param anotherid
    -        *            another id object
    -        * @return true if they are equal, or false otherwise.
    -        */
    -       @Override
    -       public boolean equals(Object anotherid) {
    -           if (anotherid instanceof IRPZone.ID) {
    -               return (((IRPZone.ID) anotherid).id).equals(this.id);
    -           } else {
    -               return false;
    -           }
    -       }
    -
    -       /** We need it for HashMap */
    -       @Override
    -       public int hashCode() {
    -           if (id!= null){
    -               return id.hashCode();
    -           }
    -           else return 0;
    -       }
    -
    -       /**
    -        * This method returns a String that represent the object
    -        *
    -        * @return a string representing the object.
    -        */
    -       @Override
    -       public String toString() {
    -           return "IRPZone.ID [id=" + id + "]";
    -       }
    -
    -       /** Serialize the object into a stream of bytes. */
    -       public void writeObject(marauroa.common.net.OutputSerializer out)
    -               throws java.io.IOException {
    -           out.write(id);
    -       }
    -
    -       /** Deserialize the object and fills this object with the data */
    -       public void readObject(marauroa.common.net.InputSerializer in) throws java.io.IOException {
    -           id = in.readString();
    -       }
    -   }
    -
    -   /**
    -    * Returns the ID of the zone
    -    *
    -    * @return zone id
    -    */
    -   public ID getID();
    -
    -   /**
    -    * This method is called when the zone is created to popullate it
    -    *
    -    * @throws Exception
    -    *             if there has been any problem loading zone
    -    */
    -   public void onInit() throws Exception;
    -
    -   /**
    -    * This method is called when the server finish to save the content of the
    -    * zone
    -    *
    -    * @throws Exception
    -    *             if there has been any problem loading zone
    -    */
    -   public void onFinish() throws Exception;
    -
    -   /**
    -    * This method adds an object to the Zone. Object can be modified after this
    -    * methods and changes are expected to happen too in zone stored object.
    -    *
    -    * @param object
    -    *            the object
    -    * @throws RPObjectInvalidException
    -    *            in case the rpobject is invalid for some reason
    -    */
    -   public void add(RPObject object) throws RPObjectInvalidException;
    -
    -   /**
    -    * This method tag an object of the Zone as modified. Object can be modified
    -    * after this methods and changes are expected to happen too in zone stored
    -    * object.
    -    *
    -    * @param object
    -    *            the object
    -    * @throws RPObjectInvalidException
    -    *            in case the rpobject is invalid for some reason
    -    */
    -   public void modify(RPObject object) throws RPObjectInvalidException;
    -
    -   /**
    -    * This method removed an object of the Zone and return it. Object can be
    -    * modified but it is not longer inside zone.
    -    *
    -    * @param id
    -    *            the object identification
    -    * @return the remove object or null if it is not found.
    -    */
    -   public RPObject remove(RPObject.ID id);
    -
    -   /**
    -    * Hide an object from the perceptions, but it doesn't remove it from world.
    -    * Any further calls to modify will be ignored.
    -    *
    -    * @param object
    -    *            the object to hide.
    -    */
    -   public void hide(RPObject object);
    -
    -   /**
    -    * Makes a hidden object to be visible again. It will appear on the
    -    * perception as an added object.
    -    *
    -    * @param object
    -    *            the object to unhide.
    -    */
    -   public void unhide(RPObject object);
    -
    -   /**
    -    * This method returns an object of the Zone. Object can be modified after
    -    * this methods and changes are expected to happen too in zone stored
    -    * object.
    -    *
    -    * @param id
    -    *            the object identification
    -    * @return the remove object or null if it is not found.
    -    */
    -   public RPObject get(RPObject.ID id);
    -
    -   /**
    -    * This method returns true if the object exists in the Zone
    -    *
    -    * @param id
    -    *            the object identification
    -    * @return true if object exists
    -    */
    -   public boolean has(RPObject.ID id);
    -
    -   /**
    -    * Assigns a valid RPObject.ID to the object given as parameter
    -    *
    -    * @param object
    -    *            the object
    -    */
    -   public void assignRPObjectID(RPObject object);
    -
    -   /**
    -    * Iterates over the elements of the zone
    -    *
    -    * @return an iterator over zone
    -    */
    -   public Iterator<RPObject> iterator();
    -
    -   /**
    -    * Returns the number of elements of the zone
    -    *
    -    * @return the amount of objects that exists in the zone.
    -    */
    -   public long size();
    -
    -   /**
    -    * This method return the perception of a zone for a player
    -    *
    -    * @param player
    -    *            player object
    -    * @param type
    -    *            type of perception
    -    * @return the perception
    -    */
    -   public Perception getPerception(RPObject player, byte type);
    -
    -   /**
    -    * This method is called to take zone to the next turn
    -    */
    -   public void nextTurn();
    -}
    +/* $Id: IRPZone.java,v 1.25 2007/11/04 11:32:31 nhnb Exp $ */
    +/***************************************************************************
    + *                      (C) Copyright 2003 - Marauroa                      *
    + ***************************************************************************
    + ***************************************************************************
    + *                                                                         *
    + *   This program is free software; you can redistribute it and/or modify  *
    + *   it under the terms of the GNU General Public License as published by  *
    + *   the Free Software Foundation; either version 2 of the License, or     *
    + *   (at your option) any later version.                                   *
    + *                                                                         *
    + ***************************************************************************/
    +package marauroa.common.game;
    +
    +import java.util.Iterator;
    +
    +/**
    + * Interface for managing the objects in a RPZone. An RPZone is a storage for
    + * objects, that has an unique per World associated id. It <b>must</b> provide
    + * at least methods for: * adding * removing * querying * modifying
    + *
    + * It almost provide methods that are called on load and on unload of the zone.
    + * And finally it must provide Perception when asked by
    + *
    + * @see marauroa.server.game.rp.RPServerManager
    + *
    + * @author miguel
    + */
    +public interface IRPZone extends Iterable<RPObject> {
    +
    +   /** An unique ID for this zone */
    +   public static class ID implements marauroa.common.net.Serializable {
    +
    +       private String id;
    +
    +       /**
    +        * Constructor
    +        *
    +        * @param zid
    +        *            the object id
    +        */
    +       public ID(String zid) {
    +           id = zid;
    +       }
    +
    +       /**
    +        * This method returns the object id
    +        *
    +        * @return the object id.
    +        */
    +       public String getID() {
    +           return id;
    +       }
    +
    +       /**
    +        * This method returns true of both ids are equal.
    +        *
    +        * @param anotherid
    +        *            another id object
    +        * @return true if they are equal, or false otherwise.
    +        */
    +       @Override
    +       public boolean equals(Object anotherid) {
    +           if (anotherid instanceof IRPZone.ID) {
    +               return (((IRPZone.ID) anotherid).id).equals(this.id);
    +           } else {
    +               return false;
    +           }
    +       }
    +
    +       /** We need it for HashMap */
    +       @Override
    +       public int hashCode() {
    +           if (id!= null){
    +               return id.hashCode();
    +           }
    +           else return 0;
    +       }
    +
    +       /**
    +        * This method returns a String that represent the object
    +        *
    +        * @return a string representing the object.
    +        */
    +       @Override
    +       public String toString() {
    +           return "IRPZone.ID [id=" + id + "]";
    +       }
    +
    +       /** Serialize the object into a stream of bytes. */
    +                @Override
    +       public void writeObject(marauroa.common.net.OutputSerializer out)
    +               throws java.io.IOException {
    +           out.write(id);
    +       }
    +
    +       /** Deserialize the object and fills this object with the data */
    +                @Override
    +       public void readObject(marauroa.common.net.InputSerializer in) throws java.io.IOException {
    +           id = in.readString();
    +       }
    +   }
    +
    +   /**
    +    * Returns the ID of the zone
    +    *
    +    * @return zone id
    +    */
    +   public ID getID();
    +
    +   /**
    +    * This method is called when the zone is created to popullate it
    +    *
    +    * @throws Exception
    +    *             if there has been any problem loading zone
    +    */
    +   public void onInit() throws Exception;
    +
    +   /**
    +    * This method is called when the server finish to save the content of the
    +    * zone
    +    *
    +    * @throws Exception
    +    *             if there has been any problem loading zone
    +    */
    +   public void onFinish() throws Exception;
    +
    +   /**
    +    * This method adds an object to the Zone. Object can be modified after this
    +    * methods and changes are expected to happen too in zone stored object.
    +    *
    +    * @param object
    +    *            the object
    +    * @throws RPObjectInvalidException
    +    *            in case the rpobject is invalid for some reason
    +    */
    +   public void add(RPObject object) throws RPObjectInvalidException;
    +
    +   /**
    +    * This method tag an object of the Zone as modified. Object can be modified
    +    * after this methods and changes are expected to happen too in zone stored
    +    * object.
    +    *
    +    * @param object
    +    *            the object
    +    * @throws RPObjectInvalidException
    +    *            in case the rpobject is invalid for some reason
    +    */
    +   public void modify(RPObject object) throws RPObjectInvalidException;
    +
    +   /**
    +    * This method removed an object of the Zone and return it. Object can be
    +    * modified but it is not longer inside zone.
    +    *
    +    * @param id
    +    *            the object identification
    +    * @return the remove object or null if it is not found.
    +    */
    +   public RPObject remove(RPObject.ID id);
    +
    +   /**
    +    * Hide an object from the perceptions, but it doesn't remove it from world.
    +    * Any further calls to modify will be ignored.
    +    *
    +    * @param object
    +    *            the object to hide.
    +    */
    +   public void hide(RPObject object);
    +
    +   /**
    +    * Makes a hidden object to be visible again. It will appear on the
    +    * perception as an added object.
    +    *
    +    * @param object
    +    *            the object to unhide.
    +    */
    +   public void unhide(RPObject object);
    +
    +   /**
    +    * This method returns an object of the Zone. Object can be modified after
    +    * this methods and changes are expected to happen too in zone stored
    +    * object.
    +    *
    +    * @param id
    +    *            the object identification
    +    * @return the remove object or null if it is not found.
    +    */
    +   public RPObject get(RPObject.ID id);
    +
    +   /**
    +    * This method returns true if the object exists in the Zone
    +    *
    +    * @param id
    +    *            the object identification
    +    * @return true if object exists
    +    */
    +   public boolean has(RPObject.ID id);
    +
    +   /**
    +    * Assigns a valid RPObject.ID to the object given as parameter
    +    *
    +    * @param object
    +    *            the object
    +    */
    +   public void assignRPObjectID(RPObject object);
    +
    +   /**
    +    * Iterates over the elements of the zone
    +    *
    +    * @return an iterator over zone
    +    */
    +        @Override
    +   public Iterator<RPObject> iterator();
    +
    +   /**
    +    * Returns the number of elements of the zone
    +    *
    +    * @return the amount of objects that exists in the zone.
    +    */
    +   public long size();
    +
    +   /**
    +    * This method return the perception of a zone for a player
    +    *
    +    * @param player
    +    *            player object
    +    * @param type
    +    *            type of perception
    +    * @return the perception
    +    */
    +   public Perception getPerception(RPObject player, byte type);
    +
    +   /**
    +    * This method is called to take zone to the next turn
    +    */
    +   public void nextTurn();
    +   
    +   /**
    +         * Zone's parent, null if root
    +         * @return Parent zone
    +         */
    +   public IRPZone getParent();
    +        
    +        /**
    +         * Set Zone's parent
    +         * @return Parent zone
    +         */
    +   public void setParent(IRPZone parent);
    +}
    diff --git a/src/marauroa/server/game/rp/MarauroaRPZone.java b/src/marauroa/server/game/rp/MarauroaRPZone.java
    index 83d270e..200baa3 100644
    --- a/src/marauroa/server/game/rp/MarauroaRPZone.java
    +++ b/src/marauroa/server/game/rp/MarauroaRPZone.java
    @@ -1,410 +1,457 @@
    -/* $Id: MarauroaRPZone.java,v 1.39 2010/12/20 22:51:47 nhnb Exp $ */
    -/***************************************************************************
    - *                      (C) Copyright 2003 - Marauroa                      *
    - ***************************************************************************
    - ***************************************************************************
    - *                                                                         *
    - *   This program is free software; you can redistribute it and/or modify  *
    - *   it under the terms of the GNU General Public License as published by  *
    - *   the Free Software Foundation; either version 2 of the License, or     *
    - *   (at your option) any later version.                                   *
    - *                                                                         *
    - ***************************************************************************/
    -package marauroa.server.game.rp;
    -
    -import java.io.PrintStream;
    -import java.util.ArrayList;
    -import java.util.Date;
    -import java.util.HashSet;
    -import java.util.Iterator;
    -import java.util.LinkedHashMap;
    -import java.util.LinkedList;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.Random;
    -import java.util.Set;
    -
    -import marauroa.common.Log4J;
    -import marauroa.common.game.IRPZone;
    -import marauroa.common.game.Perception;
    -import marauroa.common.game.RPObject;
    -import marauroa.common.game.RPObjectInvalidException;
    -import marauroa.server.db.command.DBCommandQueue;
    -import marauroa.server.game.db.DAORegister;
    -import marauroa.server.game.db.RPZoneDAO;
    -import marauroa.server.game.dbcommand.StoreZoneCommand;
    -
    -/**
    - * Default implementation of <code>IRPZone</code>. This class implements the
    - * Delta^2 algorithm to save bandwidth.
    - * <p>
    - * The idea behind the DPA is to avoid sending ALL the objects to a client each
    - * time, but only those that have been modified. Imagine that we have 1000
    - * objects, and only Object 1 and Object 505 are active objects that are
    - * modified each turn.
    - *
    - * <pre>
    - *   The Traditional method:
    - *
    - *   - Get objects that our player should see ( 1000 objects )
    - *   - Send them to player ( 1000 objects )
    - *   - Next turn
    - *   - Get objects that our player should see ( 1000 objects )
    - *   - Send them to player
    - *   - Next turn
    - *   ...
    - * </pre>
    - *
    - * I hope you see the problem... we are sending objects that haven't changed
    - * each turn.
    - *
    - * <pre>
    - *   The delta perception algorithm:
    - *
    - *   - Get objects that our player should see ( 1000 objects )
    - *   - Reduce the list to the modified ones ( 1000 objects )
    - *   - Store also the objects that are not longer visible ( 0 objects )
    - *   - Send them to player ( 1000 objects )
    - *   - Next turn
    - *   - Get objects that our player should see ( 1000 objects )
    - *   - Reduce the list to the modified ones ( 2 objects )
    - *   - Store also the objects that are not longer visible ( 0 objects )
    - *   - Send them to player ( 2 objects )
    - *   - Next turn
    - *   ...
    - * </pre>
    - *
    - * The next step of the delta perception algorithm is pretty clear: delta2<br>
    - * The idea is to send only what changes of the objects that changed. This way
    - * we save even more bandwidth, making perceptions around 20% of the original
    - * delta perception size.
    - * <p>
    - * The delta2 algorithm is based on four containers:
    - * <ul>
    - * <li>List of added objects
    - * <li>List of modified added attributes of objects
    - * <li>List of modified deleted attributes of objects
    - * <li>List of deleted objects
    - * </ul>
    - * To make perceptions work, it is important to call the modify method in
    - * RPZone, so this way objects modified are stored in the modified list.
    - *
    - * @author miguel
    - */
    -public class MarauroaRPZone implements IRPZone {
    -
    -   /** the logger instance. */
    -   private static final marauroa.common.Logger logger = Log4J.getLogger(MarauroaRPZone.class);
    -
    -   /** Name of the zone */
    -   protected ID zoneid;
    -
    -   /** Objects contained by the zone indexed by its id. */
    -   protected Map<RPObject.ID, RPObject> objects;
    -
    -   /**
    -    * Objects that has been modified on zone since last turn. This information
    -    * is useful for Delta^2 algorithm.
    -    */
    -   private Set<RPObject> modified;
    -
    -   /** This is the perception for the current turn. */
    -   private Perception perception;
    -
    -   /** This is a cache for the perception for this turn. */
    -   private Perception prebuildDeltaPerception = null;
    -
    -   /** This is a sync perception cache */
    -   private Perception prebuildSyncPerception = null;
    -
    -   /** This variable stores the last assigned id, that is unique per zone. */
    -   private int lastNonPermanentIdAssigned = 0;
    -
    -   private static Random rand = new Random();
    -
    -   /**
    -    * Creates a new MarauroaRPZone
    -    *
    -    * @param zoneid name of zone
    -    */
    -   public MarauroaRPZone(String zoneid) {
    -       this.zoneid = new ID(zoneid);
    -       rand.setSeed(new Date().getTime());
    -
    -       objects = new LinkedHashMap<RPObject.ID, RPObject>();
    -       modified = new HashSet<RPObject>();
    -
    -       perception = new Perception(Perception.DELTA, this.zoneid);
    -   }
    -
    -   /** Returns the zoneid */
    -   public ID getID() {
    -       return zoneid;
    -   }
    -
    -   public void onFinish() throws Exception {
    -       storeToDatabase();
    -   }
    -
    -   /**
    -    * Store objects that has been tagged as storable to database asynchronously.
    -    * Note: This methods returns before the saving is completed.
    -    */
    -   public void storeToDatabase() {
    -       List<RPObject> list = new LinkedList<RPObject>();
    -       for (RPObject object : objects.values()) {
    -           list.add((RPObject) object.clone());
    -       }
    -       DBCommandQueue.get().enqueue(new StoreZoneCommand(this, list));
    -   }
    -
    -
    -   /**
    -    * Load objects in database for this zone that were stored 
    -    * and waits for the database operation to complete.
    -    */
    -   public void onInit() throws Exception {
    -       DAORegister.get().get(RPZoneDAO.class).loadRPZone(this);
    -   }
    -
    -   /**
    -    * This method adds an object to this zone.
    -    *
    -    * @param object
    -    *            object to add.
    -    * @throws RPObjectInvalidException
    -    *             if it lacks of mandatory attributes.
    -    */
    -   public void add(RPObject object) throws RPObjectInvalidException {
    -       try {
    -           RPObject.ID id = object.getID();
    -
    -           object.resetAddedAndDeleted();
    -           objects.put(id, object);
    -
    -           if (!object.isHidden()) {
    -               perception.added(object);
    -           }
    -       } catch (Exception e) {
    -           throw new RPObjectInvalidException(e);
    -       }
    -   }
    -
    -   /**
    -    * This method notify zone that the object has been modified. You should
    -    * call it only once per turn, even if inside the turn you modify it several
    -    * times.
    -    *
    -    * @param object
    -    *            object to modify.
    -    * @throws RPObjectInvalidException
    -    *             if it lacks of mandatory attributes.
    -    */
    -   public void modify(RPObject object) throws RPObjectInvalidException {
    -       try {
    -           modified.add(object);
    -       } catch (Exception e) {
    -           throw new RPObjectInvalidException(e.getMessage());
    -       }
    -   }
    -
    -   /**
    -    * Removes the object from zone.
    -    *
    -    * @param id
    -    *            identified of the removed object
    -    * @return the removed object
    -    */
    -   public RPObject remove(RPObject.ID id) {
    -       RPObject object = objects.remove(id);
    -
    -       if (object != null) {
    -           /* We create an empty copy of the object */
    -           RPObject deleted = new RPObject();
    -           deleted.setID(object.getID());
    -           deleted.setRPClass(object.getRPClass());
    -
    -           perception.removed(deleted);
    -       }
    -
    -       return object;
    -   }
    -
    -   /**
    -    * Hide an object from the perceptions, but it doesn't remove it from world.
    -    * Any further calls to modify will be ignored.
    -    *
    -    * @param object
    -    *            the object to hide.
    -    */
    -   public void hide(RPObject object) {
    -       object.hide();
    -
    -       /* We create an empty copy of the object */
    -       RPObject deleted = new RPObject();
    -       deleted.setID(object.getID());
    -       deleted.setRPClass(object.getRPClass());
    -
    -       perception.removed(deleted);
    -   }
    -
    -   /**
    -    * Makes a hidden object to be visible again. It will appear on the
    -    * perception as an added object.
    -    *
    -    * @param object
    -    *            the object to unhide.
    -    */
    -   public void unhide(RPObject object) {
    -       object.unhide();
    -
    -       object.resetAddedAndDeleted();
    -       perception.added(object);
    -   }
    -
    -   /**
    -    * Returns the object which id is id.
    -    *
    -    * @param id
    -    *            identified of the removed object
    -    * @return the object
    -    */
    -   public RPObject get(RPObject.ID id) {
    -       return objects.get(id);
    -   }
    -
    -   /**
    -    * Returns true if the zone has that object.
    -    *
    -    * @param id
    -    *            identified of the removed object
    -    * @return true if object exists.
    -    */
    -   public boolean has(RPObject.ID id) {
    -       return objects.containsKey(id);
    -   }
    -
    -   /**
    -    * This method assigns a valid id to the object.
    -    *
    -    * @param object
    -    *            the object that is going to obtain a new id
    -    */
    -   public void assignRPObjectID(RPObject object) {
    -       RPObject.ID id = new RPObject.ID(++lastNonPermanentIdAssigned, zoneid);
    -       while (has(id)) {
    -           id = new RPObject.ID(++lastNonPermanentIdAssigned, zoneid);
    -       }
    -
    -       object.put("id", id.getObjectID());
    -       object.put("zoneid", zoneid.getID());
    -   }
    -
    -   /**
    -    * Iterates over all the objects in the zone.
    -    *
    -    * @return an iterator
    -    */
    -   public Iterator<RPObject> iterator() {
    -       return objects.values().iterator();
    -   }
    -
    -   /**
    -    * Returns the perception of given type for that object.
    -    *
    -    * @param player
    -    *            object whose perception we are going to build
    -    * @param type
    -    *            the type of perception:
    -    *            <ul>
    -    *            <li>SYNC
    -    *            <li>DELTA
    -    *            </ul>
    -    */
    -   public Perception getPerception(RPObject player, byte type) {
    -       if (type == Perception.DELTA) {
    -           if (prebuildDeltaPerception == null) {
    -               prebuildDeltaPerception = perception;
    -
    -               for (RPObject modified_obj : modified) {
    -                   if (modified_obj.isHidden()) {
    -                       continue;
    -                   }
    -                   try {
    -                       if (logger.isDebugEnabled()) {
    -                           if(!has(modified_obj.getID())) {
    -                               logger.debug("Modifying a non existing object: "+modified_obj);
    -                           }
    -                       }
    -                       
    -                       prebuildDeltaPerception.modified(modified_obj);
    -                   } catch (Exception e) {
    -                       logger.error("cannot add object to modified list (object is: ["
    -                               + modified_obj + "])", e);
    -                   }
    -               }
    -           }
    -
    -           return prebuildDeltaPerception;
    -       } else /* type==Perception.SYNC */{
    -           if (prebuildSyncPerception == null) {
    -               prebuildSyncPerception = new Perception(Perception.SYNC, getID());
    -               prebuildSyncPerception.addedList = new ArrayList<RPObject>(objects.size());
    -               for (RPObject obj : objects.values()) {
    -                   if (!obj.isHidden()) {
    -                       prebuildSyncPerception.addedList.add(obj);
    -                   }
    -               }
    -           }
    -
    -           return prebuildSyncPerception;
    -       }
    -   }
    -
    -   /**
    -    * This methods resets the delta^2 information of objects.
    -    */
    -   protected void reset() {
    -       /*
    -        * We only reset the objects that have been modified because the rest should have been modified.
    -        */
    -       for (RPObject object : modified) {
    -           object.resetAddedAndDeleted();
    -       }
    -   }
    -
    -   /**
    -    * This method returns the amount of objects in the zone.
    -    *
    -    * @return amount of objects.
    -    */
    -   public long size() {
    -       return objects.size();
    -   }
    -
    -   /**
    -    * This method prints the whole zone. Handle it with care.
    -    *
    -    * @param out
    -    *            the PrintStream where zone is printed.
    -    */
    -   public void print(PrintStream out) {
    -       for (RPObject object : objects.values()) {
    -           out.println(object);
    -       }
    -   }
    -
    -   /**
    -    * This method moves zone from this turn to the next turn. It is called by
    -    * RPWorld.
    -    */
    -   public void nextTurn() {
    -       reset();
    -
    -       prebuildSyncPerception = null;
    -       prebuildDeltaPerception = null;
    -
    -       modified.clear();
    -       perception.clear();
    -   }
    -}
    +/* $Id: MarauroaRPZone.java,v 1.39 2010/12/20 22:51:47 nhnb Exp $ */
    +/***************************************************************************
    + *                      (C) Copyright 2003 - Marauroa                      *
    + ***************************************************************************
    + ***************************************************************************
    + *                                                                         *
    + *   This program is free software; you can redistribute it and/or modify  *
    + *   it under the terms of the GNU General Public License as published by  *
    + *   the Free Software Foundation; either version 2 of the License, or     *
    + *   (at your option) any later version.                                   *
    + *                                                                         *
    + ***************************************************************************/
    +package marauroa.server.game.rp;
    +
    +import java.io.PrintStream;
    +import java.util.*;
    +import marauroa.common.Log4J;
    +import marauroa.common.game.IRPZone;
    +import marauroa.common.game.Perception;
    +import marauroa.common.game.RPObject;
    +import marauroa.common.game.RPObjectInvalidException;
    +import marauroa.server.db.command.DBCommandQueue;
    +import marauroa.server.game.db.DAORegister;
    +import marauroa.server.game.db.RPZoneDAO;
    +import marauroa.server.game.dbcommand.StoreZoneCommand;
    +
    +/**
    + * Default implementation of <code>IRPZone</code>. This class implements the
    + * Delta^2 algorithm to save bandwidth.
    + * <p>
    + * The idea behind the DPA is to avoid sending ALL the objects to a client each
    + * time, but only those that have been modified. Imagine that we have 1000
    + * objects, and only Object 1 and Object 505 are active objects that are
    + * modified each turn.
    + *
    + * <pre>
    + *   The Traditional method:
    + *
    + *   - Get objects that our player should see ( 1000 objects )
    + *   - Send them to player ( 1000 objects )
    + *   - Next turn
    + *   - Get objects that our player should see ( 1000 objects )
    + *   - Send them to player
    + *   - Next turn
    + *   ...
    + * </pre>
    + *
    + * I hope you see the problem... we are sending objects that haven't changed
    + * each turn.
    + *
    + * <pre>
    + *   The delta perception algorithm:
    + *
    + *   - Get objects that our player should see ( 1000 objects )
    + *   - Reduce the list to the modified ones ( 1000 objects )
    + *   - Store also the objects that are not longer visible ( 0 objects )
    + *   - Send them to player ( 1000 objects )
    + *   - Next turn
    + *   - Get objects that our player should see ( 1000 objects )
    + *   - Reduce the list to the modified ones ( 2 objects )
    + *   - Store also the objects that are not longer visible ( 0 objects )
    + *   - Send them to player ( 2 objects )
    + *   - Next turn
    + *   ...
    + * </pre>
    + *
    + * The next step of the delta perception algorithm is pretty clear: delta2<br>
    + * The idea is to send only what changes of the objects that changed. This way
    + * we save even more bandwidth, making perceptions around 20% of the original
    + * delta perception size.
    + * <p>
    + * The delta2 algorithm is based on four containers:
    + * <ul>
    + * <li>List of added objects
    + * <li>List of modified added attributes of objects
    + * <li>List of modified deleted attributes of objects
    + * <li>List of deleted objects
    + * </ul>
    + * To make perceptions work, it is important to call the modify method in
    + * RPZone, so this way objects modified are stored in the modified list.
    + *
    + * @author miguel
    + */
    +public class MarauroaRPZone implements IRPZone {
    +
    +   /** the logger instance. */
    +   private static final marauroa.common.Logger logger = Log4J.getLogger(MarauroaRPZone.class);
    +
    +   /** Name of the zone */
    +   protected ID zoneid;
    +
    +   /** Objects contained by the zone indexed by its id. */
    +   protected Map<RPObject.ID, RPObject> objects;
    +
    +   /**
    +    * Objects that has been modified on zone since last turn. This information
    +    * is useful for Delta^2 algorithm.
    +    */
    +   private Set<RPObject> modified;
    +
    +   /** This is the perception for the current turn. */
    +   private Perception perception;
    +
    +   /** This is a cache for the perception for this turn. */
    +   private Perception prebuildDeltaPerception = null;
    +
    +   /** This is a sync perception cache */
    +   private Perception prebuildSyncPerception = null;
    +
    +   /** This variable stores the last assigned id, that is unique per zone. */
    +   private int lastNonPermanentIdAssigned = 0;
    +
    +   private static Random rand = new Random();
    +   
    +   protected IRPZone parent;
    +
    +   /**
    +    * Creates a new MarauroaRPZone
    +    *
    +    * @param zoneid name of zone
    +    */
    +   
    +   public MarauroaRPZone(String zoneid) {
    +       this(zoneid, null);
    +   }
    +   
    +   public MarauroaRPZone(String zoneid, MarauroaRPZone parent) {
    +       this.parent = parent;
    +       this.zoneid = new ID(zoneid);
    +       rand.setSeed(new Date().getTime());
    +
    +       objects = new LinkedHashMap<RPObject.ID, RPObject>();
    +       modified = new HashSet<RPObject>();
    +
    +                perception = new Perception(Perception.DELTA, this.zoneid);
    +   }
    +
    +   /** Returns the zoneid */
    +   @Override
    +   public ID getID() {
    +       return zoneid;
    +   }
    +
    +   @Override
    +   public void onFinish() throws Exception {
    +       storeToDatabase();
    +   }
    +
    +   /**
    +    * Store objects that has been tagged as storable to database asynchronously.
    +    * Note: This methods returns before the saving is completed.
    +    */
    +   public void storeToDatabase() {
    +       List<RPObject> list = new LinkedList<RPObject>();
    +       for (RPObject object : objects.values()) {
    +           list.add((RPObject) object.clone());
    +       }
    +       DBCommandQueue.get().enqueue(new StoreZoneCommand(this, list));
    +   }
    +
    +
    +   /**
    +    * Load objects in database for this zone that were stored 
    +    * and waits for the database operation to complete.
    +    */
    +   @Override
    +   public void onInit() throws Exception {
    +       DAORegister.get().get(RPZoneDAO.class).loadRPZone(this);
    +   }
    +
    +   /**
    +    * This method adds an object to this zone.
    +    *
    +    * @param object
    +    *            object to add.
    +    * @throws RPObjectInvalidException
    +    *             if it lacks of mandatory attributes.
    +    */
    +   @Override
    +   public void add(RPObject object) throws RPObjectInvalidException {
    +       try {
    +           RPObject.ID id = object.getID();
    +
    +           object.resetAddedAndDeleted();
    +           objects.put(id, object);
    +
    +           if (!object.isHidden()) {
    +               perception.added(object);
    +           }
    +       } catch (Exception e) {
    +           throw new RPObjectInvalidException(e);
    +       }
    +   }
    +
    +   /**
    +    * This method notify zone that the object has been modified. You should
    +    * call it only once per turn, even if inside the turn you modify it several
    +    * times.
    +    *
    +    * @param object
    +    *            object to modify.
    +    * @throws RPObjectInvalidException
    +    *             if it lacks of mandatory attributes.
    +    */
    +   @Override
    +   public void modify(RPObject object) throws RPObjectInvalidException {
    +       try {
    +           modified.add(object);
    +       } catch (Exception e) {
    +           throw new RPObjectInvalidException(e.getMessage());
    +       }
    +   }
    +
    +   /**
    +    * Removes the object from zone.
    +    *
    +    * @param id
    +    *            identified of the removed object
    +    * @return the removed object
    +    */
    +   @Override
    +   public RPObject remove(RPObject.ID id) {
    +       RPObject object = objects.remove(id);
    +
    +       if (object != null) {
    +           /* We create an empty copy of the object */
    +           RPObject deleted = new RPObject();
    +           deleted.setID(object.getID());
    +           deleted.setRPClass(object.getRPClass());
    +
    +           perception.removed(deleted);
    +       }
    +
    +       return object;
    +   }
    +
    +   /**
    +    * Hide an object from the perceptions, but it doesn't remove it from world.
    +    * Any further calls to modify will be ignored.
    +    *
    +    * @param object
    +    *            the object to hide.
    +    */
    +   @Override
    +   public void hide(RPObject object) {
    +       object.hide();
    +
    +       /* We create an empty copy of the object */
    +       RPObject deleted = new RPObject();
    +       deleted.setID(object.getID());
    +       deleted.setRPClass(object.getRPClass());
    +
    +       perception.removed(deleted);
    +   }
    +
    +   /**
    +    * Makes a hidden object to be visible again. It will appear on the
    +    * perception as an added object.
    +    *
    +    * @param object
    +    *            the object to unhide.
    +    */
    +   @Override
    +   public void unhide(RPObject object) {
    +       object.unhide();
    +
    +       object.resetAddedAndDeleted();
    +       perception.added(object);
    +   }
    +
    +   /**
    +    * Returns the object which id is id.
    +    *
    +    * @param id
    +    *            identified of the removed object
    +    * @return the object
    +    */
    +   @Override
    +   public RPObject get(RPObject.ID id) {
    +       return objects.get(id);
    +   }
    +
    +   /**
    +    * Returns true if the zone has that object.
    +    *
    +    * @param id
    +    *            identified of the removed object
    +    * @return true if object exists.
    +    */
    +   @Override
    +   public boolean has(RPObject.ID id) {
    +       return objects.containsKey(id);
    +   }
    +
    +   /**
    +    * This method assigns a valid id to the object.
    +    *
    +    * @param object
    +    *            the object that is going to obtain a new id
    +    */
    +   @Override
    +   public void assignRPObjectID(RPObject object) {
    +       RPObject.ID id = new RPObject.ID(++lastNonPermanentIdAssigned, zoneid);
    +       while (has(id)) {
    +           id = new RPObject.ID(++lastNonPermanentIdAssigned, zoneid);
    +       }
    +
    +       object.put("id", id.getObjectID());
    +       object.put("zoneid", zoneid.getID());
    +   }
    +
    +   /**
    +    * Iterates over all the objects in the zone.
    +    *
    +    * @return an iterator
    +    */
    +   @Override
    +   public Iterator<RPObject> iterator() {
    +       return objects.values().iterator();
    +   }
    +        
    +   /**
    +    * Returns the perception of given type for that object.
    +    *
    +    * @param player
    +    *            object whose perception we are going to build
    +    * @param type
    +    *            the type of perception:
    +    *            <ul>
    +    *            <li>SYNC
    +    *            <li>DELTA
    +    *            </ul>
    +    */
    +   @Override
    +   public Perception getPerception(RPObject player, byte type) {
    +            logger.info("Adding changes from " + getID());
    +       if (type == Perception.DELTA) {
    +           if (prebuildDeltaPerception == null) {
    +                            prebuildDeltaPerception = perception;
    +
    +               for (RPObject modified_obj : modified) {
    +                   if (modified_obj.isHidden()) {
    +                       continue;
    +                        }
    +                   try {
    +                       if (logger.isDebugEnabled()) {
    +                           if(!has(modified_obj.getID())) {
    +                               logger.debug("Modifying a non existing object: "+modified_obj);
    +                           }
    +                       }
    +                                
    +                       prebuildDeltaPerception.modified(modified_obj);
    +                   } catch (Exception e) {
    +                       logger.error("cannot add object to modified list (object is: ["
    +                               + modified_obj + "])", e);
    +                            }
    +                        }
    +           }
    +                    //Add the changes in the hierarchy. This is recursive.
    +                    if (getParent() != null) {
    +                        Perception parentPerception = getParent().getPerception(player, type);
    +                        for (RPObject modified_obj : parentPerception.modifiedAddedList) {
    +                            if (modified_obj.isHidden()) {
    +                                continue;
    +                            }
    +                            try {
    +                                if (logger.isDebugEnabled()) {
    +                                    if (!has(modified_obj.getID())) {
    +                                        logger.debug("Modifying a non existing object: " + modified_obj);
    +                                    }
    +                                }
    +
    +                                prebuildDeltaPerception.modified(modified_obj);
    +                            } catch (Exception e) {
    +                                logger.error("cannot add object to modified list (object is: ["
    +                                        + modified_obj + "])", e);
    +                            }
    +                        }
    +                    }
    +           return prebuildDeltaPerception;
    +       } else /* type==Perception.SYNC */{
    +           if (prebuildSyncPerception == null) {
    +                                prebuildSyncPerception = new Perception(Perception.SYNC, getID());
    +               prebuildSyncPerception.addedList = new ArrayList<RPObject>(objects.size());
    +               for (RPObject obj : objects.values()) {
    +                   if (!obj.isHidden()) {
    +                       prebuildSyncPerception.addedList.add(obj);
    +                   }
    +               }
    +           }
    +
    +           return prebuildSyncPerception;
    +       }
    +   }
    +
    +   /**
    +    * This methods resets the delta^2 information of objects.
    +    */
    +   protected void reset() {
    +       /*
    +        * We only reset the objects that have been modified because the rest should have been modified.
    +        */
    +       for (RPObject object : modified) {
    +           object.resetAddedAndDeleted();
    +       }
    +   }
    +
    +   /**
    +    * This method returns the amount of objects in the zone.
    +    *
    +    * @return amount of objects.
    +    */
    +   @Override
    +   public long size() {
    +       return objects.size();
    +   }
    +
    +   /**
    +    * This method prints the whole zone. Handle it with care.
    +    *
    +    * @param out
    +    *            the PrintStream where zone is printed.
    +    */
    +   public void print(PrintStream out) {
    +       for (RPObject object : objects.values()) {
    +           out.println(object);
    +       }
    +   }
    +
    +   /**
    +    * This method moves zone from this turn to the next turn. It is called by
    +    * RPWorld.
    +    */
    +   @Override
    +   public void nextTurn() {
    +       reset();
    +
    +       prebuildSyncPerception = null;
    +       prebuildDeltaPerception = null;
    +
    +       modified.clear();
    +       perception.clear();
    +   }
    +
    +   @Override
    +   public IRPZone getParent() {
    +       return parent;
    +   }
    +
    +        /**
    +        * @param parent the parent to set
    +        */
    +    @Override
    +        public void setParent(IRPZone parent) {
    +            this.parent = parent;
    +        }
    +}
    diff --git a/src/marauroa/server/game/rp/RPWorld.java b/src/marauroa/server/game/rp/RPWorld.java
    index fd984c1..b815fc8 100644
    --- a/src/marauroa/server/game/rp/RPWorld.java
    +++ b/src/marauroa/server/game/rp/RPWorld.java
    @@ -13,9 +13,10 @@
     package marauroa.server.game.rp;
    
     import java.util.Iterator;
    +import java.util.LinkedList;
     import java.util.Map;
     import java.util.concurrent.ConcurrentHashMap;
    -
    +import marauroa.client.net.IZoneListener;
     import marauroa.common.Log4J;
     import marauroa.common.game.IRPZone;
     import marauroa.common.game.RPObject;
    @@ -52,6 +53,8 @@ public class RPWorld implements Iterable<IRPZone> {
    
        /** The all-mighty player container. */
        PlayerEntryContainer playerContainer;
    +        
    +        LinkedList<IZoneListener> zoneListeners = new LinkedList<IZoneListener>();
    
        /**
         * creates a new RPWorld. Note this class is designed as a singleton.
    @@ -86,9 +89,9 @@ public class RPWorld implements Iterable<IRPZone> {
         */
        public static RPWorld get() {
            if (instance == null) {
    -           RPWorld instance = new RPWorld();
    -           instance.initialize();
    -           RPWorld.instance = instance;
    +           RPWorld temp = new RPWorld();
    +           temp.initialize();
    +           RPWorld.instance = temp;
            }
            return instance;
        }
    @@ -120,6 +123,11 @@ public class RPWorld implements Iterable<IRPZone> {
         */
        public void addRPZone(IRPZone zone) {
            zones.put(zone.getID(), zone);
    +                
    +                for (Iterator<IZoneListener> it = zoneListeners.iterator(); it.hasNext();) {
    +                    IZoneListener listener = it.next();
    +                    listener.onZoneAdded(zone.getID());
    +                }
        }
    
        /**
    @@ -169,6 +177,11 @@ public class RPWorld implements Iterable<IRPZone> {
            if(zone!=null) {
              zone.onFinish();
            }
    +                
    +                for (Iterator<IZoneListener> it = zoneListeners.iterator(); it.hasNext();) {
    +                    IZoneListener listener = it.next();
    +                    listener.onZoneRemoved(zoneid);
    +                }
    
            return zone;
        }
    @@ -182,14 +195,7 @@ public class RPWorld implements Iterable<IRPZone> {
         * @throws Exception caused by onFinish
         */
        public IRPZone removeRPZone(RPObject.ID objectid) throws Exception {
    -       IRPZone.ID zoneid=new IRPZone.ID(objectid.getZoneID());
    -       IRPZone zone=zones.remove(zoneid);
    -       
    -       if(zone!=null) {
    -         zone.onFinish();
    -       }
    -       
    -       return zone;
    +       return removeRPZone(new IRPZone.ID(objectid.getZoneID()));
        }
    
        /**
    @@ -281,6 +287,7 @@ public class RPWorld implements Iterable<IRPZone> {
         *
         * @return iterator over zones.
         */
    +    @Override
        public Iterator<IRPZone> iterator() {
            return zones.values().iterator();
        }
    @@ -366,4 +373,20 @@ public class RPWorld implements Iterable<IRPZone> {
    
            return size;
        }
    +        
    +        /**
    +         * Add a zone listener
    +         * @param listener 
    +         */
    +        public void addZoneListener(IZoneListener listener){
    +            zoneListeners.add(listener);
    +        }
    +        
    +        /**
    +         * Remove a zone listener
    +         * @param listener 
    +         */
    +        public void removeZoneListener(IZoneListener listener){
    +            zoneListeners.remove(listener);
    +        }
     }
    

    I didn't had time to really test them with your use case but it works with the normal applications so nothing seems to be broken.

     
  • Did you take a look at my clean up? I'm not Git expert so I couldn't figure out how to export the patch I made.

     
  • mahe
    mahe
    2011-12-12

    I see the references from client to server code. Those aren't smart, thats right.

    As the current systems is so focused on having only one zone per player, do you think that it is possible to be compatible with older versions and still get these new features?

     
  • I was able to do it (see my code changes). Basically the distinction is done at runtime if the zone has a parent or not. Is a simple check. Everything is done internally.

    That wasn't the case on your code.

     
  • Hello javy,

    I am sorry, I had to give up on the patch. Git does not accept the patch because of the reformatting done by the forum. I tried to apply the patch manually and removed all the Java 5 incompatible @Overrides, but in the end it still has lots of compiler errors left.

    I tried to apply it on top of maheee changes, but perhaps we have used different versions. Or it might be against our git repository, but as far as I can tell, this would just result in a different set of compiler errors.

    Hendrik

     
  • Ah, finally got it. experimental_subzones now contains Javy's version. I removed the interface based @Overrides again and reverted the changes to RPWorld, which introduced a ZoneListener but the interface was missing.

    As far as I understand your patch, the changes are server side only.

    Do we need client support, or can the games handle it on their own?

    A small test program would be helpful. Automatic tests are always useful. But in this case, I think it is vital, because Stendhal does not use this features, so there is some risk of it to break in the future.

     
  • Glad you were able to use it. I thought my approach was cleaner but I don't have any test case. Mahee is your go to person there. I just enhanced the code to make it more appropriate but everything is in theory.

     
  • A couple of things still to be improved:
    1) Zones store parent instead of child: I left it like that so Mahee could in theory plug and play with his code but it should store the children. Better yet a List of children. The logic would need to be updated accordingly.
    2) Maybe extending MarauroaZone into a special zone with this characteristic would be better than having all bundled in the same class. Based on my changes it might not be needed, changes where minimal IMO, just a couple of if else statements with new logic if zone had parent.
    3) Maybe a new entity attribute is needed to know if an object should know about the other zones or not, just for flexibility.

     


Anonymous


Cancel   Add attachments