// SkeletonBot, a modular framework for building a JavaBot.
// Public domain.

package edu.bowdoin.gamebots;

import java.util.HashMap;
import java.util.Iterator;

import edu.isi.gamebots.client.Bot;
import edu.isi.gamebots.client.Message;
import edu.isi.gamebots.client.MessageBlock;
import edu.isi.gamebots.examples.Vector3D;

import java.util.Properties;

/**
 * SkeltonBot provides a modular framework for building a JavaBot.
 *
 * <p> What SkeletonBot does for you:</p>
 * <ul>
 * <li>Concurrently updates information about the bot's state and provides
 * <code>get_*</code> methods to retrieve this information without needing to
 * worry about concurrency.</li>
 * <li>Sends your bot's name as configured in the GUI at startup.</li>
 * </ul>
 *
 * What you need to do:
 * <ul>
 * <li>Override the <code>decider</code> method.  This method runs in the
 * background no more than once every <code>sleep_time</code> seconds and
 * is responsible for controlling the bot's behavior.</li>
 * <li>Override some of the <code>handle_*</code> methods that are called when
 * the bot receives a message of that class.  For example, to create a graph of
 * the navigation markers on the map, override the <code>handle_nav</code>
 * method with your own implementation.  No <code>handle_*</code> method must
 * be overwritten.</li>
 * </ul>
 *
 * What you should not do:
 * <ul>
 * <li>Assume all concurrency is taken care of.  Information sharing between
 * the <code>update_*</code> methods and the <code>decider</code> method
 * requires a mutex.</li>
 * </ul>
 *
 * Sample of message sending, from <code>change_weapon</code>:
<pre>
    Properties p = new Properties();
    p.setProperty(ACTOR_ID, w);
    send_msg(CHANGEWEAPON, p);
</pre>
 *
 * Sample of parsing messages:
<pre>
    String v = m.getProperty(PLAYER_HEALTH);
    try {
        health = Integer.parseInt(v);
    } catch (NumberFormatException e) {
        // Ignore
    }
</pre>
 *
 * @author Alec Berryman &lt;<a href="mailto:aberryma@bowdoin.edu">aberryma@bowdoin.edu</a>&gt;
 * @version 2007-04-16.1
 */
public class SkeletonBot extends Bot {
    // Information about the bot's self.
    private final Object self_mutex = new Object(); // mutex
    private Vector3D rotation; // heading/orientation on map
    private Vector3D location; // absolute location on map
    private Vector3D velocity; // velocity in each direction
    private int health; // bot's health, from 1-199.
    private int armor; // bot's armor, from 0-199.
    private String weapon; // current weapon
    private HashMap<String,Integer> weapons = new HashMap(); // weapon <-> ammo map

    // Background thread controlling the bot's actions.
    private Thread decision_thread;
    private Runnable decision_runner = new Runnable() {
        public void run() {
            Thread thread = Thread.currentThread();
            while(thread == decision_thread) {
                try {
                    decider();
                    thread.sleep(sleep_time);
                } catch( InterruptedException error ) {
                    // don't handle this - see disconnected()
                }
            }
        }
    };

    /**
     * Constant used to indicate that the bot does not possess the requested
     * weapon.
     *
     * @see #get_ammo(String weapon)
     */
    protected static final int NO_WEAPON = -1;

    /**
     * Suspend the decision thread's execution for this many milliseconds after
     * each decision.  By default, 500.
     *
     * @see #decision_runner
     */
    protected long sleep_time = 500l;

    /**
     * Return the bot's current rotation.
     *
     * @return Rotations in radians.
     */
    protected final Vector3D get_rotation() {
        Vector3D i;
        synchronized(self_mutex) {
            i = rotation;
        }
        return i;
    }

    /**
     * Return the bot's current location.
     *
     * @return Bot's absolute location on map.
     */
    protected final Vector3D get_location() {
        Vector3D i;
        synchronized(self_mutex) {
            i = location;
        }
        return i;
    }

    /**
     * Return the bot's current velocity.
     *
     * @return Bot's velocity.
     */
    protected final Vector3D get_velocity() {
        Vector3D i;
        synchronized(self_mutex) {
            i = velocity;
        }
        return i;
    }

    /**
     * Return the bot's current health.
     *
     * @return Health ranges from 1-199.
     */
    protected final int get_health() {
        int i;
        synchronized(self_mutex) {
            i = health;
        }
        return i;
    }

    /**
     * Return the bot's current armor.
     *
     * @return Armor ranges from 0-199.
     */
    protected final int get_armor() {
        int i;
        synchronized(self_mutex) {
            i = armor;
        }
        return i;
    }

    /**
     * Return the bot's current weapon.
     *
     * @return Weapon name (UT IT; map specific)
     */
    protected final String get_weapon() {
        String i;
        synchronized(self_mutex) {
            i = weapon;
        }
        return i;
    }

    /**
     * Return the amount of ammo for the bot's current weapon.
     *
     * @return Ammo count is 0 if the bot is out of ammo.
     */
    protected final int get_ammo() {
        Integer i;
        synchronized(self_mutex) {
            i = weapons.get(weapon);
        }
        // apparently you can't test if primitive types are equal to null?
        if (i == null) {
            // We've hit between updating the weapon type (async message) and
            // updating the ammo count (sync message), and the current weapon
            // is a new weapon so we don't yet have an ammo count for it.
            // Assume we have 1 - that's the minimum for things like the Ion
            // Painter and the Reedeemer - and call it good.
            return 1;
        }
        return i.intValue();
    }

    /** Return the amount of ammo for the given weapon.
     *
     * Note: ammo updating doesn't work very well.  The amount returned is a
     * lower bound.  The ammo count will be generally accurate for the weapon
     * you're currently holding, but if you have never held a weapon, you will
     * have no information about its ammo, and if you pick up ammo for a weapon
     * you already have but do not have equipped, you will not know about the
     * added ammo until you next equip it.
     *
     * @param weapon Weapon name (UT ID)
     * @return Ammo count is 0 if the bot is out of ammo for the weapon,
     * NO_WEAPON if the bot does not posess the weapon.
     *
     */
    protected final int get_ammo(String weapon) {
        Integer i;
        synchronized(self_mutex) {
            i = weapons.get(weapon);
        }
        if (i == null) {
            return NO_WEAPON;
        }
        return i.intValue();
    }

    /**
     * Control the bot's actions; run from a background thread no more than
     * once every <code>sleep_time</code>.
     *
     * @see #sleep_time
     */
    protected void decider() { }


    /**
     * Called after the bot establishes a connection to the server.  Starts the
     * decision thread in the background.
     */
    protected void connected() {
        decision_thread = new Thread(decision_runner);
        decision_thread.start();
    }


    /**
     * Called after the bot disconnects from the server.  Stops the decision
     * thread running in the background.
     */
    protected void disconnected() {
        // Depending on when this method is called, the decision thread could
        // sleep for some time (see variable sleep_time).  Interrupting the
        // thread will interrupt the sleeping and have the thread finish more
        // quickly.
        if (decision_thread != null) {
            Thread old = decision_thread;
            decision_thread = null;
            old.interrupt();
        }
    }

    /**
     * Receives and parses asynchronous messages (like "you ran into a wall" or
     * "there's a missle coming"). This method calls the corresponding handle_*
     * method for the following message types:
     * <ul>
     * <li>AIN</li>
     * <li>CWP</li>
     * <li>WAL</li>
     * <li>FAL</li>
     * <li>BMP</li>
     * <li>PRJ</li>
     * <li>KIL</li>
     * <li>DAM</li>
     * <li>HIT</li>
     * <li>PTH</li>
     * <li>RCH</li>
     * <li>DIE</li>
     * <li>NFO</li>
     * </ul>
     *
     * @param m A single message.
     * @see #handle_ain
     * @see #handle_cwp
     * @see #handle_wal
     * @see #handle_fal
     * @see #handle_bmp
     * @see #handle_prj
     * @see #handle_kil
     * @see #handle_dam
     * @see #handle_hit
     * @see #handle_pth
     * @see #handle_rch
     * @see #handle_die
     * @see #handle_nfo
     */
    protected void receivedAsyncMessage(Message m) {
        String t = m.getType();

        // Some of the message types are hardcoded because GamebotsConstants
        // doesn't provide suitable constants.  Rather then add them there and
        // run into trouble when others use this, just hardcode them.
        if (t.equals(ITEM)) {
            // We got a new item!
            handle_ain(m);
        } else if (t.equals("CWP")) {
            // We switched weapons.  Update the bot's current weapon here
            // instead of in the synchronous message loop for efficiency.
            update_cwp(m);
            handle_cwp(m);
        } else if (t.equals(WALL)) {
            // We ran into the wall.
            handle_wal(m);
        } else if (t.equals("FAL")) {
            // We fell.
            handle_fal(m);
        } else if (t.equals(BUMP)) {
            // We hit something that's not a wall.
            handle_bmp(m);
        } else if (t.equals("PRJ")) {
            // Incoming projectile.
            handle_prj(m);
        } else if (t.equals(KILL)) {
            // Someone else died.
            handle_kil(m);
        } else if (t.equals(DAMAGE)) {
            // We took damage.
            handle_dam(m);
        } else if (t.equals("HIT")) {
            // We hit another player.
            handle_hit(m);
        } else if (t.equals(PATH)) {
            // We got a response to our GETPATH query.
            handle_pth(m);
        } else if (t.equals("RCH")) {
            // We got a response to our CHECKREACH query.
            handle_rch(m);
        } else if (t.equals(DIE)) {
            // We died.  I put this one near the end, as if it's unlikely to
            // happen often.
            update_die(m); // reset relevant data structures
            handle_die(m);
        } else if (t.equals(INFO)) {
            // Beginning of the game NFO message.
            update_nfo(m);
            handle_nfo(m);
        }
    }

    /**
     * Receives and parses synchronous messages (like "at time 42.123242, the
     * state of the world is..." or "here are the navigation markers you can
     * see at time 31.4923857").  This method calls the corresponding handle_*
     * method for the following message types:
     * <ul>
     * <li>SLF</li>
     * <li>GAM</li
     * <li>PLR</li>
     * <li>NAV</li>
     * <li>INV</li>
     * <li>MOV</li>
     * <li>FLG</li>
     * <li>DOM</li>
     * </ul>
     *
     * @param ms A set of messages with a common timestamp.
     * @see #handle_slf
     * @see #handle_gam
     * @see #handle_plr
     * @see #handle_nav
     * @see #handle_inv
     * @see #handle_mov
     * @see #handle_flg
     * @see #handle_dom
     */
    protected void receivedSyncMessage(MessageBlock ms) {
        Iterator<Message> i = ms.getMessages();

        // Some of the message types are hardcoded because GamebotsConstants
        // doesn't provide suitable constants.  Rather then add them there and
        // run into trouble when others use this, just hardcode them.
        while (i.hasNext()) {
            Message m = i.next();
            String t = m.getType();

            if (t.equals(SELF)) {
                update_slf(m);
                handle_slf(m);
            } else if (t.equals(GAMESTATE)) {
                handle_gam(m);
            } else if (t.equals(PLAYER)) {
                handle_plr(m);
            } else if (t.equals(NAV)) {
                handle_nav(m);
            } else if (t.equals(INV)) {
                handle_inv(m);
            } else if (t.equals(MOV)) {
                handle_mov(m);
            } else if (t.equals("FLG")) {
                handle_flg(m);
            } else if (t.equals(DOM)) {
                handle_dom(m);
            }
        }
    }

    /**
     * Updates the several data structures that represent the bot's current
     * condition.
     *
     * @param m SLF message.
     */
    private void update_slf(Message m) {
        String v; // temporarily holds message value
        double[] l; // temporarily holds locations

        // Extract all the properties we're interested in.
        synchronized(self_mutex) {
            // Our heading
            v = m.getProperty(ROTATION);
            if (v != null) {
                try {
                    // A full rotation in UT is 65535 degrees.  That's less than
                    // helpful; convert to radians.
                    l = parseVector(v);
                    rotation = new Vector3D(ut_to_radians(l[0]),
                            ut_to_radians(l[1]),
                            ut_to_radians(l[2]));
                } catch (NumberFormatException e) {
                    // Ignore
                }
            }

            // Our location
            v =  m.getProperty(LOCATION);
            if (v != null) {
                try {
                    l = parseVector(v);
                    location = new Vector3D(l[0], l[1], l[2]);
                } catch (NumberFormatException e) {
                    // Ignore
                }
            }

            // Our velocity
            v = m.getProperty(VELOCITY);
            if (v != null) {
                try {
                    l = parseVector(v);
                    velocity = new Vector3D(l[0], l[1], l[2]);
                } catch (NumberFormatException e) {
                    // Ignore
                }
            }

            // Name doesn't matter; don't check it.

            // Our health.  We'll get asynchronous messages about taking
            // damage, but it doesn't specify how much the armor took (if any)
            // as opposed to the health.
            v = m.getProperty(PLAYER_HEALTH);
            if (v != null) {
                try {
                    health = Integer.parseInt(v);
                } catch (NumberFormatException e) {
                    // Ignore
                }
            }

            // Our weapon.
            weapon = m.getProperty("Weapon");

            // Our ammo.
            v = m.getProperty("CurrentAmmo");
            if (v != null && weapon != null) {
                try {
                    Integer ammo = Integer.decode(v);
                    weapons.put(weapon, ammo);
                } catch (NumberFormatException e) {
                    // Ignore
                }
            }

            // Our armor.  We'll get asynchronous messages about taking damage,
            // but those don't specify how much the armor took.
            v = m.getProperty(PLAYER_ARMOR);
            if (v != null) {
                try {
                    armor = Integer.parseInt(v);
                } catch (NumberFormatException e) {
                    // Ignore
                }
            }
        }
    }

    /**
     * Handle SLF messages containing information about the bot.  The data
     * structures containing information about the bot's current condition have
     * already been updated when this method is called.
     *
     * @param m SLF message.
     */
    protected void handle_slf(Message m) { }

    /**
     * Handle GAM messages containing information about the game,
     *
     * @param m GAM message.
     */
    protected void handle_gam(Message m) { }

    /**
     * Handle PLR messages containing information about another player.
     *
     * @param m PLR message.
     */
    protected void handle_plr(Message m) { }

    /**
     * Handle NAV messages containing information about a navigation point.
     *
     * @param m NAV message.
     */
    protected void handle_nav(Message m) { }

    /**
     * Handle INV messages containing information about an item on the ground
     * that the bot can pick up.
     *
     * @param m INV message.
     */
    protected void handle_inv(Message m) { }

    /**
     * Handle MOV messages containing information about a "mover" such as an
     * elevator or door.
     *
     * @param m MOV message.
     */
    protected void handle_mov(Message m) { }

    /**
     * Handle FLG messages containing information about a flag.  This method
     * will only be called in CTF games.
     *
     * @param m FLG message.
     */
    protected void handle_flg(Message m) { }

    /**
     * Handle DOM messages containing information about a domination point.
     * This method will only be called in domination games.
     *
     * @param m DOM message.
     */
    protected void handle_dom(Message m) { }

    /**
     * Handle AIN messages containing information about a newly-acquired item.
     *
     * @param m AIN message.
     */
    protected void handle_ain(Message m) { }

    /**
     * Update the bot's current weapon.  This update is not done in the
     * synchronous message loop for efficiency reasons.
     *
     * @param m CWP message.
     */
    private void update_cwp(Message m) {
        synchronized(self_mutex) {
            weapon =  m.getProperty(ACTOR_CLASS);
            // No need to see if the new weapon needs to be added to weapons;
            // it'll get picked up in the next SLF update.
        }
    }

    /**
     * Handle CWP messages notifying the bot that it has changed weapons,
     * possibly due to user request or because the weapon has run out of ammo.
     *
     * @param m CWP message.
     */
    protected void handle_cwp(Message m) { }

    /**
     * Handle WAL messages notifying the bot that it has run into a wall.
     *
     * @param m WAL message.
     * @see #handle_bmp
     */
    protected void handle_wal(Message m) { }

    /**
     * Handle FAL messages notifying the bot that it is falling.
     *
     * @param m FAL message.
     */
    protected void handle_fal(Message m) { }

    /**
     * Handle BMP messages notifying the bot that it has run into another
     * actor, most likely another player.
     *
     * @param m BMP message.
     * @see #handle_wal
     */
    protected void handle_bmp(Message m) { }

    /**
     * Handle PRJ messages notifying the bot of an incoming projectile, like a
     * rocket.
     *
     * @param m PRJ message.
     */
    protected void handle_prj(Message m) { }

    /**
     * Handle KIL messages notifying the bot that another player has died.
     *
     * @param m KIL message.
     */
    protected void handle_kil(Message m) { }

    /**
     * Handle DAM messages notifying the bot that it has taken damage.
     *
     * @param m DAM message.
     */
    protected void handle_dam(Message m) { }

    /**
     * Handle HIT messages notifying the bot that it has shot another player.
     *
     * @param m HIT message.
     */
    protected void handle_hit(Message m) { }

    /**
     * Handle PTH messages containing responses to GETPATH queries.
     *
     * @param m PTH message.
     */
    protected void handle_pth(Message m) { }

    /**
     * Handle RCH messages containing responses to CHECKREACH queries.
     *
     * @param m RCH message.
     */
    protected void handle_rch(Message m) { }

    /**
     * Reset relevant data structures on bot death.
     *
     * @param m DIE message.
     */
    private void update_die(Message m) {
        synchronized(self_mutex) {
            weapon = null;
            weapons.clear();
        }
    }

    /**
     * Handle DIE messages notifying the bot that it has died.
     *
     * @param m DIE message.
     */
    protected void handle_die(Message m) { }

    /**
     * Send the bot's name back to the server to complete the initial
     * handshake.
     *
     * @param m NFO message.
     */
    protected void update_nfo(Message m) {
        Properties p = new Properties();
        p.setProperty(PLAYER_NAME, getName()); // name set in GUI
        send_msg(INIT, p);
    }

    /**
     * Handle initial NFO messages containing the initial game state.  When
     * this method is called, the initial INIT handshake has been completed.
     *
     * @param m NFO message.
     */
    protected void handle_nfo(Message m) { }

    /**
     * Convert UT degrees to radians.  65535 UT degrees is 2Pi.
     *
     * @param ut Value in UT degrees.
     * @return Value in radians.
     */
    public static final double ut_to_radians(double ut) {
        return ut / 65535 * 2 * Math.PI;
    }

    /**
     * Convert radians to UT degrees.  65535 UT degrees is 2Pi.
     *
     * @param radians Value in radians.
     * @return Value in UT degrees.
     */
    public static final double radians_to_ut(double radians) {
        return radians * 65535 / 2 / Math.PI;
    }

    /**
     * Send a message to the server.
     *
     * @param type Message type.
     * @param payload Message contents; may be null.
     * @throws IllegalArgumentException Type may not be null or the empty string.
     */
    protected final void send_msg(String type, Properties payload) {
        // mutex in GamebotsClient
        client.sendMessage(type, payload);
    }

    /**
     * Stop movement.
     */
    protected final void send_stop() {
        send_msg(STOP, null);
    }

    /**
     * Jump.
     */
    protected final void send_jump() {
        send_msg(JUMP, null);
    }

    /**
     * Turn towards and run to the specified destination.  If the bot is unable
     * to directly reach the target, it will go as far as it can (until it runs
     * into a wall or falls into lava).
     *
     * @param t UT target ID
     */
    protected final void send_runto(String t) {
        Properties p = new Properties();
        p.setProperty(ARG_TARGET, t);
        send_msg(RUNTO, p);
    }

    /**
     * Turn towards and run to the specified destination.  If the bot is unable
     * to directly reach the target, it will go as far as it can (until it runs
     * into a wall or falls into lava).
     *
     * @param l Absolute location of target
     */
    protected final void send_runto(Vector3D l) {
        Properties p = new Properties();
        p.setProperty(LOCATION, l.x + " " + l.y + " " + l.z );
        send_msg(RUNTO, p);
    }

    /**
     * Strafe towards a destination while focusing on another.
     *
     * Focusing on a point is reportedly broken in UT2k3 and UT2k4.
     *
     * @param d Destination point.
     * @param f Focus point.
     */
    protected final void send_strafe(Vector3D d, Vector3D f) {
        Properties p = new Properties();
        p.setProperty(LOCATION, d.x + " " + d.y + " " + d.z );
        p.setProperty("Focus", f.x + " " + f.y + " " + f.z );
        send_msg(STRAFE, p);
    }

    /**
     * Strafe towards a destination while facing a target.  The target must be
     * visible to the bot.
     *
     * @param d Destination point.
     * @param t Target's UT ID.
     */
    protected final void send_strafe(Vector3D d, String t) {
        Properties p = new Properties();
        p.setProperty(LOCATION, d.x + " " + d.y + " " + d.z);
        p.setProperty(ARG_TARGET, t);
        send_msg(STRAFE, p);
    }

    /**
     * Turn the bot to face the specified target.  The target must be be
     * visible to the bot.
     *
     * @param t Target's UT IT
     */
    protected final void send_turnto(String t) {
        Properties p = new Properties();
        p.setProperty(ARG_TARGET, t);
        send_msg(TURNTO, p);
    }

    /**
     * Turn the bot to face the specified location.
     *
     * @param l Absolute location.
     */
    protected final void send_turnto(Vector3D l) {
        Properties p = new Properties();
        p.setProperty(ROTATION, l.y + " " + l.z + " " + l.x ); // pitch/yaw/roll
        send_msg(TURNTO, p);
    }

    /**
     * Rotate the bot the specified amount relative to its current position
     * about the z axis.
     *
     * @param z Rotation amount about the z axis (yaw) in radians.
     */
    protected final void send_rotate_z(double z) {
        Properties p = new Properties();
        p.setProperty(ARG_AMOUNT, Double.toString(radians_to_ut(z)));
        send_msg(ROTATE, p);
    }

    /**
     * Shoot the specified target.  The target must be visible.  The server
     * will provide limited auto-aim and leading.
     *
     * @param t Target's UT ID.
     */
    protected final void send_shoot(String t) {
        send_shoot(t, false);
    }

    /**
     * Shoot the specified target.  The target must be visible.  The server
     * will provide limited auto-aim and leading.
     *
     * @param t Target's UT ID.
     * @param a If false, the default firing mode is used; if true, the
     * alternate.
     */
    protected final void send_shoot(String t, boolean a) {
        Properties p = new Properties();
        p.setProperty(ARG_TARGET, t);
        if (a) {
            p.setProperty(ARG_ALT, TRUE);
        } else {
            p.setProperty(ARG_ALT, FALSE);
        }
        send_msg(SHOOT, p);
    }

    /**
     * Shoot at a specified location.
     *
     * @param l Absolute location.
     */
    protected final void send_shoot(Vector3D l) {
        send_shoot(l, false);
    }

    /**
     * Shoot at a specified location.
     *
     * @param l Absolute location.
     * @param a If false, the default firing mode is used; if true, the
     * alternate.
     */
    protected final void send_shoot(Vector3D l, boolean a) {
        Properties p = new Properties();
        p.setProperty(LOCATION, l.x + " " + l.y + " " + l.z);
        if (a) {
            p.setProperty(ARG_ALT, TRUE);
        } else {
            p.setProperty(ARG_ALT, FALSE);
        }
        send_msg(SHOOT, p);
    }

    /**
     * Switch to the specified weapon.  If the weapon is already equipped, or
     * if the agent does not possess the weapon, this method will do nothing.
     *
     * @param w Desired weapon
     */
    protected final void send_change_weapon(String w) {
        Properties p = new Properties();
        p.setProperty(ACTOR_ID, w);
        send_msg(CHANGEWEAPON, p);
    }

    /**
     * Switch to the best weapon the bot has, as decided by the server.  This
     * weapon will have ammo.
     */
    protected final void send_change_best_weapon() {
        Properties p = new Properties();
        p.setProperty(ACTOR_ID, "Best");
        send_msg(CHANGEWEAPON, p);
    }

    /**
     * Stop shooting.
     */
    protected final void send_stop_shoot() {
        send_msg(STOP_SHOOT, null);
    }

    /**
     * Check to see if the bot can move directly from its current location to
     * the specified one.
     *
     * @param id Unique identification number echoed in RCH messages.
     * @param d Absolute target location.
     */
    protected final void send_checkreach(String id, Vector3D d) {
        Properties p = new Properties();
        p.setProperty("Id", id);
        p.setProperty(LOCATION, d.x + " " + d.y + " " + d.z);
        send_msg(CHECKREACH, p);
    }

     /**
     * Request a path via navigation nodes to the specified location.
     *
     * @param id Unique identification number to be echoed in PTH messages.
     * @param d Absolute location of the destination.
     */
    protected final void send_getpath(String id, Vector3D d) {
       Properties p = new Properties();
        p.setProperty("Id", id);
        p.setProperty(LOCATION, d.x + " " + d.y + " " + d.z);
        send_msg(GETPATH, p);
    }

    /**
     * Send a message to all players.
     *
     * @param m Message to be sent to all players.
     */
    protected final void send_message_all(String m) {
        Properties p = new Properties();
        p.setProperty(ARG_GLOBAL, TRUE);
        p.setProperty(ARG_TEXT, m);
        send_msg(MESSAGE, p);
    }

    /**
     * Send a message to teammates.
     *
     * @param m Message to be sent to teammates.
     */
    protected final void send_message_team(String m) {
        Properties p = new Properties();
        p.setProperty(ARG_GLOBAL, FALSE);
        p.setProperty(ARG_TEXT, m);
        send_msg(MESSAGE, p);
    }
}

