From a6f9773c358dd7d091ff64cbda504ab8d8066dd3 Mon Sep 17 00:00:00 2001 From: Stijn Buys Date: Wed, 22 Sep 2010 21:32:34 +0000 Subject: full trading support for networked games --- src/client/inventorylistview.cc | 81 ++++++++++++++++++++++++---------- src/client/inventorylistview.h | 3 ++ src/client/map.cc | 2 +- src/client/playerview.cc | 2 +- src/client/trademenu.cc | 12 ++--- src/client/video.cc | 4 +- src/core/entity.cc | 36 ++++++++++++++- src/core/gameconnection.cc | 53 +++++----------------- src/core/gameconnection.h | 13 +++--- src/core/gameinterface.h | 13 +++--- src/core/gameserver.cc | 10 ++--- src/core/gameserver.h | 10 ++--- src/core/info.cc | 26 ++++++++++- src/core/info.h | 13 ++++-- src/core/inventory.cc | 20 +++++++-- src/core/inventory.h | 24 +++++++--- src/core/item.cc | 25 ++++++++++- src/core/item.h | 24 ++++++++-- src/core/net.h | 7 +++ src/core/netclient.h | 6 +-- src/core/netconnection.cc | 92 +++++++++++++++++++++++++++++++++++++- src/core/netconnection.h | 7 ++- src/core/netplayer.cc | 1 + src/core/netserver.cc | 97 ++++++++++++++++++++++++++++++++++++++--- src/core/netserver.h | 12 ++--- src/ui/listview.cc | 4 +- 26 files changed, 457 insertions(+), 140 deletions(-) (limited to 'src') diff --git a/src/client/inventorylistview.cc b/src/client/inventorylistview.cc index d87ed2b..e2b613b 100644 --- a/src/client/inventorylistview.cc +++ b/src/client/inventorylistview.cc @@ -4,14 +4,20 @@ the terms and conditions of the GNU General Public License version 2 */ +#include "core/application.h" #include "client/inventorylistview.h" #include "ui/listitem.h" +#include "ui/paint.h" namespace client { InventoryListView::InventoryListView(ui::Widget *parent) : ui::ListView (parent) { + listview_inventory = 0; + listview_infotype = 0; listview_timestamp = 0; + listview_infotimestamp = 0; + set_inventory(0, 0); } @@ -22,55 +28,82 @@ void InventoryListView::set_inventory(core::Inventory *inventory, core::InfoType { remove_children(); + deselect(); + + listview_timestamp = 0; + listview_infotimestamp = 0; listview_inventory = inventory; listview_infotype = infotype; - - const core::Item *selecteditem = (selected() ? selected()->item() : 0); - - if (!inventory || !infotype) { + + if (!listview_inventory || !listview_infotype) { return; } - // TODO scan the inventories and request updated infos - // update when necessary + const core::Item *selecteditem = (selected() ? selected()->item() : 0); + listview_timestamp = inventory->timestamp(); for (core::Inventory::Items::const_iterator it = inventory->items().begin(); it != inventory->items().end(); it++) { core::Item *item = (*it); - if (item->info() && (item->info()->type() == infotype) && (item->amount() != 0) ) { - ui::ListItem *listitem = 0; + if (item->info()) { - std::ostringstream str; - str << item->info()->name().c_str(); + // this makes sure inventory info gets requested from the server + core::game()->request_info(item->info()->id()); - if (item->amount() > 0) { - str << " (" << item->amount() << ")"; + if (item->info()->timestamp() > listview_infotimestamp) { + listview_infotimestamp = item->info()->timestamp(); } - listitem = new ui::ListItem(this, str.str().c_str()); - listitem->set_height(listitem->font()->height() * 2.0f); - listitem->set_item(item); - listitem->set_info(item->info()); - // preserve previous selection during update - if (item == selecteditem) { - ui::ListView::select(listitem); + + if ((item->info()->type() == infotype) && (item->amount() != 0)) { + ui::ListItem *listitem = 0; + + std::ostringstream str; + str << item->info()->name().c_str(); + + if (item->amount() > 0) { + str << " (" << item->amount() << ")"; + } + listitem = new ui::ListItem(this, str.str().c_str()); + listitem->set_height(listitem->font()->height() * 2.0f); + listitem->set_item(item); + listitem->set_info(item->info()); + // preserve previous selection during update + if (item == selecteditem) { + ui::ListView::select(listitem); + } } } } - listview_timestamp = inventory->timestamp(); - event_resize(); emit(EventListViewChanged); } +bool InventoryListView::verify_inventory() +{ + for (core::Inventory::Items::const_iterator it = listview_inventory->items().begin(); it != listview_inventory->items().end(); it++) { + core::Item *item = (*it); + if (item->info() && (item->info()->timestamp() > listview_infotimestamp)) { + return false; + } + } + return true; +} + void InventoryListView::draw() { - if (listview_inventory && (listview_timestamp != listview_inventory->timestamp())) { - set_inventory(listview_inventory, listview_infotype); + if (listview_inventory && listview_infotype) { + // inventory was updated + if (listview_timestamp != listview_inventory->timestamp()) { + set_inventory(listview_inventory, listview_infotype); + // inventory info was updated + } else if (!verify_inventory()) { + set_inventory(listview_inventory, listview_infotype); + } } ListView::draw(); } -} // namespace client \ No newline at end of file +} // namespace client diff --git a/src/client/inventorylistview.h b/src/client/inventorylistview.h index ff10aa1..5d5a85f 100644 --- a/src/client/inventorylistview.h +++ b/src/client/inventorylistview.h @@ -30,7 +30,10 @@ protected: virtual void draw(); private: + bool verify_inventory(); + unsigned long listview_timestamp; + unsigned long listview_infotimestamp; core::Inventory *listview_inventory; core::InfoType *listview_infotype; }; diff --git a/src/client/map.cc b/src/client/map.cc index 46f96fd..54afc3f 100644 --- a/src/client/map.cc +++ b/src/client/map.cc @@ -266,7 +266,7 @@ void Map::set_target(const core::Entity *entity) { map_targetlabel->show(); if (map_target->info()) - map_inforecord = core::game()->info(map_target->info()->id()); + map_inforecord = core::game()->request_info(map_target->info()->id()); else map_inforecord = 0; diff --git a/src/client/playerview.cc b/src/client/playerview.cc index b5629f0..7791a2a 100644 --- a/src/client/playerview.cc +++ b/src/client/playerview.cc @@ -129,7 +129,7 @@ void PlayerView::show_menu(const std::string & args) view_trademenu->hide(); // requesting the info through game makes sure it is retreived from the server - view_buymenu->set_item( core::game()->info(id) ); + view_buymenu->set_item( core::game()->request_info(id)); // show buy menu view_buymenu->show(); } else { diff --git a/src/client/trademenu.cc b/src/client/trademenu.cc index 0b8fb64..91724f7 100644 --- a/src/client/trademenu.cc +++ b/src/client/trademenu.cc @@ -80,6 +80,7 @@ TradeMenu::TradeMenu(ui::Widget *parent, const char * label) : ui::Window(parent menu_listitem = 0; set_itemtype(0); + set_item(0); hide(); } @@ -97,17 +98,16 @@ void TradeMenu::set_itemtype(core::InfoType *item_type) core::Inventory *inventory_player = 0; core::Inventory *inventory_view = 0; - if (core::localcontrol()) - inventory_player = core::localcontrol()->inventory(); + inventory_player = core::game()->request_inventory(core::localcontrol()); if(core::localplayer()->view()) - inventory_view = core::localplayer()->view()->inventory(); + inventory_view = core::game()->request_inventory(core::localplayer()->view()); menu_inventorylistview->set_inventory(inventory_player, item_type); menu_traderlistview->set_inventory(inventory_view, item_type); - set_item(0); + //set_item(0); } void TradeMenu::set_item(ui::ListItem *item) @@ -302,10 +302,12 @@ bool TradeMenu::on_emit(Widget *sender, const Event event, void *data) set_item(menu_inventorylistview->selected()); } return true; + } else if (event == ui::Widget::EventListViewChanged) { // listview changed if (sender == menu_traderlistview) { set_item(menu_traderlistview->selected()); + } else if (sender == menu_inventorylistview) { set_item(menu_inventorylistview->selected()); } @@ -318,7 +320,7 @@ bool TradeMenu::on_emit(Widget *sender, const Event event, void *data) void TradeMenu::draw() { // sanity check - if ((menu_inventorylistview->inventory() != (core::localcontrol() ? core::localcontrol()->inventory() : 0 )) || + if ((menu_inventorylistview->inventory() != (core::localcontrol() ? core::localcontrol()->inventory() : 0 )) || (menu_traderlistview->inventory() != (core::localplayer()->view() ? core::localplayer()->view()->inventory() : 0 ))) { set_itemtype(menu_itemtype); } diff --git a/src/client/video.cc b/src/client/video.cc index 6eee36b..aa7ca9e 100644 --- a/src/client/video.cc +++ b/src/client/video.cc @@ -4,14 +4,14 @@ the terms and conditions of the GNU General Public License version 2 */ +#include "core/core.h" +#include "core/gameserver.h" #include "client/video.h" #include "client/input.h" #include "client/client.h" #include "client/targets.h" #include "render/render.h" #include "render/gl.h" -#include "core/core.h" -#include "core/gameserver.h" #include "filesystem/filesystem.h" #include "sys/sys.h" #include "ui/ui.h" diff --git a/src/core/entity.cc b/src/core/entity.cc index 0b3a402..1494438 100644 --- a/src/core/entity.cc +++ b/src/core/entity.cc @@ -183,6 +183,8 @@ Entity::Entity(std::istream & is) entity_inventory = 0; entity_info = 0; + entity_inventory = 0; + memset(entity_extension, 0, sizeof(entity_extension)); } @@ -219,6 +221,12 @@ void Entity::clear_updates() entity_created = false; entity_dirty = false; entity_oldzone = 0; + + if (entity_inventory && entity_inventory->dirty()) { + // inventory timestamp must be set for singleplayer + entity_inventory->set_timestamp(game()->timestamp()); + entity_inventory->set_dirty(false); + } } void Entity::set_info(Info *info) @@ -299,7 +307,11 @@ void Entity::serialize_server_create(std::ostream & os) const << "\"" <name() : "") << "\" " - << (info() ? info()->id() : 0) << " "; + << (info() ? info()->id() : 0) << " " + << (inventory() ? 1 : 0) << " "; + + if (inventory()) + inventory()->serialize_server_update(os); } void Entity::receive_server_create(std::istream &is) @@ -371,6 +383,28 @@ void Entity::receive_server_create(std::istream &is) entity_info = 0; } + // has inventory + if (is >> o) { + if (!o) { + if (inventory()) { + con_warn << "Receive no inventory for entity " << id() << " " << label() << " with inventory!" << std::endl; + entity_inventory->clear(); + } + } else { + if (!inventory()) { + entity_inventory = new Inventory(); + } + } + } else { + if (inventory()) { + con_warn << "Receive no inventory for entity " << id() << " " << label() << " with inventory!" << std::endl; + entity_inventory->clear(); + } + } + + if (inventory()) { + inventory()->receive_server_update(is); + } entity_dirty = false; } diff --git a/src/core/gameconnection.cc b/src/core/gameconnection.cc index 3167790..f64c8fb 100644 --- a/src/core/gameconnection.cc +++ b/src/core/gameconnection.cc @@ -91,7 +91,7 @@ bool GameConnection::interactive() const return true; } -Info *GameConnection::info(unsigned int id) +Info *GameConnection::request_info(const unsigned int id) { if (!id) { con_warn << "Information requested for illegal id 0!" << std::endl; @@ -104,12 +104,11 @@ Info *GameConnection::info(unsigned int id) info = new Info(id); } - if ( !info->timestamp() || (connection_timestamp < info->timestamp() + INFOTIMEOUT) ) + if (info->type() || (connection_timestamp < info->timestamp() + INFOTIMEOUT) ) return info; // send an information request to the server if (connection_network) { - info->set_timestamp(connection_timestamp); connection_network->send_info_request(info); connection_network->transmit(); } else { @@ -119,51 +118,21 @@ Info *GameConnection::info(unsigned int id) return info; } -/* -Info *GameConnection::info(const std::string &type, const std::string &label) +Inventory *GameConnection::request_inventory(Entity *entity) { - if (!type.size()) { - con_warn << "Information requested with empty type label!" << std::endl; - return 0; - } - - if (!label.size()) { - con_warn << "Information requested with empty label!" << std::endl; + if (!entity) { + con_warn << "Inventory request for NULL entity" << std::endl; return 0; } - // find the info record type - InfoType *infotype = InfoType::find(type); - if (!infotype) { - // create a new info record type and set the label - infotype = new InfoType(type.c_str()); - } - - // find the info record - Info *info = Info::find(infotype, label); - if (info) { - if (!info->timestamp() || (connection_timestamp - info->timestamp()) < INFOTIMEOUT) - return info; - } else { - // create a new info record and set the label - info = new Info(infotype, label); - info->add_text("Requesting information..."); - } - - // send an information request to the server - if (connection_network) { - //con_debug << "Requesting info for " << info->type()->label() << ":" << info->label() << std::endl; - info->set_timestamp(connection_timestamp); - connection_network->send_info_request(info); + if (entity->inventory() && connection_network) { + connection_network->send_inventory_request(entity); connection_network->transmit(); - } else { - info->add_text("^RNot connected."); - info->set_timestamp(0); - } - return info; + } + + return (entity->inventory()); } -*/ - + void GameConnection::abort() { connection_running = false; diff --git a/src/core/gameconnection.h b/src/core/gameconnection.h index bb615c1..89c3aff 100644 --- a/src/core/gameconnection.h +++ b/src/core/gameconnection.h @@ -57,12 +57,13 @@ public: /// localplayer sends a private message to another player void private_message(std::string const &args); -/* - /// returns an info record - virtual Info *info(const std::string &type, const std::string &label); -*/ - /// returns an info record - virtual Info *info(unsigned int id); + + /// request info record with id + virtual Info *request_info(const unsigned int id); + + /// request inventory for entity with id + virtual Inventory *request_inventory(Entity *entity); + /*----- static ---------------------------------------------------- */ diff --git a/src/core/gameinterface.h b/src/core/gameinterface.h index 11cc0de..bdce1d3 100644 --- a/src/core/gameinterface.h +++ b/src/core/gameinterface.h @@ -9,6 +9,7 @@ #include "core/player.h" #include "core/info.h" +#include "core/inventory.h" #include "model/vertexarray.h" namespace core @@ -61,12 +62,12 @@ public: /// return the current game time virtual unsigned long timestamp() const = 0; -/* - /// returns an info record - virtual Info *info(const std::string &type, const std::string &label) = 0; -*/ - /// returns an info record - virtual Info *info(unsigned int id) = 0; + + /// request info record with id + virtual Info *request_info(const unsigned int id) = 0; + + /// request inventory for entity with id + virtual Inventory *request_inventory(Entity *entity) = 0; /*----- mutators ------------------------------------------------- */ diff --git a/src/core/gameserver.cc b/src/core/gameserver.cc index 09a0fff..963db6d 100644 --- a/src/core/gameserver.cc +++ b/src/core/gameserver.cc @@ -243,19 +243,15 @@ float GameServer::time() const return ((float)(server_timestamp) / 1000.0f); } -Info *GameServer::info(unsigned int id) +Info *GameServer::request_info(unsigned int id) { return Info::find(id); } -Info *GameServer::info(const std::string &type, const std::string &label) +Inventory *GameServer::request_inventory(Entity *entity) { - // find the class - InfoType *infotype = InfoType::find(type); - if (!infotype) - return 0; + return (entity ? entity->inventory() : 0); - return Info::find(infotype, label); } void GameServer::abort() diff --git a/src/core/gameserver.h b/src/core/gameserver.h index 3f57a8b..775c41c 100644 --- a/src/core/gameserver.h +++ b/src/core/gameserver.h @@ -88,11 +88,11 @@ public: return server_startup; } - /// returns an info record - virtual Info *info(const std::string &type, const std::string &label); - - /// returns an info record - virtual Info *info(unsigned int id); + /// request info record with id + virtual Info *request_info(const unsigned int id); + + /// request inventory for entity with id + virtual Inventory *request_inventory(Entity *entity); /*----- static ---------------------------------------------------- */ diff --git a/src/core/info.cc b/src/core/info.cc index 16e68f5..cc1720e 100644 --- a/src/core/info.cc +++ b/src/core/info.cc @@ -53,6 +53,23 @@ InfoType *InfoType::find(const std::string & label) return 0; } +void InfoType::list() +{ + if (infotype_registry.size()) { + con_print << " "; + for (Registry::const_iterator it = infotype_registry.begin(); it != infotype_registry.end(); it++) { + InfoType *infotype = (*it); + + con_print << infotype->label() << " "; + } + con_print << std::endl; + } + con_print << "^B " << infotype_registry.size() << " info types" << std::endl; + + infotype_registry.clear(); +} + + /* ---- class Info ------------------------------------------------- */ unsigned int info_id_counter = 0; @@ -158,7 +175,10 @@ void Info::clear_text() void Info::serialize_server_update(std::ostream & os) const { - os << '"' << name() << "\" \"" << modelname() << "\" " << info_text.size() << " "; + os << '"' << name() << "\" \"" << modelname() << "\" " + << price() << " " + << volume() << " " + << info_text.size() << " "; for (Text::const_iterator it = info_text.begin(); it != info_text.end(); it++) { if (it != info_text.begin()) @@ -186,6 +206,9 @@ void Info::receive_server_update(std::istream &is) while ((is.get(c)) && (c != '"')) n += c; set_modelname(n); + + is >> info_price; + is >> info_volume; // read info text size_t s; @@ -377,6 +400,7 @@ void Info::list(const Info *info) void Info::list() { + InfoType::list(); list_header(); for (Registry::iterator it = info_registry.begin(); it != info_registry.end(); it++) { Info *info = (*it); diff --git a/src/core/info.h b/src/core/info.h index b3fa58f..b8154d7 100644 --- a/src/core/info.h +++ b/src/core/info.h @@ -25,6 +25,9 @@ namespace core class InfoType : public Label { public: + /// info registry type definition + typedef std::vector Registry; + /** * @brief create a new information card category * The constructor automaticly adds the instance to the registry @@ -40,11 +43,15 @@ public: /// search the infotype registry for a label static InfoType *find(const std::string & label); + + inline static const Registry ®istry() { + return infotype_registry; + } + /// list the infotypes + static void list(); + private: - /// info registry type definition - typedef std::vector Registry; - static Registry infotype_registry; }; // class InfoType diff --git a/src/core/inventory.cc b/src/core/inventory.cc index 4b9a2e5..7957294 100644 --- a/src/core/inventory.cc +++ b/src/core/inventory.cc @@ -18,6 +18,7 @@ Inventory::Inventory(const float capacity) inventory_timestamp = 0; inventory_capacity = capacity; inventory_capacity_used = 0; + inventory_dirty = false; } Inventory::~Inventory() @@ -38,10 +39,11 @@ void Inventory::set_timestamp(const unsigned long timestamp) inventory_timestamp = timestamp; } -void Inventory::set_dirty() +void Inventory::set_dirty(const bool dirty) { - recalculate(); - inventory_timestamp = core::game()->timestamp(); + inventory_dirty = dirty; + if (dirty) + recalculate(); } void Inventory::add(Item *item) @@ -52,6 +54,7 @@ void Inventory::add(Item *item) return; } inventory_items.push_back(item); + inventory_dirty = true; } void Inventory::remove(Item *item) @@ -63,6 +66,7 @@ void Inventory::remove(Item *item) return; } } + // FIXME remove doesn't work over network } Item *Inventory::find(const Info *info) const @@ -97,6 +101,16 @@ void Inventory::recalculate() } } +void Inventory::serialize_server_update(std::ostream & os) const +{ + os << capacity() << " "; +} + +void Inventory::receive_server_update(std::istream &is) +{ + is >> inventory_capacity; +} + const long Inventory::max_amount(const long credits, const long price, const float volume) const { if ((price * volume) == 0) { diff --git a/src/core/inventory.h b/src/core/inventory.h index 7aaf299..80dbde1 100644 --- a/src/core/inventory.h +++ b/src/core/inventory.h @@ -45,7 +45,8 @@ public: }; /** - * @brief return the timestamp + * @brief return the timestamp of the last server update + * This is a client-side property and shoul not be used server-side */ inline const unsigned long timestamp() const { return inventory_timestamp; @@ -72,6 +73,10 @@ public: return inventory_capacity - inventory_capacity_used; } + inline const bool dirty() const { + return inventory_dirty; + } + /** * @brief returns the number of units of an item that can be stored in this inventory * @param credits number of player credits, limits the amount @@ -109,21 +114,24 @@ public: /** * @brief mark the inventory as dirty - * This method will set the timestamp to the current game time - * and will recalculate the available capacity - * @see recalculate() */ - void set_dirty(); + void set_dirty(const bool dirty = true); /** * @brief set the maximal inventory capacity, in cubic meters */ void set_capacity(const float capacity); -private: - // recalculate inventory capacity + /// recalculate inventory capacity void recalculate(); + + /// serialize a server-to-client update on a stream + void serialize_server_update(std::ostream & os) const; + /// receive a server-to-client update from a stream + void receive_server_update(std::istream &is); + +private: // items in the inventory Items inventory_items; @@ -135,6 +143,8 @@ private: // current capacity used, in cubic meters float inventory_capacity_used; + + bool inventory_dirty; }; } // namsepace core diff --git a/src/core/item.cc b/src/core/item.cc index 77b0f9c..e9d9f65 100644 --- a/src/core/item.cc +++ b/src/core/item.cc @@ -4,8 +4,10 @@ the terms of the GNU General Public License version 2 */ -#include "core/item.h" #include "sys/sys.h" +#include "core/application.h" +#include "core/item.h" + namespace core { @@ -17,6 +19,7 @@ Item::Item(const Info *info) item_info = info; item_amount = 0; item_price = info->price(); + set_timestamp(game() ? game()->timestamp() : 0); } Item::~Item() @@ -28,21 +31,41 @@ Item::~Item() void Item::set_amount(const long amount) { item_amount = amount; + set_timestamp(game() ? game()->timestamp() : 0); } void Item::inc_amount(const long amount) { item_amount += amount; + set_timestamp(game() ? game()->timestamp() : 0); } void Item::dec_amount(const long amount) { item_amount -= amount; + set_timestamp(game() ? game()->timestamp() : 0); } void Item::set_price(const long price) { item_price = price; + set_timestamp(game() ? game()->timestamp() : 0); +} + +void Item::set_timestamp(const unsigned long timestamp) +{ + item_timestamp = timestamp; +} + +void Item::serialize_server_update(std::ostream & os) const +{ + os << amount() << " " << price() << " "; +} + +void Item::receive_server_update(std::istream &is) +{ + is >> item_amount; + is >> item_price; } } // namespace core diff --git a/src/core/item.h b/src/core/item.h index 9c17dc9..e4e8507 100644 --- a/src/core/item.h +++ b/src/core/item.h @@ -39,24 +39,40 @@ public: inline const long price() const { return item_price; } + + inline const unsigned long timestamp() const { + return item_timestamp; + } /* ---- mutators ----------------------------------------------- */ /** * @brief set associated amount */ - void set_amount(const long amount); + void set_amount(const long amount); + + void inc_amount(const long amount); + + void dec_amount(const long amount); - void inc_amount(const long amount); + void set_price(const long price); - void dec_amount(const long amount); + void set_dirty(); + + /* ---- serializers -------------------------------------------- */ - void set_price(const long price); + void serialize_server_update(std::ostream & os) const; + + void receive_server_update(std::istream &is); private: + void set_timestamp(const unsigned long timestamp); + const Info *item_info; long item_price; long item_amount; + + unsigned long item_timestamp; }; } // namespace core diff --git a/src/core/net.h b/src/core/net.h index f6904b2..39dfdf6 100644 --- a/src/core/net.h +++ b/src/core/net.h @@ -28,6 +28,13 @@ const float NETTIMEOUT = 20; } +#ifndef _WIN32 +#include +#else +#include +#include +#endif + #include "core/netserver.h" #include "core/netclient.h" #include "core/netconnection.h" diff --git a/src/core/netclient.h b/src/core/netclient.h index 818422e..cca9529 100644 --- a/src/core/netclient.h +++ b/src/core/netclient.h @@ -7,6 +7,8 @@ #ifndef __INCLUDED_CORE_NETCLIENT_H__ #define __INCLUDED_CORE_NETCLIENT_H__ +#include "core/net.h" + #include #include @@ -19,10 +21,6 @@ #include #include -#else - -#include - #endif #include diff --git a/src/core/netconnection.cc b/src/core/netconnection.cc index 39bed62..11e23c9 100644 --- a/src/core/netconnection.cc +++ b/src/core/netconnection.cc @@ -435,6 +435,17 @@ void NetConnection::send_info_request(Info *info) info->set_timestamp(timestamp()); } +// send an inventory update request +void NetConnection::send_inventory_request(Entity *entity) +{ + if (!entity || !entity->inventory()) + return; + + std::ostringstream msg; + msg << "inv " << entity->id() << " " << entity->inventory()->timestamp() << "\n"; + this->send_raw(msg.str()); +} + // parse incoming client messages /** * The following incoming messages are parsed; @@ -452,7 +463,8 @@ void NetConnection::send_info_request(Info *info) * sup * pif * pid - * inf