summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog20
-rw-r--r--data/graphics/gui/Makefile.am1
-rw-r--r--data/graphics/gui/emotions.pngbin0 -> 19386 bytes
-rw-r--r--data/help/commands.txt53
-rw-r--r--src/Makefile.am10
-rw-r--r--src/being.cpp2
-rw-r--r--src/being.h4
-rw-r--r--src/beingmanager.cpp15
-rw-r--r--src/beingmanager.h5
-rw-r--r--src/configuration.cpp187
-rw-r--r--src/configuration.h147
-rw-r--r--src/game.cpp15
-rw-r--r--src/gui/popup_box.cpp139
-rw-r--r--src/gui/popup_box.h71
-rw-r--r--src/gui/popupmenu.cpp52
-rw-r--r--src/gui/setup.cpp10
-rw-r--r--src/gui/setup.h2
-rw-r--r--src/gui/setup_players.cpp342
-rw-r--r--src/gui/setup_players.h70
-rw-r--r--src/gui/table.cpp419
-rw-r--r--src/gui/table.h150
-rw-r--r--src/gui/table_model.cpp149
-rw-r--r--src/gui/table_model.h137
-rw-r--r--src/main.cpp4
-rw-r--r--src/net/beinghandler.cpp6
-rw-r--r--src/net/chathandler.cpp43
-rw-r--r--src/net/tradehandler.cpp15
-rw-r--r--src/net/tradehandler.h18
-rw-r--r--src/player.cpp31
-rw-r--r--src/player.h28
-rw-r--r--src/player_relations.cpp406
-rw-r--r--src/player_relations.h226
32 files changed, 2656 insertions, 121 deletions
diff --git a/ChangeLog b/ChangeLog
index 23fbf347..5fdb0fc8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+2008-05-14 fate <fate.tmw@googlemail.com>
+
+ * src/configuration.cpp, src/game.cpp, src/player_relations.h,
+ src/beingmanager.h, src/gui/setup_players.cpp, src/gui/setup.cpp,
+ src/gui/table_model.h, src/gui/table_model.cpp, src/gui/popup_box.h,
+ src/gui/popup_box.cpp, src/gui/table.h, src/gui/setup.h,
+ src/gui/table.cpp, src/gui/setup_players.h, src/gui/popupmenu.cpp,
+ src/beingmanager.cpp, src/player.cpp, src/main.cpp, src/being.cpp,
+ src/player.h, src/net/tradehandler.h, src/net/beinghandler.cpp,
+ src/net/tradehandler.cpp, src/net/chathandler.cpp,
+ src/configuration.h, src/player_relations.cpp, src/Makefile.am,
+ src/being.h, data/graphics/gui/emotions.png,
+ data/graphics/gui/Makefile.am, data/help/commands.txt: Added ability
+ to define friends, players you want to ignore or disregard and
+ configure whether trading is allowed. Based on new popup code,
+ configuration improvements to store hierarchical data and a table
+ model.
+
2008-05-08 Dennis Friis <peavey@placid.dk>
* src/game.cpp: Make F8 toggle shortcut window as suggested by And1
@@ -7,7 +25,7 @@
2008-05-06 Dennis Friis <peavey@placid.dk>
- * src/gui/itemshortcutcontainer.cpp: Dont allow dragging of empty
+ * src/gui/itemshortcutcontainer.cpp: Don't allow dragging of empty
placeholders.
2008-04-29 Bjørn Lindeijer <bjorn@lindeijer.nl>
diff --git a/data/graphics/gui/Makefile.am b/data/graphics/gui/Makefile.am
index 032447a2..cfa14192 100644
--- a/data/graphics/gui/Makefile.am
+++ b/data/graphics/gui/Makefile.am
@@ -11,6 +11,7 @@ gui_DATA = \
checkbox.png \
close_button.png \
deepbox.png \
+ emotions.png \
fixedfont.png \
hits_blue.png \
hits_red.png \
diff --git a/data/graphics/gui/emotions.png b/data/graphics/gui/emotions.png
new file mode 100644
index 00000000..146f2d24
--- /dev/null
+++ b/data/graphics/gui/emotions.png
Binary files differ
diff --git a/data/help/commands.txt b/data/help/commands.txt
index f663068f..5cd9aee4 100644
--- a/data/help/commands.txt
+++ b/data/help/commands.txt
@@ -37,3 +37,56 @@
Left click to execute default action: walk, pick up an item, attack a monster
and talk to NPCs (be sure to click on their feet). Right click to show up a
context menu. Holding [Left Shift] prevents from walking when attacking.
+
+
+##2COMMUNICATION:
+
+ Communication is often essential to success in this game. You can communicate
+ in several ways: By chatting and showing emotions (see above), by trading
+ (with the right-click context menu), and by whispering.
+
+ To whisper, type
+
+ /whisper <name> <message>
+
+ This will send <message> to player <name>, if that player is logged in.
+
+
+##2IGNORING COMMUNICATION
+
+ You may find that not all communication is to your liking. While most people
+ are nice, some may offend you or try to make your life harder-- since this is
+ an open game, there is nothing the developers can do to prevent this.
+
+ However, you can protect yourself from such players by ignoring them. Right-
+ click on them to bring up the context menu, then select `Ignore' or
+ `Disregard' (see below). You can fine-tune your player relations in the
+ `Setup' menu, which lists all the players you have added to it. To open this
+ menu, select `Setup' in the upper right corner of the screen, then `Players'.
+
+ There you will find a list of all players you are acquainted with, as well as
+ several configuration options:
+
+ - ##2save player list##P: Should your acquaintance list be saved when you
+ quit the game? If you enable this option, your list will survive when you
+ quit and re-start.
+ - ##2allow trading##P: Do you wish to allow trade requests from arbitrary
+ players?
+ - ##2allow whispers##P: Do you wish to allow arbitrary players to send
+ private messages to you in-game?
+
+##2THE PLAYER LIST
+
+ The player list lists all of your acquaintances. They are categorised as one
+ of the following:
+
+ - ##2neutral##P: As far as the game is concerned, this is the same as not
+ having the player listed: the player may chat with you, but may only trade
+ or whisper if you have this option allowed for everyone.
+ - ##2friend##P: You consider this player a friend. The player may chat,
+ message your in private, or trade with you at any point.
+ - ##2disregarded##P: You wish to disregard this player, meaning that his or
+ her chat messages are not logged and trade requests and whispers are
+ ignored.
+ - ##2ignored##P: You wish to completely ignore this player. You will not
+ even see floating text for him or her anymore, nor emotions.
diff --git a/src/Makefile.am b/src/Makefile.am
index dab89e54..f69caaf5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -78,6 +78,8 @@ tmw_SOURCES = gui/widgets/resizegrip.cpp \
gui/passwordfield.h \
gui/playerbox.cpp \
gui/playerbox.h \
+ gui/popup_box.cpp \
+ gui/popup_box.h \
gui/popupmenu.cpp \
gui/popupmenu.h \
gui/progressbar.cpp \
@@ -98,6 +100,8 @@ tmw_SOURCES = gui/widgets/resizegrip.cpp \
gui/setup_joystick.h \
gui/setup_keyboard.cpp \
gui/setup_keyboard.h \
+ gui/setup_players.cpp \
+ gui/setup_players.h \
gui/setuptab.h \
gui/setup_video.cpp \
gui/setup_video.h \
@@ -113,6 +117,10 @@ tmw_SOURCES = gui/widgets/resizegrip.cpp \
gui/status.h \
gui/tabbedcontainer.cpp \
gui/tabbedcontainer.h \
+ gui/table.h \
+ gui/table.cpp \
+ gui/table_model.h \
+ gui/table_model.cpp \
gui/textbox.cpp \
gui/textbox.h \
gui/textfield.cpp \
@@ -272,6 +280,8 @@ tmw_SOURCES = gui/widgets/resizegrip.cpp \
particleemitter.h \
player.cpp \
player.h \
+ player_relations.cpp \
+ player_relations.h \
properties.h \
serverinfo.h \
simpleanimation.cpp \
diff --git a/src/being.cpp b/src/being.cpp
index c262602d..0e0fd720 100644
--- a/src/being.cpp
+++ b/src/being.cpp
@@ -72,7 +72,7 @@ Being::Being(int id, int job, Map *map):
{
// Load the emotion set
ResourceManager *rm = ResourceManager::getInstance();
- emotionSet = rm->getImageSet("graphics/sprites/emotions.png", 30, 32);
+ emotionSet = rm->getImageSet("graphics/gui/emotions.png", 30, 32);
if (!emotionSet) logger->error("Unable to load emotions!");
}
diff --git a/src/being.h b/src/being.h
index 62ad7ed1..277c673e 100644
--- a/src/being.h
+++ b/src/being.h
@@ -37,6 +37,8 @@
#define NR_HAIR_STYLES 8
#define NR_HAIR_COLORS 10
+#define FIRST_IGNORE_EMOTE 14
+
class AnimatedSprite;
class Equipment;
class ItemInfo;
@@ -356,6 +358,8 @@ class Being : public Sprite
*/
void controlParticle(Particle *particle);
+ void setEmote(Uint8 emotion, Uint8 emote_time) { mEmotion = emotion; mEmotionTime = emote_time; }
+
const std::auto_ptr<Equipment> mEquipment;
protected:
diff --git a/src/beingmanager.cpp b/src/beingmanager.cpp
index 11fc1c6d..b683b1bd 100644
--- a/src/beingmanager.cpp
+++ b/src/beingmanager.cpp
@@ -146,6 +146,21 @@ Being* BeingManager::findBeingByPixel(Uint16 x, Uint16 y)
return NULL;
}
+Being* BeingManager::findBeingByName(std::string name, Being::Type type)
+{
+ for (BeingIterator i = mBeings.begin(); i != mBeings.end(); i++)
+ {
+ Being *being = (*i);
+ if (being->getName() == name
+ && (type == Being::UNKNOWN
+ || type == being->getType()))
+ return being;
+ }
+ return NULL;
+}
+
+
+
Beings& BeingManager::getAll()
{
return mBeings;
diff --git a/src/beingmanager.h b/src/beingmanager.h
index d001c377..243486e4 100644
--- a/src/beingmanager.h
+++ b/src/beingmanager.h
@@ -79,6 +79,11 @@ class BeingManager
Being::Type type = Being::UNKNOWN);
/**
+ * Finds a being by name and (optionally) by type.
+ */
+ Being* findBeingByName(std::string name, Being::Type type = Being::UNKNOWN);
+
+ /**
* Return a being nearest to another being.
*
* \param maxdist maximal distance. If minimal distance is larger,
diff --git a/src/configuration.cpp b/src/configuration.cpp
index 864ad7c3..7e8cb542 100644
--- a/src/configuration.cpp
+++ b/src/configuration.cpp
@@ -32,6 +32,109 @@
#include "utils/tostring.h"
#include "utils/xml.h"
+void ConfigurationObject::setValue(const std::string &key, std::string value)
+{
+ mOptions[key] = value;
+}
+
+void ConfigurationObject::setValue(const std::string &key, float value)
+{
+ setValue(key, toString((value == (int)value) ? (int)value : value));
+}
+
+void Configuration::setValue(const std::string &key, float value)
+{
+ setValue(key, toString((value == (int)value) ? (int)value : value));
+}
+
+void Configuration::setValue(const std::string &key, std::string value)
+{
+ ConfigurationObject::setValue(key, value);
+
+ // Notify listeners
+ ListenerMapIterator list = mListenerMap.find(key);
+ if (list != mListenerMap.end()) {
+ Listeners listeners = list->second;
+ for (ListenerIterator i = listeners.begin(); i != listeners.end(); i++)
+ {
+ (*i)->optionChanged(key);
+ }
+ }
+}
+
+std::string ConfigurationObject::getValue(const std::string &key, std::string deflt)
+{
+ OptionIterator iter = mOptions.find(key);
+ return ((iter != mOptions.end()) ? iter->second : deflt);
+}
+
+float ConfigurationObject::getValue(const std::string &key, float deflt)
+{
+ OptionIterator iter = mOptions.find(key);
+ return (iter != mOptions.end()) ? atof(iter->second.c_str()) : deflt;
+}
+
+void
+ConfigurationObject::deleteList(const std::string &name)
+{
+ for (ConfigurationList::const_iterator
+ it = mContainerOptions[name].begin(); it != mContainerOptions[name].end(); it++)
+ delete *it;
+
+ mContainerOptions[name].clear();
+}
+
+void
+ConfigurationObject::clear(void)
+{
+ for (std::map<std::string, ConfigurationList>::const_iterator
+ it = mContainerOptions.begin(); it != mContainerOptions.end(); it++)
+ deleteList(it->first);
+ mOptions.clear();
+}
+
+
+ConfigurationObject::~ConfigurationObject(void)
+{
+ clear();
+}
+
+void
+ConfigurationObject::initFromXML(xmlNodePtr parent_node)
+{
+ clear();
+
+ for_each_xml_child_node(node, parent_node)
+ {
+ if (xmlStrEqual(node->name, BAD_CAST "list")) {
+ // list option handling
+
+ std::string name = XML::getProperty(node, "name", std::string());
+
+ for_each_xml_child_node(subnode, node)
+ {
+ if (xmlStrEqual(subnode->name, BAD_CAST name.c_str())
+ && subnode->type == XML_ELEMENT_NODE) {
+ ConfigurationObject *cobj = new ConfigurationObject();
+
+ cobj->initFromXML(subnode); // recurse
+
+ mContainerOptions[name].push_back(cobj);
+ }
+ }
+
+ } else if (xmlStrEqual(node->name, BAD_CAST "option")) {
+ // single option handling
+
+ std::string name = XML::getProperty(node, "name", std::string());
+ std::string value = XML::getProperty(node, "value", std::string());
+
+ if (!name.empty() && !value.empty())
+ mOptions[name] = value;
+ } // otherwise ignore
+ }
+}
+
void Configuration::init(const std::string &filename)
{
mConfigPath = filename;
@@ -57,23 +160,45 @@ void Configuration::init(const std::string &filename)
return;
}
- for_each_xml_child_node(node, rootNode)
+ initFromXML(rootNode);
+
+ xmlFreeDoc(doc);
+}
+
+void
+ConfigurationObject::writeToXML(xmlTextWriterPtr writer)
+{
+ for (OptionIterator i = mOptions.begin(); i != mOptions.end(); i++)
{
- if (!xmlStrEqual(node->name, BAD_CAST "option"))
- continue;
+ xmlTextWriterStartElement(writer, BAD_CAST "option");
+ xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "name", BAD_CAST i->first.c_str());
+ xmlTextWriterWriteAttribute(writer,
+ BAD_CAST "value", BAD_CAST i->second.c_str());
+ xmlTextWriterEndElement(writer);
+ }
- std::string name = XML::getProperty(node, "name", std::string());
- std::string value = XML::getProperty(node, "value", std::string());
+ for (std::map<std::string, ConfigurationList>::const_iterator
+ it = mContainerOptions.begin(); it != mContainerOptions.end(); it++) {
+ const char *name = it->first.c_str();
- if (!name.empty() && !value.empty())
- {
- mOptions[name] = value;
+ xmlTextWriterStartElement(writer, BAD_CAST "list");
+ xmlTextWriterWriteAttribute(writer, BAD_CAST "name", BAD_CAST name);
+
+ // recurse on all elements
+ for (ConfigurationList::const_iterator
+ elt_it = it->second.begin(); elt_it != it->second.end(); elt_it++) {
+
+ xmlTextWriterStartElement(writer, BAD_CAST name);
+ (*elt_it)->writeToXML(writer);
+ xmlTextWriterEndElement(writer);
}
- }
- xmlFreeDoc(doc);
+ xmlTextWriterEndElement(writer);
+ }
}
+
void Configuration::write()
{
// Do not attempt to write to file that cannot be opened for writing
@@ -100,52 +225,12 @@ void Configuration::write()
xmlTextWriterStartDocument(writer, NULL, NULL, NULL);
xmlTextWriterStartElement(writer, BAD_CAST "configuration");
- for (OptionIterator i = mOptions.begin(); i != mOptions.end(); i++)
- {
- xmlTextWriterStartElement(writer, BAD_CAST "option");
- xmlTextWriterWriteAttribute(writer,
- BAD_CAST "name", BAD_CAST i->first.c_str());
- xmlTextWriterWriteAttribute(writer,
- BAD_CAST "value", BAD_CAST i->second.c_str());
- xmlTextWriterEndElement(writer);
- }
+ writeToXML(writer);
xmlTextWriterEndDocument(writer);
xmlFreeTextWriter(writer);
}
-void Configuration::setValue(const std::string &key, std::string value)
-{
- mOptions[key] = value;
-
- // Notify listeners
- ListenerMapIterator list = mListenerMap.find(key);
- if (list != mListenerMap.end()) {
- Listeners listeners = list->second;
- for (ListenerIterator i = listeners.begin(); i != listeners.end(); i++)
- {
- (*i)->optionChanged(key);
- }
- }
-}
-
-void Configuration::setValue(const std::string &key, float value)
-{
- setValue(key, toString((value == (int)value) ? (int)value : value));
-}
-
-std::string Configuration::getValue(const std::string &key, std::string deflt)
-{
- OptionIterator iter = mOptions.find(key);
- return ((iter != mOptions.end()) ? iter->second : deflt);
-}
-
-float Configuration::getValue(const std::string &key, float deflt)
-{
- OptionIterator iter = mOptions.find(key);
- return (iter != mOptions.end()) ? atof(iter->second.c_str()) : deflt;
-}
-
void Configuration::addListener(
const std::string &key, ConfigListener *listener)
{
diff --git a/src/configuration.h b/src/configuration.h
index 28246a02..36d9e150 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -27,28 +27,53 @@
#include <map>
#include <list>
#include <string>
+#include <cassert>
+#include <libxml/xmlwriter.h>
class ConfigListener;
+class ConfigurationObject;
/**
- * Configuration handler for reading (and writing).
+ * Configuration list manager interface; responsible for serlialising/deserialising
+ * configuration choices in containers.
*
- * \ingroup CORE
+ * \param T Type of the container elements to serialise
+ * \param CONT Type of the container we (de)serialise
*/
-class Configuration
+template <class T, class CONT>
+class ConfigurationListManager
{
public:
/**
- * Reads config file and parse all options into memory.
+ * Writes a value into a configuration object
*
- * \param filename path to config file
+ * \param value The value to write out
+ * \param obj The configuation object to write to
+ * \return obj, or otherwise NULL to indicate that this option should be skipped
*/
- void init(const std::string &filename);
+ virtual ConfigurationObject *writeConfigItem(T value, ConfigurationObject *obj) = 0;
/**
- * Writes the current settings back to the config file.
+ * Reads a value from a configuration object
+ *
+ * \param obj The configuration object to read from
+ * \param container The container to insert the object to
*/
- void write();
+ virtual CONT readConfigItem(ConfigurationObject *obj, CONT container) = 0;
+};
+
+/**
+ * Configuration object, mapping values to names and possibly containing
+ * lists of further configuration objects
+ *
+ * \ingroup CORE
+ */
+class ConfigurationObject
+{
+ friend class Configuration;
+
+ public:
+ virtual ~ConfigurationObject(void);
/**
* Sets an option using a string value.
@@ -56,7 +81,7 @@ class Configuration
* \param key Option identifier.
* \param value Value.
*/
- void setValue(const std::string &key, std::string value);
+ virtual void setValue(const std::string &key, std::string value);
/**
* Sets an option using a numeric value.
@@ -64,7 +89,7 @@ class Configuration
* \param key Option identifier.
* \param value Value.
*/
- void setValue(const std::string &key, float value);
+ virtual void setValue(const std::string &key, float value);
/**
* Gets a value as string.
@@ -83,6 +108,102 @@ class Configuration
float getValue(const std::string &key, float deflt);
/**
+ * Re-sets all data in the configuration
+ */
+ virtual void clear(void);
+
+ /**
+ * Serialises a container into a list of configuration options
+ *
+ * \param IT Iterator type over CONT
+ * \param T Elements that IT iterates over
+ * \param CONT The associated container type
+ *
+ * \param name Name of the list the elements should be stored under
+ * \param begin Iterator start
+ * \param end Iterator end
+ * \param manager An object capable of serialising T items
+ */
+ template <class IT, class T, class CONT>
+ void setList(const std::string &name, IT begin, IT end, ConfigurationListManager<T, CONT> *manager)
+ {
+ ConfigurationObject *nextobj = new ConfigurationObject();
+ deleteList(name);
+ ConfigurationList *list = &(mContainerOptions[name]);
+
+ for (IT it = begin; it != end; it++) {
+ ConfigurationObject *wrobj = manager->writeConfigItem(*it, nextobj);
+ if (wrobj) { // wrote something
+ assert (wrobj == nextobj);
+ nextobj = new ConfigurationObject();
+ list->push_back(wrobj);
+ } else
+ nextobj->clear(); // you never know...
+ }
+
+ delete nextobj;
+ }
+
+ /**
+ * Serialises a container into a list of configuration options
+ *
+ * \param IT Iterator type over CONT
+ * \param T Elements that IT iterates over
+ * \param CONT The associated container type
+ *
+ * \param name Name of the list the elements should be read from under
+ * \param empty Initial (empty) container to write to
+ * \param manager An object capable of deserialising items into CONT
+ */
+ template<class T, class CONT>
+ CONT getList(const std::string &name, CONT empty, ConfigurationListManager<T, CONT> *manager)
+ {
+ ConfigurationList *list = &(mContainerOptions[name]);
+ CONT container = empty;
+
+ for (ConfigurationList::const_iterator it = list->begin(); it != list->end(); it++)
+ container = manager->readConfigItem(*it, container);
+
+ return container;
+ }
+
+ protected:
+ virtual void initFromXML(xmlNodePtr node);
+ virtual void writeToXML(xmlTextWriterPtr writer);
+
+ void deleteList(const std::string &name);
+
+ typedef std::map<std::string, std::string> Options;
+ typedef Options::iterator OptionIterator;
+ Options mOptions;
+
+ typedef std::list<ConfigurationObject *> ConfigurationList;
+ std::map<std::string, ConfigurationList> mContainerOptions;
+};
+
+/**
+ * Configuration handler for reading (and writing).
+ *
+ * \ingroup CORE
+ */
+class Configuration : public ConfigurationObject
+{
+ public:
+ virtual ~Configuration(void) {}
+
+ /**
+ * Reads config file and parse all options into memory.
+ *
+ * \param filename path to config file
+ */
+ void init(const std::string &filename);
+
+ /**
+ * Writes the current settings back to the config file.
+ */
+ void write();
+
+ /**
* Adds a listener to the listen list of the specified config option.
*/
void addListener(const std::string &key, ConfigListener *listener);
@@ -93,11 +214,9 @@ class Configuration
*/
void removeListener(const std::string &key, ConfigListener *listener);
+ virtual void setValue(const std::string &key, std::string value);
+ virtual void setValue(const std::string &key, float value);
private:
- typedef std::map<std::string, std::string> Options;
- typedef Options::iterator OptionIterator;
- Options mOptions;
-
typedef std::list<ConfigListener*> Listeners;
typedef Listeners::iterator ListenerIterator;
typedef std::map<std::string, Listeners> ListenerMap;
diff --git a/src/game.cpp b/src/game.cpp
index b12b114f..58c28640 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -43,6 +43,7 @@
#include "log.h"
#include "npc.h"
#include "particle.h"
+#include "player_relations.h"
#include "gui/buy.h"
#include "gui/buysell.h"
@@ -496,10 +497,16 @@ void Game::handleInput()
case SDLK_t:
// Toggle accepting of incoming trade requests
{
- TradeHandler *th = static_cast<TradeHandler*>(
- mTradeHandler.get());
- th->setAcceptTradeRequests(
- !th->acceptTradeRequests());
+ unsigned int deflt = player_relations.getDefault();
+ if (deflt & PlayerRelation::TRADE) {
+ chatWindow->chatLog("Ignoring incoming trade requests", BY_SERVER);
+ deflt &= ~PlayerRelation::TRADE;
+ } else {
+ chatWindow->chatLog("Accepting incoming trade requests", BY_SERVER);
+ deflt |= PlayerRelation::TRADE;
+ }
+
+ player_relations.setDefault(deflt);
}
used = true;
break;
diff --git a/src/gui/popup_box.cpp b/src/gui/popup_box.cpp
new file mode 100644
index 00000000..4cac2169
--- /dev/null
+++ b/src/gui/popup_box.cpp
@@ -0,0 +1,139 @@
+/*
+ * The Mana World
+ * Copyright 2004 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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
+ * any later version.
+ *
+ * The Mana World is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "popup_box.h"
+#include "window.h"
+#include "browserbox.h"
+
+#include <iostream>
+#include <sstream>
+
+class PoppedSelectionWindow : public Window
+{
+public:
+ PoppedSelectionWindow(PopupBox *owner):
+ mOwner(owner)
+ {
+ setResizable(false);
+ setTitleBarHeight(0);
+ mShowTitle = false;
+
+ mBrowserBox = new BrowserBox();
+ mBrowserBox->setPosition(4, 4);
+ mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND);
+ mBrowserBox->setOpaque(false);
+ add(mBrowserBox);
+ mBrowserBox->setLinkHandler(owner);
+
+ initModel();
+ }
+
+ void initModel(void)
+ {
+ mBrowserBox->clearRows();
+ gcn::ListModel *model = mOwner->getListModel();
+ for (int i = 0; i < model->getNumberOfElements(); i++) {
+ std::stringstream s;
+ s << "@@" << i << "|" << model->getElementAt(i) << "@@";
+ mBrowserBox->addRow(s.str());
+ }
+
+ setContentSize(mBrowserBox->getWidth() + 8, mBrowserBox->getHeight() + 8);
+ }
+
+protected:
+ BrowserBox *mBrowserBox;
+ PopupBox *mOwner;
+};
+
+PopupBox::PopupBox(gcn::ListModel *list_model) :
+ DropDown(list_model),
+ mWindow(NULL)
+{
+}
+
+PopupBox::~PopupBox(void)
+{
+ if (mWindow)
+ delete mWindow;
+}
+
+////////////////////////////////////////
+// Widget ops
+
+void
+PopupBox::draw(gcn::Graphics *graphics)
+{
+ // fill background
+ graphics->setColor(gcn::Color(0xd0, 0xd0, 0xd0));
+ graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight()));
+
+ // draw frame-ish object
+ graphics->setColor(gcn::Color(0xc0, 0xc0, 0xc0));
+ graphics->drawRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight()));
+ graphics->setColor(gcn::Color(0xe0, 0xe0, 0xe0));
+ graphics->drawLine(0, getHeight(), getWidth(), getHeight());
+ graphics->drawLine(getWidth(), 0, getWidth(), getHeight());
+
+ graphics->drawText(getListModel()->getElementAt(getSelected()), 0, 0);
+}
+
+////////////////////////////////////////
+// MouseListener ops
+
+void
+PopupBox::mousePressed(gcn::MouseEvent& mouseEvent)
+{
+ if (0 <= mouseEvent.getY()
+ && mouseEvent.getY() < getHeight()
+ && mouseEvent.getX() >= 0
+ && mouseEvent.getX() < getWidth()
+ && mouseEvent.getButton() == gcn::MouseEvent::LEFT
+ && mouseEvent.getSource() == this) {
+ if (mWindow == NULL) {
+ mWindow = new PoppedSelectionWindow(this);
+ mWindow->resizeToContent();
+ }
+
+ int x, y;
+ getAbsolutePosition(x, y);
+ mWindow->setPosition(mouseEvent.getX() + x,
+ mouseEvent.getY() + y);
+ mWindow->setVisible(true);
+ mWindow->requestMoveToTop();
+ }
+}
+
+////////////////////////////////////////
+// LinkHandler ops
+
+void
+PopupBox::handleLink(const std::string &link)
+{
+ if (mWindow)
+ mWindow->setVisible(false);
+
+ std::stringstream s;
+ s << link;
+ int index;
+ s >> index;
+ setSelected(index);
+}
diff --git a/src/gui/popup_box.h b/src/gui/popup_box.h
new file mode 100644
index 00000000..1e3b155e
--- /dev/null
+++ b/src/gui/popup_box.h
@@ -0,0 +1,71 @@
+/*
+ * The Mana World
+ * Copyright 2004 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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
+ * any later version.
+ *
+ * The Mana World is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _TMW_POPUP_BOX_H
+#define _TMW_POPUP_BOX_H
+
+#include <guichan/widgets/dropdown.hpp>
+
+#include "../guichanfwd.h"
+#include "linkhandler.h"
+
+class PoppedSelectionWindow;
+
+/**
+ * Popup box widget. Appears as a box that `pops out' into a separate floating window when selected.
+ *
+ * API adapted from guichan's listbox API. Typewise
+ */
+class PopupBox : public gcn::DropDown,
+ public LinkHandler
+{
+public:
+ /**
+ * Constructor.
+ *
+ * \param listModel the list model to use.
+ */
+ PopupBox(gcn::ListModel *list_model = NULL);
+
+ /**
+ * Destructor.
+ */
+ virtual ~PopupBox(void);
+
+ // Overriding Widget
+
+ virtual void draw(gcn::Graphics* graphics);
+
+
+ // Overriding MouseListener
+
+ virtual void mousePressed(gcn::MouseEvent& mouseEvent);
+
+
+ // Implementing Linkhandler
+ void handleLink(const std::string& link);
+
+protected:
+ PoppedSelectionWindow *mWindow;
+};
+
+
+#endif /* !defined(_TMW_POPUP_BOX_H) */
diff --git a/src/gui/popupmenu.cpp b/src/gui/popupmenu.cpp
index 8f93cb1e..f9bfe56d 100644
--- a/src/gui/popupmenu.cpp
+++ b/src/gui/popupmenu.cpp
@@ -38,6 +38,7 @@
#include "../item.h"
#include "../localplayer.h"
#include "../npc.h"
+#include "../player_relations.h"
#include "../resources/iteminfo.h"
#include "../resources/itemdb.h"
@@ -75,8 +76,29 @@ void PopupMenu::showPopup(int x, int y, Being *being)
// add as buddy will be options in this menu.
const std::string &name = mBeing->getName();
mBrowserBox->addRow("@@trade|Trade With " + name + "@@");
-
mBrowserBox->addRow("@@attack|Attack " + name + "@@");
+
+ mBrowserBox->addRow("##3---");
+
+ switch (player_relations.getRelation(name)) {
+ case PlayerRelation::NEUTRAL:
+ mBrowserBox->addRow("@@friend|Befriend " + name + "@@");
+
+ case PlayerRelation::FRIEND:
+ mBrowserBox->addRow("@@disregard|Disregard " + name + "@@");
+ mBrowserBox->addRow("@@ignore|Ignore " + name + "@@");
+ break;
+
+ case PlayerRelation::DISREGARDED:
+ mBrowserBox->addRow("@@unignore|Un-Ignore " + name + "@@");
+ mBrowserBox->addRow("@@ignore|Completely ignore " + name + "@@");
+ break;
+
+ case PlayerRelation::IGNORED:
+ mBrowserBox->addRow("@@unignore|Un-Ignore " + name + "@@");
+ break;
+ }
+
//mBrowserBox->addRow("@@follow|Follow " + name + "@@");
//mBrowserBox->addRow("@@buddy|Add " + name + " to Buddy List@@");
}
@@ -144,6 +166,34 @@ void PopupMenu::handleLink(const std::string& link)
player_node->attack(mBeing, true);
}
+ else if (link == "unignore" &&
+ mBeing != NULL &&
+ mBeing->getType() == Being::PLAYER)
+ {
+ player_relations.setRelation(mBeing->getName(), PlayerRelation::NEUTRAL);
+ }
+
+ else if (link == "ignore" &&
+ mBeing != NULL &&
+ mBeing->getType() == Being::PLAYER)
+ {
+ player_relations.setRelation(mBeing->getName(), PlayerRelation::IGNORED);
+ }
+
+ else if (link == "disregard" &&
+ mBeing != NULL &&
+ mBeing->getType() == Being::PLAYER)
+ {
+ player_relations.setRelation(mBeing->getName(), PlayerRelation::DISREGARDED);
+ }
+
+ else if (link == "friend" &&
+ mBeing != NULL &&
+ mBeing->getType() == Being::PLAYER)
+ {
+ player_relations.setRelation(mBeing->getName(), PlayerRelation::FRIEND);
+ }
+
/*
// Follow Player action
else if (link == "follow")
diff --git a/src/gui/setup.cpp b/src/gui/setup.cpp
index 3089e54a..54bf01f4 100644
--- a/src/gui/setup.cpp
+++ b/src/gui/setup.cpp
@@ -30,9 +30,11 @@
#include "setup_joystick.h"
#include "setup_video.h"
#include "setup_keyboard.h"
+#include "setup_players.h"
#include "tabbedcontainer.h"
#include "../utils/dtor.h"
+#include <iostream>
extern Window *statusWindow;
extern Window *minimap;
@@ -46,7 +48,7 @@ Setup::Setup():
Window("Setup")
{
setCloseButton(true);
- int width = 250;
+ int width = 310;
int height = 245;
setContentSize(width, height);
@@ -62,7 +64,7 @@ Setup::Setup():
}
TabbedContainer *panel = new TabbedContainer();
- panel->setDimension(gcn::Rectangle(5, 5, 250, 205));
+ panel->setDimension(gcn::Rectangle(5, 5, width, 205));
panel->setOpaque(false);
SetupTab *tab;
@@ -83,6 +85,10 @@ Setup::Setup():
panel->addTab(tab, "Keyboard");
mTabs.push_back(tab);
+ tab = new Setup_Players();
+ panel->addTab(tab, "Players");
+ mTabs.push_back(tab);
+
add(panel);
setLocationRelativeTo(getParent());
diff --git a/src/gui/setup.h b/src/gui/setup.h
index 77173367..543cab2c 100644
--- a/src/gui/setup.h
+++ b/src/gui/setup.h
@@ -48,7 +48,7 @@ class Setup : public Window, public gcn::ActionListener
/**
* Destructor.
*/
- ~Setup();
+ virtual ~Setup();
/**
* Event handling method.
diff --git a/src/gui/setup_players.cpp b/src/gui/setup_players.cpp
new file mode 100644
index 00000000..16c916b4
--- /dev/null
+++ b/src/gui/setup_players.cpp
@@ -0,0 +1,342 @@
+/*
+ * The Mana World
+ * Copyright 2004 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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
+ * any later version.
+ *
+ * The Mana World is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * $Id$
+ */
+
+#include "setup_players.h"
+
+#include <vector>
+#include <guichan/widgets/label.hpp>
+#include "popup_box.h"
+#include "button.h"
+
+#include "checkbox.h"
+#include "ok_dialog.h"
+
+#include "../player_relations.h"
+#include "../configuration.h"
+#include "../log.h"
+#include "../sound.h"
+
+#define COLUMNS_NR 2 // name plus listbox
+#define NAME_COLUMN 0
+#define RELATION_CHOICE_COLUMN 1
+
+#define ROW_HEIGHT 12
+// The following column widths really shouldn't be hardcoded but should scale with the size of the widget... except
+// that, right now, the widget doesn't exactly scale either.
+#define NAME_COLUMN_WIDTH 120
+#define RELATION_CHOICE_COLUMN_WIDTH 80
+
+#define WIDGET_AT(row, column) (((row) * COLUMNS_NR) + column)
+
+static std::string table_titles[COLUMNS_NR] = {"name", "relation"};
+
+static const std::string RELATION_NAMES[PlayerRelation::RELATIONS_NR] = {
+ "neutral", "friend", "disregarded", "ignored"
+};
+
+class PlayerRelationListModel : public gcn::ListModel
+{
+public:
+ virtual ~PlayerRelationListModel(void) { }
+
+ virtual int getNumberOfElements(void)
+ {
+ return PlayerRelation::RELATIONS_NR;
+ }
+
+ virtual std::string getElementAt(int i)
+ {
+ if (i >= getNumberOfElements() || i < 0)
+ return "";
+ return RELATION_NAMES[i];
+ }
+};
+
+class PlayerTableModel : public TableModel
+{
+public:
+ PlayerTableModel(void) :
+ mPlayers(NULL)
+ {
+ playerRelationsUpdated();
+ }
+
+ virtual ~PlayerTableModel(void)
+ {
+ freeWidgets();
+ if (mPlayers)
+ delete mPlayers;
+ }
+
+ virtual int getRows(void)
+ {
+ return mPlayers->size();
+ }
+
+ virtual int getColumns(void)
+ {
+ return COLUMNS_NR;
+ }
+
+ virtual int getRowHeight(void)
+ {
+ return ROW_HEIGHT;
+ }
+
+ virtual int getColumnWidth(int index)
+ {
+ if (index == NAME_COLUMN)
+ return NAME_COLUMN_WIDTH;
+ else
+ return RELATION_CHOICE_COLUMN_WIDTH;
+ }
+
+ virtual void playerRelationsUpdated(void)
+ {
+ signalBeforeUpdate();
+
+ freeWidgets();
+ std::vector<std::string> *player_names = player_relations.getPlayers();
+ if (mPlayers)
+ delete mPlayers;
+ mPlayers = player_names;
+
+ // set up widgets
+ for (unsigned int r = 0; r < player_names->size(); ++r) {
+ std::string name = (*player_names)[r];
+ gcn::Widget *widget = new gcn::Label(name);
+ mWidgets.push_back(widget);
+
+ PopupBox *choicebox = new PopupBox(new PlayerRelationListModel());
+ choicebox->setSelected(player_relations.getRelation(name));
+ mWidgets.push_back(choicebox);
+ }
+
+ signalAfterUpdate();
+ }
+
+ virtual void updateModelInRow(int row)
+ {
+ PopupBox *choicebox = dynamic_cast<PopupBox *>(getElementAt(row, RELATION_CHOICE_COLUMN));
+ player_relations.setRelation(getPlayerAt(row),
+ static_cast<PlayerRelation::relation>(choicebox->getSelected()));
+ }
+
+
+ virtual gcn::Widget *getElementAt(int row, int column)
+ {
+ return mWidgets[WIDGET_AT(row, column)];
+ }
+
+ virtual void freeWidgets(void)
+ {
+ if (mPlayers)
+ delete mPlayers;
+ mPlayers = NULL;
+
+ for (std::vector<gcn::Widget *>::const_iterator it = mWidgets.begin(); it != mWidgets.end(); it++) {
+ delete *it;
+ }
+
+ mWidgets.clear();
+ }
+
+ std::string getPlayerAt(int index)
+ {
+ return (*mPlayers)[index];
+ }
+
+protected:
+ std::vector<std::string> *mPlayers;
+ std::vector<gcn::Widget *> mWidgets;
+};
+
+/**
+ * Class for choosing one of the various `what to do when ignoring a player' options
+ */
+class IgnoreChoicesListModel : public gcn::ListModel
+{
+public:
+ virtual ~IgnoreChoicesListModel(void) { }
+
+ virtual int getNumberOfElements(void)
+ {
+ return player_relations.getPlayerIgnoreStrategies()->size();
+ }
+
+ virtual std::string getElementAt(int i)
+ {
+ if (i >= getNumberOfElements()) {
+ return "???";
+ }
+ return (*player_relations.getPlayerIgnoreStrategies())[i]->mDescription;
+ }
+};
+
+#define ACTION_DELETE "delete"
+#define ACTION_TABLE "table"
+#define ACTION_STRATEGY "strategy"
+
+Setup_Players::Setup_Players():
+ mPlayerTableTitleModel(new StaticTableModel(1, COLUMNS_NR)),
+ mPlayerTableModel(new PlayerTableModel()),
+ mPlayerTable(new GuiTable(mPlayerTableModel)),
+ mPlayerTitleTable(new GuiTable(mPlayerTableTitleModel)),
+ mPlayerScrollArea(new ScrollArea(mPlayerTable)),
+ mPersistIgnores(new CheckBox("save player list", player_relations.getPersistIgnores())),
+ mDefaultTrading(new CheckBox("allow trading", player_relations.getDefault() & PlayerRelation::TRADE)),
+ mDefaultWhisper(new CheckBox("allow whispers", player_relations.getDefault() & PlayerRelation:: WHISPER)),
+ mDeleteButton(new Button("Delete", ACTION_DELETE, this)),
+ mIgnoreActionChoicesBox(new PopupBox(new IgnoreChoicesListModel()))
+{
+ setOpaque(false);
+
+ int table_width = NAME_COLUMN_WIDTH + RELATION_CHOICE_COLUMN_WIDTH;
+ mPlayerTableTitleModel->fixColumnWidth(NAME_COLUMN, NAME_COLUMN_WIDTH);
+ mPlayerTableTitleModel->fixColumnWidth(RELATION_CHOICE_COLUMN, RELATION_CHOICE_COLUMN_WIDTH);
+ mPlayerTitleTable->setDimension(gcn::Rectangle(10, 10, table_width, 10));
+ mPlayerTitleTable->setBackgroundColor(gcn::Color(0xbf, 0xbf, 0xbf));
+ for (int i = 0; i < COLUMNS_NR; i++)
+ mPlayerTableTitleModel->set(0, i, new gcn::Label(table_titles[i]));
+ mPlayerTitleTable->setLinewiseSelection(true);
+
+ mPlayerScrollArea->setDimension(gcn::Rectangle(10, 25, table_width + COLUMNS_NR, 90));
+ mPlayerScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+ mPlayerTable->setActionEventId(ACTION_TABLE);
+ mPlayerTable->setLinewiseSelection(true);
+ mPlayerTable->addActionListener(this);
+
+ mDeleteButton->setPosition(10, 118);
+
+ gcn::Label *ignore_action_label = new gcn::Label("When ignoring:");
+
+ ignore_action_label->setPosition(80, 118);
+ mIgnoreActionChoicesBox->setDimension(gcn::Rectangle(80, 132, 120, 12));
+ mIgnoreActionChoicesBox->setActionEventId(ACTION_STRATEGY);
+ mIgnoreActionChoicesBox->addActionListener(this);
+ int ignore_strategy_index = 0; // safe default
+ if (player_relations.getPlayerIgnoreStrategy()) {
+ ignore_strategy_index = player_relations.getPlayerIgnoreStrategyIndex(
+ player_relations.getPlayerIgnoreStrategy()->mShortName);
+ if (ignore_strategy_index < 0)
+ ignore_strategy_index = 0;
+ }
+ mIgnoreActionChoicesBox->setSelected(ignore_strategy_index);
+ mIgnoreActionChoicesBox->adjustHeight();
+
+ mPersistIgnores->setPosition(80, 148);
+ mDefaultTrading->setPosition(80, 160);
+ mDefaultWhisper->setPosition(80, 172);
+
+ reset();
+
+ add(ignore_action_label);
+ add(mDefaultTrading);
+ add(mDefaultWhisper);
+ add(mIgnoreActionChoicesBox);
+ add(mDeleteButton);
+ add(mPlayerScrollArea);
+ add(mPlayerTitleTable);
+ add(mPersistIgnores);
+
+ player_relations.addListener(this);
+}
+
+Setup_Players::~Setup_Players(void)
+{
+ player_relations.removeListener(this);
+}
+
+
+void
+Setup_Players::reset()
+{
+ // We now have to search through the list of ignore choices to find the current
+ // selection. We could use an index into the table of config options in
+ // player_relations instead of strategies to sidestep this.
+ int selection = 0;
+ for (unsigned int i = 0; i < player_relations.getPlayerIgnoreStrategies()->size(); ++i)
+ if ((*player_relations.getPlayerIgnoreStrategies())[i] ==
+ player_relations.getPlayerIgnoreStrategy()) {
+
+ selection = i;
+ break;
+ }
+
+ mIgnoreActionChoicesBox->setSelected(selection);
+}
+
+void
+Setup_Players::apply()
+{
+ player_relations.setPersistIgnores(mPersistIgnores->isSelected());
+ player_relations.store();
+
+ unsigned int old_default_relations =
+ player_relations.getDefault() & ~(PlayerRelation::TRADE | PlayerRelation::WHISPER);
+ player_relations.setDefault(old_default_relations
+ | (mDefaultTrading->isSelected()? PlayerRelation::TRADE : 0)
+ | (mDefaultWhisper->isSelected()? PlayerRelation::WHISPER : 0));
+}
+
+void
+Setup_Players::cancel()
+{
+}
+
+void
+Setup_Players::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == ACTION_TABLE) {
+ // temporarily eliminate ourselves: we are fully aware of this change, so there is no
+ // need for asynchronous updates. (In fact, thouse might destroy the widet that
+ // triggered them, which would be rather embarrassing.)
+ player_relations.removeListener(this);
+
+ int row = mPlayerTable->getSelectedRow();
+ if (row >= 0)
+ mPlayerTableModel->updateModelInRow(row);
+
+ player_relations.addListener(this);
+
+ } else if (event.getId() == ACTION_DELETE) {
+ std::string name = mPlayerTableModel->getPlayerAt(mPlayerTable->getSelectedRow());
+
+ player_relations.removePlayer(name);
+
+ } else if (event.getId() == ACTION_STRATEGY) {
+ PlayerIgnoreStrategy *s =
+ (*player_relations.getPlayerIgnoreStrategies())[
+ mIgnoreActionChoicesBox->getSelected()];
+
+ player_relations.setPlayerIgnoreStrategy(s);
+ }
+}
+
+void
+Setup_Players::updatedPlayer(const std::string &name)
+{
+ mPlayerTableModel->playerRelationsUpdated();
+ mDefaultTrading->setSelected(player_relations.getDefault() & PlayerRelation::TRADE);
+ mDefaultWhisper->setSelected(player_relations.getDefault() & PlayerRelation::WHISPER);
+}
diff --git a/src/gui/setup_players.h b/src/gui/setup_players.h
new file mode 100644
index 00000000..2c6ccc82
--- /dev/null
+++ b/src/gui/setup_players.h
@@ -0,0 +1,70 @@
+/*
+ * The Mana World
+ * Copyright 2004 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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
+ * any later version.
+ *
+ * The Mana World is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _TMW_GUI_SETUP_PLAYERS_H
+#define _TMW_GUI_SETUP_PLAYERS_H
+
+#include "setuptab.h"
+
+#include "scrollarea.h"
+#include "button.h"
+#include "table.h"
+#include "popup_box.h"
+#include <guichan/actionlistener.hpp>
+
+#include "../guichanfwd.h"
+
+#include "../player_relations.h"
+
+class PlayerTableModel;
+
+class Setup_Players : public SetupTab, public gcn::ActionListener, public PlayerRelationsListener
+{
+public:
+ Setup_Players();
+ virtual ~Setup_Players();
+
+ void apply();
+ void cancel();
+
+ void reset();
+
+ void action(const gcn::ActionEvent &event);
+
+ virtual void updatedPlayer(const std::string &name);
+
+private:
+
+ StaticTableModel *mPlayerTableTitleModel;
+ PlayerTableModel *mPlayerTableModel;
+ GuiTable *mPlayerTable;
+ GuiTable *mPlayerTitleTable;
+ ScrollArea *mPlayerScrollArea;
+
+ gcn::CheckBox *mPersistIgnores;
+ gcn::CheckBox *mDefaultTrading;
+ gcn::CheckBox *mDefaultWhisper;
+
+ Button *mDeleteButton;
+ PopupBox *mIgnoreActionChoicesBox;
+};
+
+#endif
diff --git a/src/gui/table.cpp b/src/gui/table.cpp
new file mode 100644
index 00000000..07f40f76
--- /dev/null
+++ b/src/gui/table.cpp
@@ -0,0 +1,419 @@
+/*
+ * The Mana World
+ * Copyright 2004 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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
+ * any later version.
+ *
+ * The Mana World is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <guichan/graphics.hpp>
+#include <guichan/actionlistener.hpp>
+#include "table.h"
+#include <cassert>
+
+
+
+class GuiTableActionListener : public gcn::ActionListener
+{
+public:
+ GuiTableActionListener(GuiTable *_table, gcn::Widget *_widget, int _row, int _column);
+
+ virtual ~GuiTableActionListener(void);
+
+ virtual void action(const gcn::ActionEvent& actionEvent);
+
+protected:
+ GuiTable *mTable;
+ int mRow;
+ int mColumn;
+ gcn::Widget *mWidget;
+};
+
+
+GuiTableActionListener::GuiTableActionListener(GuiTable *table, gcn::Widget *widget, int row, int column) :
+ mTable(table),
+ mRow(row),
+ mColumn(column),
+ mWidget(widget)
+{
+ if (widget) {
+ widget->addActionListener(this);
+ widget->_setParent(table);
+ }
+}
+
+GuiTableActionListener::~GuiTableActionListener(void)
+{
+ if (mWidget) {
+ mWidget->removeActionListener(this);
+ mWidget->_setParent(NULL);
+ }
+}
+
+void
+GuiTableActionListener::action(const gcn::ActionEvent& actionEvent)
+{
+ mTable->setSelected(mRow, mColumn);
+ mTable->distributeActionEvent();
+}
+
+
+GuiTable::GuiTable(TableModel *initial_model) :
+ mLinewiseMode(false),
+ mModel(NULL),
+ mSelectedRow(0),
+ mSelectedColumn(0),
+ mTopWidget(NULL)
+{
+ setModel(initial_model);
+ addMouseListener(this);
+ addKeyListener(this);
+}
+
+GuiTable::~GuiTable(void)
+{
+ delete mModel;
+}
+
+TableModel *
+GuiTable::getModel(void) const
+{
+ return mModel;
+}
+
+void
+GuiTable::setModel(TableModel *new_model)
+{
+ if (mModel) {
+ uninstallActionListeners();
+ mModel->removeListener(this);
+ }
+
+ mModel = new_model;
+ installActionListeners();
+
+ new_model->installListener(this);
+ recomputeDimensions();
+}
+
+void
+GuiTable::recomputeDimensions(void)
+{
+ int rows_nr = mModel->getRows();
+ int columns_nr = mModel->getColumns();
+ int width = 0;
+ int height = 0;
+
+ if (mSelectedRow >= rows_nr)
+ mSelectedRow = rows_nr - 1;
+
+ if (mSelectedColumn >= columns_nr)
+ mSelectedColumn = columns_nr - 1;
+
+ for (int i = 0; i < columns_nr; i++)
+ width += getColumnWidth(i);
+
+ height = getRowHeight() * rows_nr;
+
+ setWidth(width);
+ setHeight(height);
+}
+
+void
+GuiTable::setSelected(int row, int column)
+{
+ mSelectedColumn = column;
+ mSelectedRow = row;
+}
+
+int
+GuiTable::getSelectedRow(void)
+{
+ return mSelectedRow;
+}
+
+int
+GuiTable::getSelectedColumn(void)
+{
+ return mSelectedColumn;
+}
+
+void
+GuiTable::setLinewiseSelection(bool linewise)
+{
+ mLinewiseMode = linewise;
+}
+
+int
+GuiTable::getRowHeight(void)
+{
+ if (mModel)
+ return mModel->getRowHeight() + 1; // border
+ else
+ return 0;
+}
+
+int
+GuiTable::getColumnWidth(int i)
+{
+ if (mModel)
+ return mModel->getColumnWidth(i) + 1; // border
+ else
+ return 0;
+}
+
+void
+GuiTable::uninstallActionListeners(void)
+{
+ for (std::vector<GuiTableActionListener *>::const_iterator it = action_listeners.begin(); it != action_listeners.end(); it++)
+ delete *it;
+ action_listeners.clear();
+}
+
+void
+GuiTable::installActionListeners(void)
+{
+ int rows = mModel->getRows();
+ int columns = mModel->getColumns();
+
+ for (int row = 0; row < rows; ++row)
+ for (int column = 0; column < columns; ++column) {
+ gcn::Widget *widget = mModel->getElementAt(row, column);
+ action_listeners.push_back(new GuiTableActionListener(this, widget,
+ row, column));
+ }
+
+ _setFocusHandler(_getFocusHandler()); // propagate focus handler to widgets
+}
+
+// -- widget ops
+void
+GuiTable::draw(gcn::Graphics* graphics)
+{
+ graphics->setColor(getBackgroundColor());
+ graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight()));
+
+ if (!mModel)
+ return;
+
+ // First, determine how many rows we need to draw, and where we should start.
+ int first_row = -(getY() / getRowHeight());
+
+ if (first_row < 0)
+ first_row = 0;
+
+ int rows_nr = 1 + (getHeight() / getRowHeight()); // May overestimate by one.
+
+ int max_rows_nr = mModel->getRows() - first_row; // clip if neccessary:
+ if (max_rows_nr < rows_nr)
+ rows_nr = max_rows_nr;
+
+ // Now determine the first and last column
+ // Take the easy way out; these are usually bounded and all visible.
+ int first_column = 0;
+ int last_column = mModel->getColumns() - 1;
+
+ // Set up everything for drawing
+ int height = getRowHeight();
+ int y_offset = first_row * height;
+
+ for (int r = first_row; r < first_row + rows_nr; ++r) {
+ int x_offset = 0;
+
+ for (int c = first_column; c <= last_column; ++c) {
+ gcn::Widget *widget = mModel->getElementAt(r, c);
+ int width = getColumnWidth(c);
+ if (widget) {
+ gcn::Rectangle bounds(x_offset, y_offset, width, height);
+
+ if (widget == mTopWidget) {
+ bounds.height = widget->getHeight();
+ bounds.width = widget->getWidth();
+ }
+
+ widget->setDimension(bounds);
+
+ graphics->pushClipArea(bounds);
+ widget->draw(graphics);
+ graphics->popClipArea();
+
+ if (!mLinewiseMode
+ && c == mSelectedColumn
+ && r == mSelectedRow)
+ graphics->drawRectangle(bounds);
+ }
+
+ x_offset += width;
+ }
+
+ if (mLinewiseMode
+ && r == mSelectedRow)
+ graphics->drawRectangle(gcn::Rectangle(0, y_offset,
+ x_offset, height));
+
+ y_offset += height;
+ }
+
+ if (mTopWidget) {
+ gcn::Rectangle bounds = mTopWidget->getDimension();
+ graphics->pushClipArea(bounds);
+ mTopWidget->draw(graphics);
+ graphics->popClipArea();
+ }
+}
+
+void
+GuiTable::logic(void)
+{
+}
+
+void
+GuiTable::moveToTop(gcn::Widget *widget)
+{
+ gcn::Widget::moveToTop(widget);
+ this->mTopWidget = widget;
+}
+
+void
+GuiTable::moveToBottom(gcn::Widget *widget)
+{
+ gcn::Widget::moveToBottom(widget);
+ if (widget == this->mTopWidget)
+ this->mTopWidget = NULL;
+}
+
+gcn::Rectangle
+GuiTable::getChildrenArea(void)
+{
+ return gcn::Rectangle(0, 0, getWidth(), getHeight());
+}
+
+// -- KeyListener notifications
+void
+GuiTable::keyPressed(gcn::KeyEvent& keyEvent)
+{
+}
+
+
+// -- MouseListener notifications
+void
+GuiTable::mousePressed(gcn::MouseEvent& mouseEvent)
+{
+ if (mouseEvent.getButton() == gcn::MouseEvent::LEFT) {
+ int row = getRowForY(mouseEvent.getY());
+ int column = getColumnForX(mouseEvent.getX());
+
+ if (row > -1 && column > -1) {
+ mSelectedColumn = column;
+ mSelectedRow = row;
+ }
+
+ distributeActionEvent();
+ }
+}
+
+void
+GuiTable::mouseWheelMovedUp(gcn::MouseEvent& mouseEvent)
+{
+}
+
+void
+GuiTable::mouseWheelMovedDown(gcn::MouseEvent& mouseEvent)
+{
+}
+
+void
+GuiTable::mouseDragged(gcn::MouseEvent& mouseEvent)
+{
+}
+
+// -- TableModelListener notifications
+void
+GuiTable::modelUpdated(bool completed)
+{
+ if (completed) {
+ recomputeDimensions();
+ installActionListeners();
+ } else // before the update?
+ uninstallActionListeners();
+}
+
+gcn::Widget *
+GuiTable::getWidgetAt(int x, int y)
+{
+ int row = getRowForY(y);
+ int column = getColumnForX(x);
+
+ if (mTopWidget
+ && mTopWidget->getDimension().isPointInRect(x, y))
+ return mTopWidget;
+
+ if (row > -1
+ && column > -1) {
+ gcn::Widget *w = mModel->getElementAt(row, column);
+ if (w->isFocusable())
+ return w;
+ else
+ return NULL; // Grab the event locally
+ } else
+ return NULL;
+}
+
+int
+GuiTable::getRowForY(int y)
+{
+ int row = y / getRowHeight();
+
+ if (row < 0
+ || row >= mModel->getRows())
+ return -1;
+ else
+ return row;
+}
+
+int
+GuiTable::getColumnForX(int x)
+{
+ int column;
+ int delta = 0;
+
+ for (column = 0; column < mModel->getColumns(); column++) {
+ delta += getColumnWidth(column);
+ if (x <= delta)
+ break;
+ }
+
+ if (column < 0
+ || column >= mModel->getColumns())
+ return -1;
+ else
+ return column;
+}
+
+
+void
+GuiTable::_setFocusHandler(gcn::FocusHandler* focusHandler)
+{
+ gcn::Widget::_setFocusHandler(focusHandler);
+
+ if (mModel)
+ for (int r = 0; r < mModel->getRows(); ++r)
+ for (int c = 0; c < mModel->getColumns(); ++c) {
+ gcn::Widget *w = mModel->getElementAt(r, c);
+ if (w)
+ w->_setFocusHandler(focusHandler);
+ }
+}
diff --git a/src/gui/table.h b/src/gui/table.h
new file mode 100644
index 00000000..cef82d5d
--- /dev/null
+++ b/src/gui/table.h
@@ -0,0 +1,150 @@
+/*
+ * The Mana World
+ * Copyright 2008 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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
+ * any later version.
+ *
+ * The Mana World is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TMW_TABLE_H_
+#define TMW_TABLE_H_
+
+#include <vector>
+
+#include <guichan/gui.hpp>
+#include <guichan/keylistener.hpp>
+#include <guichan/mouselistener.hpp>
+#include <guichan/platform.hpp>
+#include <guichan/widget.hpp>
+
+#include "table_model.h"
+#include "../guichanfwd.h"
+
+class GuiTableActionListener;
+
+/**
+ * A table, with rows and columns made out of sub-widgets. Largely inspired by (and can be thought of as a generalisation of)
+ * the guichan listbox implementation.
+ *
+ * Normally you want this within a ScrollArea.
+ *
+ * \ingroup GUI
+ */
+class GuiTable : public gcn::Widget,
+ public gcn::MouseListener,
+ public gcn::KeyListener,
+ public TableModelListener
+{
+ friend class GuiTableActionListener; // so that the action listener can call distributeActionEvent
+public:
+ GuiTable(TableModel * initial_model = NULL);
+
+ virtual ~GuiTable(void);
+
+ /**
+ * Retrieves the active table model
+ */
+ TableModel *getModel(void) const;
+
+ /**
+ * Sets the table model
+ *
+ * Note that actions issued by widgets returned from the model will update the table
+ * selection, but only AFTER any event handlers installed within the widget have been
+ *triggered. To be notified after such an update, add an action listener to the table
+ * instead.
+ */
+ void setModel(TableModel *m);
+
+ void setSelected(int row, int column);
+
+ int getSelectedRow(void);
+
+ int getSelectedColumn(void);
+
+ gcn::Rectangle getChildrenArea(void);
+
+ /**
+ * Toggle whether to use linewise selection mode, in which the table selects an entire
+ * line at a time, rather than a single cell.
+ *
+ * Note that column information is tracked even in linewise selection mode; this mode
+ * therefore only affects visualisation.
+ *
+ * Disabled by default.
+ *
+ * \param linewise: Whether to enable linewise selection mode
+ */
+ void setLinewiseSelection(bool linewise);
+
+ // Inherited from Widget
+ virtual void draw(gcn::Graphics* graphics);
+
+ virtual void logic(void);
+
+ virtual gcn::Widget *getWidgetAt(int x, int y);
+
+ virtual void moveToTop(gcn::Widget *child);
+
+ virtual void moveToBottom(gcn::Widget *child);
+
+ virtual void _setFocusHandler(gcn::FocusHandler* focusHandler);
+
+ // Inherited from KeyListener
+ virtual void keyPressed(gcn::KeyEvent& keyEvent);
+
+
+ // Inherited from MouseListener
+ virtual void mousePressed(gcn::MouseEvent& mouseEvent);
+
+ virtual void mouseWheelMovedUp(gcn::MouseEvent& mouseEvent);
+
+ virtual void mouseWheelMovedDown(gcn::MouseEvent& mouseEvent);
+
+ virtual void mouseDragged(gcn::MouseEvent& mouseEvent);
+
+ // Constraints inherited from TableModelListener
+ virtual void modelUpdated(bool);
+
+protected:
+
+ virtual void uninstallActionListeners(void); // frees all action listeners on inner widgets
+ virtual void installActionListeners(void); // installs all action listeners on inner widgets
+
+ virtual int getRowHeight(void);
+ virtual int getColumnWidth(int i);
+
+private:
+
+ int getRowForY(int y); // -1 on error
+ int getColumnForX(int x); // -1 on error
+ void recomputeDimensions(void);
+ bool mLinewiseMode;
+
+ TableModel *mModel;
+
+ int mSelectedRow;
+ int mSelectedColumn;
+
+ int mPopFramesNr; // Number of frames to skip upwards when drawing the selected widget
+
+ gcn::Widget *mTopWidget; // If someone moves a fresh widget to the top, we must display it
+
+ std::vector<GuiTableActionListener *> action_listeners; // Vector for compactness; used as a list in practice.
+};
+
+
+#endif /* !defined(TMW_TABLE_H_) */
diff --git a/src/gui/table_model.cpp b/src/gui/table_model.cpp
new file mode 100644
index 00000000..78df0636
--- /dev/null
+++ b/src/gui/table_model.cpp
@@ -0,0 +1,149 @@
+/*
+ * The Mana World
+ * Copyright 2008 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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
+ * any later version.
+ *
+ * The Mana World is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <guichan/widget.hpp>
+#include <cstdlib>
+#include "table_model.h"
+
+void
+TableModel::installListener(TableModelListener *listener)
+{
+ listeners.insert(listener);
+}
+
+void
+TableModel::removeListener(TableModelListener *listener)
+{
+ listeners.erase(listener);
+}
+
+void
+TableModel::signalBeforeUpdate(void)
+{
+ for (std::set<TableModelListener *>::const_iterator it = listeners.begin(); it != listeners.end(); it++)
+ (*it)->modelUpdated(false);
+}
+
+void
+TableModel::signalAfterUpdate(void)
+{
+ for (std::set<TableModelListener *>::const_iterator it = listeners.begin(); it != listeners.end(); it++)
+ (*it)->modelUpdated(true);
+}
+
+
+
+#define WIDGET_AT(row, column) (((row) * mColumns) + (column))
+#define DYN_SIZE(h) ((h) >= 0) // determines whether this size is tagged for auto-detection
+
+StaticTableModel::StaticTableModel(int row, int column) :
+ mRows(row),
+ mColumns(column),
+ mHeight(1)
+{
+ mTableModel.resize(row * column, NULL);
+ mWidths.resize(column, 1);
+}
+
+StaticTableModel::~StaticTableModel(void)
+{
+ for (std::vector<gcn::Widget *>::const_iterator it = mTableModel.begin(); it != mTableModel.end(); it++)
+ if (*it)
+ delete *it;
+}
+
+void
+StaticTableModel::set(int row, int column, gcn::Widget *widget)
+{
+ if (row >= mRows || row < 0
+ || column >= mColumns || row > 0)
+ // raise exn?
+ return;
+
+ if (DYN_SIZE(mHeight)
+ && widget->getHeight() > mHeight)
+ mHeight = widget->getHeight();
+
+ if (DYN_SIZE(mWidths[column])
+ && widget->getWidth() > mWidths[column])
+ mWidths[column] = widget->getWidth();
+
+ signalBeforeUpdate();
+
+ if (mTableModel[WIDGET_AT(row, column)])
+ delete mTableModel[WIDGET_AT(row, column)];
+
+ mTableModel[WIDGET_AT(row, column)] = widget;
+
+ signalAfterUpdate();
+}
+
+gcn::Widget *
+StaticTableModel::getElementAt(int row, int column)
+{
+ return mTableModel[WIDGET_AT(row, column)];
+}
+
+void
+StaticTableModel::fixColumnWidth(int column, int width)
+{
+ if (width < 0
+ || column < 0 || column >= mColumns)
+ return;
+
+ mWidths[column] = -width; // Negate to tag as fixed
+}
+
+void
+StaticTableModel::fixRowHeight(int height)
+{
+ if (height < 0)
+ return;
+
+ mHeight = -height;
+}
+
+int
+StaticTableModel::getRowHeight(void)
+{
+ return abs(mHeight);
+}
+
+int
+StaticTableModel::getColumnWidth(int column)
+{
+ if (column < 0 || column >= mColumns)
+ return 0;
+
+ return abs(mWidths[column]);
+}
+
+int
+StaticTableModel::getRows(void)
+{
+ return mRows;
+}
+
+int
+StaticTableModel::getColumns(void)
+{
+ return mColumns;
+}
diff --git a/src/gui/table_model.h b/src/gui/table_model.h
new file mode 100644
index 00000000..48ef1c0d
--- /dev/null
+++ b/src/gui/table_model.h
@@ -0,0 +1,137 @@
+/*
+ * The Mana World
+ * Copyright 2008 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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
+ * any later version.
+ *
+ * The Mana World is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TMW_TABLE_MODEL_H_
+#define TMW_TABLE_MODEL_H_
+
+#include <guichan/gui.hpp>
+#include <set>
+#include <vector>
+#include "../guichanfwd.h"
+
+class TableModelListener
+{
+public:
+ /**
+ * Must be invoked by the TableModel whenever a global change is about to occur or
+ * has occurred (e.g., when a row or column is being removed or added).
+ *
+ * This method is triggered twice, once before and once after the update.
+ *
+ * \param completed whether we are signalling the end of the update
+ */
+ virtual void modelUpdated(bool completed) = 0;
+};
+
+/**
+ * A model for a regular table of widgets.
+ */
+class TableModel
+{
+public:
+ virtual ~TableModel(void) { }
+
+ /**
+ * Determines the number of rows (lines) in the table
+ */
+ virtual int getRows(void) = 0;
+
+ /**
+ * Determines the number of columns in each row
+ */
+ virtual int getColumns(void) = 0;
+
+ /**
+ * Determines the height for each row
+ */
+ virtual int getRowHeight(void) = 0;
+
+ /**
+ * Determines the width of each individual column
+ */
+ virtual int getColumnWidth(int index) = 0;
+
+ /**
+ * Retrieves the widget stored at the specified location within the table.
+ */
+ virtual gcn::Widget *getElementAt(int row, int column) = 0;
+
+ virtual void installListener(TableModelListener *listener);
+
+ virtual void removeListener(TableModelListener *listener);
+
+protected:
+ /**
+ * Tells all listeners that the table is about to see an update
+ */
+ virtual void signalBeforeUpdate(void);
+
+ /**
+ * Tells all listeners that the table has seen an update
+ */
+ virtual void signalAfterUpdate(void);
+
+private:
+ std::set<TableModelListener *> listeners;
+};
+
+
+class StaticTableModel : public TableModel
+{
+public:
+ StaticTableModel(int width, int height);
+ virtual ~StaticTableModel(void);
+
+ /**
+ * Inserts a widget into the table model.
+ * The model is resized to accomodate the widget's width and height, unless column width / row height have been fixed.
+ */
+ virtual void set(int row, int column, gcn::Widget *widget);
+
+ /**
+ * Fixes the column width for a given column; this overrides dynamic width inference.
+ *
+ * Semantics are undefined for width 0.
+ */
+ virtual void fixColumnWidth(int column, int width);
+
+ /**
+ * Fixes the row height; this overrides dynamic height inference.
+ *
+ * Semantics are undefined for width 0.
+ */
+ virtual void fixRowHeight(int height);
+
+ virtual int getRows();
+ virtual int getColumns();
+ virtual int getRowHeight();
+ virtual int getColumnWidth(int index);
+ virtual gcn::Widget *getElementAt(int row, int column);
+
+
+protected:
+ int mRows, mColumns;
+ int mHeight;
+ std::vector<gcn::Widget *> mTableModel;
+ std::vector<int> mWidths;
+};
+
+#endif /* !defined(TMW_TABLE_MODEL_H_) */
diff --git a/src/main.cpp b/src/main.cpp
index 9e75090c..01250594 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -46,6 +46,7 @@
#include "configuration.h"
#include "keyboardconfig.h"
+#include "player_relations.h"
#include "game.h"
#include "graphics.h"
#include "itemshortcut.h"
@@ -331,6 +332,9 @@ void init_engine(const Options &options)
// Initialize keyboard
keyboard.init();
+
+ // Initialise player relations
+ player_relations.init();
}
/** Clear the engine */
diff --git a/src/net/beinghandler.cpp b/src/net/beinghandler.cpp
index 4c77523a..959a4972 100644
--- a/src/net/beinghandler.cpp
+++ b/src/net/beinghandler.cpp
@@ -36,6 +36,7 @@
#include "../main.h"
#include "../particle.h"
#include "../sound.h"
+#include "../player_relations.h"
const int EMOTION_TIME = 150; /**< Duration of emotion icon */
@@ -245,8 +246,9 @@ void BeingHandler::handleMessage(MessageIn *msg)
break;
}
- dstBeing->mEmotion = msg->readInt8();
- dstBeing->mEmotionTime = EMOTION_TIME;
+ if (player_relations.hasPermission(dstBeing, PlayerRelation::EMOTE))
+ dstBeing->setEmote(msg->readInt8(), EMOTION_TIME);
+
break;
case SMSG_BEING_CHANGE_LOOKS:
diff --git a/src/net/chathandler.cpp b/src/net/chathandler.cpp
index d2e1361e..524911d3 100644
--- a/src/net/chathandler.cpp
+++ b/src/net/chathandler.cpp
@@ -32,6 +32,7 @@
#include "../being.h"
#include "../beingmanager.h"
#include "../game.h"
+#include "../player_relations.h"
#include "../gui/chat.h"
@@ -40,6 +41,8 @@
extern Being *player_node;
+#define SERVER_NAME "Server"
+
ChatHandler::ChatHandler()
{
static const Uint16 _messages[] = {
@@ -89,14 +92,20 @@ void ChatHandler::handleMessage(MessageIn *msg)
break;
chatMsg = msg->readString(chatMsgLength);
- if (nick != "Server")
+ if (nick != SERVER_NAME)
chatMsg = nick + " : " + chatMsg;
- chatWindow->chatLog(chatMsg, (nick == "Server") ? BY_SERVER : ACT_WHISPER);
+
+ if (nick == SERVER_NAME)
+ chatWindow->chatLog(chatMsg, BY_SERVER);
+ else {
+ if (player_relations.hasPermission(nick, PlayerRelation::WHISPER))
+ chatWindow->chatLog(chatMsg, ACT_WHISPER);
+ }
break;
// Received speech from being
- case SMSG_BEING_CHAT:
+ case SMSG_BEING_CHAT: {
chatMsgLength = msg->readInt16() - 8;
being = beingManager->findBeing(msg->readInt32());
@@ -106,14 +115,27 @@ void ChatHandler::handleMessage(MessageIn *msg)
}
chatMsg = msg->readString(chatMsgLength);
- chatWindow->chatLog(chatMsg, BY_OTHER);
- chatMsg.erase(0, chatMsg.find(" : ", 0) + 3);
+
+ std::string::size_type pos = chatMsg.find(" : ", 0);
+ std::string sender_name = ((pos == std::string::npos)
+ ? ""
+ : chatMsg.substr(0, pos));
+
+ // We use getIgnorePlayer instead of ignoringPlayer here because ignorePlayer' side
+ // effects are triggered right below for Being::IGNORE_SPEECH_FLOAT.
+ if (player_relations.checkPermissionSilently(sender_name, PlayerRelation::SPEECH_LOG))
+ chatWindow->chatLog(chatMsg, BY_OTHER);
+
+ chatMsg.erase(0, pos + 3);
trim(chatMsg);
- being->setSpeech(chatMsg, SPEECH_TIME);
+
+ if (player_relations.hasPermission(sender_name, PlayerRelation::SPEECH_FLOAT))
+ being->setSpeech(chatMsg, SPEECH_TIME);
break;
+ }
case SMSG_PLAYER_CHAT:
- case SMSG_GM_CHAT:
+ case SMSG_GM_CHAT: {
chatMsgLength = msg->readInt16() - 4;
if (chatMsgLength <= 0)
@@ -122,17 +144,17 @@ void ChatHandler::handleMessage(MessageIn *msg)
}
chatMsg = msg->readString(chatMsgLength);
+ std::string::size_type pos = chatMsg.find(" : ", 0);
if (msg->getId() == SMSG_PLAYER_CHAT)
{
chatWindow->chatLog(chatMsg, BY_PLAYER);
- std::string::size_type pos = chatMsg.find(" : ", 0);
if (pos != std::string::npos)
- {
chatMsg.erase(0, pos + 3);
- }
+
trim(chatMsg);
+
player_node->setSpeech(chatMsg, SPEECH_TIME);
}
else
@@ -140,6 +162,7 @@ void ChatHandler::handleMessage(MessageIn *msg)
chatWindow->chatLog(chatMsg, BY_GM);
}
break;
+ }
case SMSG_WHO_ANSWER:
chatWindow->chatLog("Online users: " + toString(msg->readInt32()),
diff --git a/src/net/tradehandler.cpp b/src/net/tradehandler.cpp
index 57060684..9b3c590e 100644
--- a/src/net/tradehandler.cpp
+++ b/src/net/tradehandler.cpp
@@ -28,6 +28,7 @@
#include "../item.h"
#include "../localplayer.h"
+#include "../player_relations.h"
#include "../gui/chat.h"
#include "../gui/confirm_dialog.h"
@@ -48,8 +49,7 @@ namespace {
} listener;
}
-TradeHandler::TradeHandler():
- mAcceptTradeRequests(true)
+TradeHandler::TradeHandler()
{
static const Uint16 _messages[] = {
SMSG_TRADE_REQUEST,
@@ -64,15 +64,6 @@ TradeHandler::TradeHandler():
handledMessages = _messages;
}
-void TradeHandler::setAcceptTradeRequests(bool acceptTradeRequests)
-{
- mAcceptTradeRequests = acceptTradeRequests;
- if (mAcceptTradeRequests) {
- chatWindow->chatLog("Accepting incoming trade requests", BY_SERVER);
- } else {
- chatWindow->chatLog("Ignoring incoming trade requests", BY_SERVER);
- }
-}
void TradeHandler::handleMessage(MessageIn *msg)
{
@@ -87,7 +78,7 @@ void TradeHandler::handleMessage(MessageIn *msg)
// special message about the player being occupied.
tradePartnerName = msg->readString(24);
- if (mAcceptTradeRequests)
+ if (player_relations.hasPermission(tradePartnerName, PlayerRelation::TRADE))
{
if (!player_node->tradeRequestOk())
{
diff --git a/src/net/tradehandler.h b/src/net/tradehandler.h
index 00f2cf38..a1971004 100644
--- a/src/net/tradehandler.h
+++ b/src/net/tradehandler.h
@@ -34,24 +34,6 @@ class TradeHandler : public MessageHandler
TradeHandler();
void handleMessage(MessageIn *msg);
-
- /**
- * Returns whether trade requests are accepted.
- *
- * @see setAcceptTradeRequests
- */
- bool acceptTradeRequests() const
- { return mAcceptTradeRequests; }
-
- /**
- * Sets whether trade requests are accepted. When set to false, trade
- * requests are automatically denied. When true, a popup will ask the
- * player whether he wants to trade.
- */
- void setAcceptTradeRequests(bool acceptTradeRequests);
-
- private:
- bool mAcceptTradeRequests;
};
#endif
diff --git a/src/player.cpp b/src/player.cpp
index 3d8a8b6e..d0c6bdc6 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -36,7 +36,8 @@
#include "gui/gui.h"
Player::Player(int id, int job, Map *map):
- Being(id, job, map)
+ Being(id, job, map),
+ mDrawStrategy(NULL)
{
}
@@ -74,15 +75,37 @@ Player::getType() const
return PLAYER;
}
+
+void
+Player::setNameDrawStrategy(PlayerNameDrawStrategy *draw_strategy)
+{
+ if (mDrawStrategy)
+ delete mDrawStrategy;
+ mDrawStrategy = draw_strategy;
+}
+
+class
+DefaultPlayerNameDrawStrategy : public PlayerNameDrawStrategy
+{
+public:
+ virtual void draw(Player *player, Graphics *graphics, int px, int py)
+ {
+ graphics->setFont(speechFont);
+ graphics->setColor(gcn::Color(255, 255, 255));
+ graphics->drawText(player->getName(), px + 15, py + 30, gcn::Graphics::CENTER);
+ }
+};
+
void
Player::drawName(Graphics *graphics, int offsetX, int offsetY)
{
int px = mPx + offsetX;
int py = mPy + offsetY;
- graphics->setFont(speechFont);
- graphics->setColor(gcn::Color(255, 255, 255));
- graphics->drawText(mName, px + 15, py + 30, gcn::Graphics::CENTER);
+ if (mDrawStrategy)
+ mDrawStrategy->draw(this, graphics, px, py);
+ else
+ DefaultPlayerNameDrawStrategy().draw(this, graphics, px, py);
}
void Player::setGender(int gender)
diff --git a/src/player.h b/src/player.h
index e9a30da0..f43a6039 100644
--- a/src/player.h
+++ b/src/player.h
@@ -29,6 +29,19 @@
class Graphics;
class Map;
+class Player;
+
+class PlayerNameDrawStrategy
+{
+public:
+ virtual ~PlayerNameDrawStrategy(void) {}
+
+ /**
+ * Draw the player's name
+ */
+ virtual void draw(Player *p, Graphics *graphics, int px, int py) = 0;
+};
+
/**
* A player being. Players have their name drawn beneath them. This class also
* implements player-specific loading of base sprite, hair sprite and equipment
@@ -68,6 +81,21 @@ class Player : public Being
*/
virtual void
setSprite(int slot, int id, std::string color = "");
+
+ /**
+ * Sets the strategy responsible for drawing the player's name
+ *
+ * \param draw_strategy A strategy describing how the player's name
+ * should be drawn, or NULL for default
+ */
+ virtual void
+ setNameDrawStrategy(PlayerNameDrawStrategy *draw_strategy);
+
+ virtual PlayerNameDrawStrategy *
+ getNameDrawStrategy(void) const { return mDrawStrategy; }
+
+ private:
+ PlayerNameDrawStrategy *mDrawStrategy;
};
#endif
diff --git a/src/player_relations.cpp b/src/player_relations.cpp
new file mode 100644
index 00000000..fe71191c
--- /dev/null
+++ b/src/player_relations.cpp
@@ -0,0 +1,406 @@
+/*
+ * The Mana World
+ * Copyright 2008 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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
+ * any later version.
+ *
+ * The Mana World is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "beingmanager.h"
+#include "player_relations.h"
+#include "graphics.h"
+#include "gui/gui.h"
+
+#include <algorithm>
+
+#define PLAYER_IGNORE_STRATEGY_NOP "nop"
+#define PLAYER_IGNORE_STRATEGY_EMOTE0 "emote0"
+#define DEFAULT_IGNORE_STRATEGY PLAYER_IGNORE_STRATEGY_EMOTE0
+
+#define NAME "name" // constant for xml serialisation
+#define RELATION "relation" // constant for xml serialisation
+
+#define IGNORE_EMOTE_TIME 100
+
+
+// (De)serialisation class
+class PlayerConfSerialiser : public ConfigurationListManager<std::pair<std::string, PlayerRelation *>,
+ std::map<std::string, PlayerRelation *> *>
+{
+ virtual ConfigurationObject *writeConfigItem(std::pair<std::string, PlayerRelation *> value,
+ ConfigurationObject *cobj)
+ {
+ if (!value.second)
+ return NULL;
+ cobj->setValue(NAME, value.first);
+ cobj->setValue(RELATION, value.second->mRelation);
+
+ return cobj;
+ }
+
+ virtual std::map<std::string, PlayerRelation *> *
+ readConfigItem(ConfigurationObject *cobj,
+ std::map<std::string, PlayerRelation *> *container)
+ {
+ std::string name = cobj->getValue(NAME, "");
+ if (name == "")
+ return container;
+
+ if (!(*container)[name]) {
+ int v = cobj->getValue(RELATION, PlayerRelation::NEUTRAL);
+ (*container)[name] = new PlayerRelation(static_cast<PlayerRelation::relation>(v));
+ }
+ // otherwise ignore the duplicate entry
+
+ return container;
+ }
+};
+
+static PlayerConfSerialiser player_conf_serialiser; // stateless singleton
+
+const unsigned int PlayerRelation::RELATION_PERMISSIONS[RELATIONS_NR] = {
+ /* NEUTRAL */ 0, // we always fall back to the defaults anyway
+ /* FRIEND */ EMOTE | SPEECH_FLOAT | SPEECH_LOG | WHISPER | TRADE,
+ /* DISREGARDED*/ EMOTE | SPEECH_FLOAT,
+ /* IGNORED */ 0
+};
+
+PlayerRelation::PlayerRelation(relation relation)
+{
+ mRelation = relation;
+}
+
+PlayerRelationsManager::PlayerRelationsManager(void) :
+ mPersistIgnores(false),
+ mDefaultPermissions(PlayerRelation::DEFAULT),
+ mIgnoreStrategy(NULL)
+{
+}
+
+void
+PlayerRelationsManager::clear(void)
+{
+ std::vector<std::string> *names = getPlayers();
+ for (std::vector<std::string>::const_iterator
+ it = names->begin(); it != names->end(); it++)
+ removePlayer(*it);
+ delete names;
+}
+
+#define PERSIST_IGNORE_LIST "persist-player-list"
+#define PLAYER_IGNORE_STRATEGY "player-ignore-strategy"
+#define DEFAULT_PERMISSIONS "default-player-permissions"
+
+int
+PlayerRelationsManager::getPlayerIgnoreStrategyIndex(const std::string &name)
+{
+ std::vector<PlayerIgnoreStrategy *> *strategies = getPlayerIgnoreStrategies();
+ for (unsigned int i = 0; i < strategies->size(); i++)
+ if ((*strategies)[i]->mShortName == name)
+ return i;
+
+ return -1;
+}
+
+void
+PlayerRelationsManager::load(void)
+{
+ clear();
+
+ mPersistIgnores = config.getValue(PERSIST_IGNORE_LIST, 0);
+ mDefaultPermissions = config.getValue(DEFAULT_PERMISSIONS, mDefaultPermissions);
+ std::string ignore_strategy_name = config.getValue(PLAYER_IGNORE_STRATEGY, DEFAULT_IGNORE_STRATEGY);
+ int ignore_strategy_index = getPlayerIgnoreStrategyIndex(ignore_strategy_name);
+ if (ignore_strategy_index >= 0)
+ setPlayerIgnoreStrategy((*getPlayerIgnoreStrategies())[ignore_strategy_index]);
+
+ config.getList<std::pair<std::string, PlayerRelation *>,
+ std::map<std::string, PlayerRelation *> *>
+ ("player", &(mRelations), &player_conf_serialiser);
+}
+
+
+void
+PlayerRelationsManager::init(void)
+{
+ load();
+
+ if (!mPersistIgnores)
+ clear(); // Yes, we still keep them around in the config file until the next update.
+}
+
+void
+PlayerRelationsManager::store(void)
+{
+ config.setList<std::map<std::string, PlayerRelation *>::const_iterator,
+ std::pair<std::string, PlayerRelation *>,
+ std::map<std::string, PlayerRelation *> *>
+ ("player",
+ mRelations.begin(), mRelations.end(),
+ &player_conf_serialiser);
+
+ config.setValue(DEFAULT_PERMISSIONS, mDefaultPermissions);
+ config.setValue(PERSIST_IGNORE_LIST, mPersistIgnores);
+ config.setValue(PLAYER_IGNORE_STRATEGY,
+ (mIgnoreStrategy)? mIgnoreStrategy->mShortName : DEFAULT_IGNORE_STRATEGY);
+
+ config.write();
+}
+
+void
+PlayerRelationsManager::signalUpdate(const std::string &name)
+{
+ store();
+
+ for (std::list<PlayerRelationsListener *>::const_iterator it = mListeners.begin(); it != mListeners.end(); it++)
+ (*it)->updatedPlayer(name);
+}
+
+unsigned int
+PlayerRelationsManager::checkPermissionSilently(const std::string &player_name, unsigned int flags)
+{
+ PlayerRelation *r = mRelations[player_name];
+ if (!r)
+ return mDefaultPermissions & flags;
+ else {
+ unsigned int permissions = PlayerRelation::RELATION_PERMISSIONS[r->mRelation];
+
+ switch (r->mRelation) {
+ case PlayerRelation::NEUTRAL:
+ permissions = mDefaultPermissions;
+ break;
+
+ case PlayerRelation::FRIEND:
+ permissions |= mDefaultPermissions; // widen
+ break;
+
+ default:
+ permissions &= mDefaultPermissions; // narrow
+ }
+
+ return permissions & flags;
+ }
+}
+
+bool
+PlayerRelationsManager::hasPermission(Being *being, unsigned int flags)
+{
+ if (being->getType() == Being::PLAYER)
+ return hasPermission(being->getName(), flags) == flags;
+ return true;
+}
+
+bool
+PlayerRelationsManager::hasPermission(const std::string &name, unsigned int flags)
+{
+ unsigned int rejections = flags & ~checkPermissionSilently(name, flags);
+ bool permitted = rejections == 0;
+
+ if (!permitted) {
+ // execute `ignore' strategy, if possible
+ if (mIgnoreStrategy)
+ mIgnoreStrategy->ignore(dynamic_cast<Player *>(beingManager->findBeingByName(name, Being::PLAYER)),
+ rejections);
+ }
+
+ return permitted;
+}
+
+void
+PlayerRelationsManager::setRelation(const std::string &player_name, PlayerRelation::relation relation)
+{
+ PlayerRelation *r = mRelations[player_name];
+ if (r == NULL)
+ mRelations[player_name] = new PlayerRelation(relation);
+ else
+ r->mRelation = relation;
+
+ signalUpdate(player_name);
+}
+
+std::vector<std::string> *
+PlayerRelationsManager::getPlayers(void)
+{
+ std::vector<std::string> *retval = new std::vector<std::string>();
+
+ for (std::map<std::string, PlayerRelation *>::const_iterator it = mRelations.begin(); it != mRelations.end(); it++)
+ if (it->second)
+ retval->push_back(it->first);
+
+ sort(retval->begin(), retval->end());
+
+ return retval;
+}
+
+void
+PlayerRelationsManager::removePlayer(const std::string &name)
+{
+ if (mRelations[name])
+ delete mRelations[name];
+
+ mRelations.erase(name);
+
+ signalUpdate(name);
+}
+
+
+PlayerRelation::relation
+PlayerRelationsManager::getRelation(const std::string &name)
+{
+ if (mRelations[name])
+ return mRelations[name]->mRelation;
+
+ return PlayerRelation::NEUTRAL;
+}
+
+////////////////////////////////////////
+// defaults
+
+unsigned int
+PlayerRelationsManager::getDefault(void) const
+{
+ return mDefaultPermissions;
+}
+
+void
+PlayerRelationsManager::setDefault(unsigned int permissions)
+{
+ mDefaultPermissions = permissions;
+
+ store();
+ signalUpdate("");
+}
+
+
+////////////////////////////////////////
+// ignore strategies
+
+
+class PIS_nothing : public PlayerIgnoreStrategy
+{
+public:
+ PIS_nothing()
+ {
+ mDescription = "completely ignore";
+ mShortName = PLAYER_IGNORE_STRATEGY_NOP;
+ }
+
+ virtual void
+ ignore(Player *player, unsigned int flags)
+ {
+ }
+};
+
+class PIS_dotdotdot : public PlayerIgnoreStrategy
+{
+public:
+ PIS_dotdotdot()
+ {
+ mDescription = "print '...'";
+ mShortName = "dotdotdot";
+ }
+
+ virtual void
+ ignore(Player *player, unsigned int flags)
+ {
+ player->setSpeech("...", 5);
+ }
+};
+
+
+class
+BlinkPlayerNameDrawStrategy : public PlayerNameDrawStrategy
+{
+public:
+ BlinkPlayerNameDrawStrategy(int count) :
+ mCount(count)
+ {
+ }
+
+ virtual void draw(Player *player, Graphics *graphics, int px, int py)
+ {
+ graphics->setFont(speechFont);
+ if (mCount & 4)
+ graphics->drawText(player->getName(), px + 15, py + 30, gcn::Graphics::CENTER);
+
+ if (mCount-- <= 0)
+ player->setNameDrawStrategy(NULL);
+ }
+private:
+ int mCount; // Number of steps to blink
+};
+
+
+class PIS_blinkname : public PlayerIgnoreStrategy
+{
+public:
+ PIS_blinkname()
+ {
+ mDescription = "blink name";
+ mShortName = "blinkname";
+ }
+
+ virtual void
+ ignore(Player *player, unsigned int flags)
+ {
+ player->setNameDrawStrategy(new BlinkPlayerNameDrawStrategy(200));
+ }
+};
+
+class PIS_emote : public PlayerIgnoreStrategy
+{
+public:
+ PIS_emote(int emote_nr, const std::string &description, const std::string &shortname) :
+ mEmotion(emote_nr)
+ {
+ mDescription = description;
+ mShortName = shortname;
+ }
+
+ virtual void
+ ignore(Player *player, unsigned int flags)
+ {
+ player->setEmote(mEmotion, IGNORE_EMOTE_TIME);
+ }
+private:
+ int mEmotion;
+};
+
+
+
+static std::vector<PlayerIgnoreStrategy *> player_ignore_strategies;
+
+std::vector<PlayerIgnoreStrategy *> *
+PlayerRelationsManager::getPlayerIgnoreStrategies(void)
+{
+ if (player_ignore_strategies.size() == 0) {
+ // not initialised yet?
+ player_ignore_strategies.push_back(new PIS_emote(FIRST_IGNORE_EMOTE,
+ "floating '...' bubble",
+ PLAYER_IGNORE_STRATEGY_EMOTE0));
+ player_ignore_strategies.push_back(new PIS_emote(FIRST_IGNORE_EMOTE + 1,
+ "floating bubble",
+ "emote1"));
+ player_ignore_strategies.push_back(new PIS_nothing());
+ player_ignore_strategies.push_back(new PIS_dotdotdot());
+ player_ignore_strategies.push_back(new PIS_blinkname());
+ }
+ return &player_ignore_strategies;
+}
+
+
+PlayerRelationsManager player_relations;
+
diff --git a/src/player_relations.h b/src/player_relations.h
new file mode 100644
index 00000000..99c16020
--- /dev/null
+++ b/src/player_relations.h
@@ -0,0 +1,226 @@
+/*
+ * The Mana World
+ * Copyright 2008 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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
+ * any later version.
+ *
+ * The Mana World is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TMW_PLAYER_RELATIONS_H_
+#define TMW_PLAYER_RELATIONS_H_
+
+#include "being.h"
+#include "player.h"
+#include "configuration.h"
+#include <string>
+#include <map>
+#include <vector>
+#include <list>
+
+struct PlayerRelation
+{
+ static const unsigned int EMOTE = (1 << 0);
+ static const unsigned int SPEECH_FLOAT = (1 << 1);
+ static const unsigned int SPEECH_LOG = (1 << 2);
+ static const unsigned int WHISPER = (1 << 3);
+ static const unsigned int TRADE = (1 << 4);
+
+ static const unsigned int RELATIONS_NR = 4;
+ static const unsigned int RELATION_PERMISSIONS[RELATIONS_NR];
+
+ static const unsigned int DEFAULT = EMOTE
+ | SPEECH_FLOAT
+ | SPEECH_LOG
+// | WHISPER -- only for friends
+ | TRADE;
+ enum relation {
+ NEUTRAL = 0,
+ FRIEND = 1,
+ DISREGARDED = 2,
+ IGNORED = 3
+ };
+
+ PlayerRelation(relation relation);
+
+ relation mRelation; // bitmask for all of the above
+};
+
+
+/**
+ * Ignore strategy: describes how we should handle ignores
+ */
+class
+PlayerIgnoreStrategy
+{
+public:
+ std::string mDescription;
+ std::string mShortName;
+
+ /**
+ * Handle the ignoring of the indicated action by the indicated player
+ */
+ virtual void ignore(Player *player, unsigned int flags) = 0;
+};
+
+class
+PlayerRelationsListener
+{
+public:
+ PlayerRelationsListener(void) { }
+ virtual ~PlayerRelationsListener(void) { }
+
+ virtual void updatedPlayer(const std::string &name) = 0;
+};
+
+/**
+ * Player relations class, represents any particular relations and/or preferences the
+ * user of the local client has wrt other players (identified by std::string).
+ */
+class
+PlayerRelationsManager
+{
+public:
+ PlayerRelationsManager(void);
+
+ /**
+ * Initialise player relations manager (load config file etc.)
+ */
+ void init(void);
+
+ /**
+ * Load configuration from our config file, or substitute defaults
+ */
+ void load(void);
+
+ /**
+ * Save configuration to our config file
+ */
+ void store(void);
+
+ /**
+ * Determines whether the player in question is being ignored, filtered by the specified flags.
+ *
+ */
+ unsigned int checkPermissionSilently(const std::string &player_name, unsigned int flags);
+
+ /**
+ * Tests whether the player in question is being ignored for any of the actions in the specified flags.
+ * If so, trigger appropriate side effects if requested by the player.
+ */
+ bool hasPermission(Being *being, unsigned int flags);
+
+ bool hasPermission(const std::string &being, unsigned int flags);
+
+ /**
+ * Updates the relationship with this player
+ */
+ void setRelation(const std::string &name, PlayerRelation::relation relation);
+
+ /**
+ * Updates the relationship with this player
+ */
+ PlayerRelation::relation getRelation(const std::string &name);
+
+ /**
+ * Deletes the information recorded for a player
+ */
+ void removePlayer(const std::string &name);
+
+
+
+ /**
+ * Retrieves the default permissions
+ */
+ unsigned int getDefault(void) const;
+
+ /**
+ * Sets the default permissions
+ */
+ void setDefault(unsigned int permissions);
+
+
+
+ /**
+ * Retrieves all known player ignore strategies
+ *
+ * The player ignore strategies are allocated statically and must not be deleted.
+ */
+ std::vector<PlayerIgnoreStrategy *> *getPlayerIgnoreStrategies(void);
+
+ /**
+ * Return the current player ignore strategy
+ *
+ * \return A player ignore strategy, or NULL
+ */
+ PlayerIgnoreStrategy *getPlayerIgnoreStrategy(void) { return mIgnoreStrategy; }
+
+ /**
+ * Sets the strategy to call when ignoring players
+ */
+ void setPlayerIgnoreStrategy(PlayerIgnoreStrategy *strategy) { mIgnoreStrategy = strategy; }
+
+ /**
+ * For a given ignore strategy short name, find the appropriate index in the ignore strategies vector
+ *
+ * \param The short name of the ignore strategy to look up
+ * \return The appropriate index, or -1
+ */
+ int getPlayerIgnoreStrategyIndex(const std::string &shortname);
+
+ /**
+ * Retrieves a sorted vector of all players for which we have any relations recorded
+ */
+ std::vector<std::string> *getPlayers(void);
+
+ /**
+ * Removes all recorded player info
+ */
+ void clear(void);
+
+ /**
+ * Do we persist our `ignore' setup?
+ *
+ */
+ bool
+ getPersistIgnores(void) const { return mPersistIgnores; }
+
+ /**
+ * Change the `ignore persist' flag
+ *
+ * @param value Whether to persist ignores
+ */
+ void
+ setPersistIgnores(bool value) { mPersistIgnores = value; }
+
+ void addListener(PlayerRelationsListener *listener) { mListeners.push_back(listener); }
+ void removeListener(PlayerRelationsListener *listener) { mListeners.remove(listener); }
+
+private:
+ void signalUpdate(const std::string &name);
+
+ bool mPersistIgnores; // If NOT set, we delete the ignored data upon reloading
+ unsigned int mDefaultPermissions;
+
+ PlayerIgnoreStrategy *mIgnoreStrategy;
+ std::map<std::string, PlayerRelation *> mRelations;
+ std::list<PlayerRelationsListener *> mListeners;
+};
+
+
+extern PlayerRelationsManager player_relations; // singleton representation of player relations
+
+
+#endif /* !defined(TMW_PLAYER_RELATIONS_H_) */