summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjørn Lindeijer <bjorn@lindeijer.nl>2008-05-14 18:57:32 +0000
committerBjørn Lindeijer <bjorn@lindeijer.nl>2008-05-14 18:57:32 +0000
commit2d648c5dc29a1ceae154194c23c799c7076894b4 (patch)
treef6c31a30260b80713257be211b139263b3291098
parent41906acb990895831e3b2c39102f41c9b580ae10 (diff)
downloadmana-client-2d648c5dc29a1ceae154194c23c799c7076894b4.tar.gz
mana-client-2d648c5dc29a1ceae154194c23c799c7076894b4.tar.bz2
mana-client-2d648c5dc29a1ceae154194c23c799c7076894b4.tar.xz
mana-client-2d648c5dc29a1ceae154194c23c799c7076894b4.zip
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.
-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_) */