diff options
32 files changed, 2656 insertions, 121 deletions
@@ -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 Binary files differnew file mode 100644 index 00000000..146f2d24 --- /dev/null +++ b/data/graphics/gui/emotions.png 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_) */ |