Added handling of predicted item drops.
This commit is contained in:
20
src/items/item.cpp
Normal file → Executable file
20
src/items/item.cpp
Normal file → Executable file
@@ -102,14 +102,23 @@ void ItemState::collected(const AbstractKart *kart)
|
||||
} // collected
|
||||
|
||||
// ============================================================================
|
||||
|
||||
/** Constructor for an item.
|
||||
* \param type Type of the item.
|
||||
* \param xyz Location of the item.
|
||||
* \param normal The normal upon which the item is placed (so that it can
|
||||
* be aligned properly with the ground).
|
||||
* \param mesh The mesh to be used for this item.
|
||||
* \param is_predicted True if the creation of the item is predicted by
|
||||
* a client. Only used in networking.
|
||||
*/
|
||||
Item::Item(ItemType type, const Vec3& xyz, const Vec3& normal,
|
||||
scene::IMesh* mesh, scene::IMesh* lowres_mesh)
|
||||
scene::IMesh* mesh, scene::IMesh* lowres_mesh, bool is_predicted)
|
||||
: ItemState(type)
|
||||
{
|
||||
assert(type != ITEM_TRIGGER); // use other constructor for that
|
||||
|
||||
m_distance_2 = 1.2f;
|
||||
m_is_predicted = is_predicted;
|
||||
initItem(type, xyz);
|
||||
|
||||
m_original_rotation = shortestArcQuat(Vec3(0, 1, 0), normal);
|
||||
@@ -118,9 +127,9 @@ Item::Item(ItemType type, const Vec3& xyz, const Vec3& normal,
|
||||
m_original_lowmesh = lowres_mesh;
|
||||
m_listener = NULL;
|
||||
|
||||
LODNode* lodnode = new LODNode("item",
|
||||
irr_driver->getSceneManager()->getRootSceneNode(),
|
||||
irr_driver->getSceneManager());
|
||||
LODNode* lodnode =
|
||||
new LODNode("item", irr_driver->getSceneManager()->getRootSceneNode(),
|
||||
irr_driver->getSceneManager());
|
||||
scene::ISceneNode* meshnode =
|
||||
irr_driver->addMesh(mesh, StringUtils::insertValues("item_%i", (int)type));
|
||||
|
||||
@@ -161,6 +170,7 @@ Item::Item(ItemType type, const Vec3& xyz, const Vec3& normal,
|
||||
Item::Item(const Vec3& xyz, float distance, TriggerItemListener* trigger)
|
||||
: ItemState(ITEM_TRIGGER)
|
||||
{
|
||||
m_is_predicted = false;
|
||||
m_distance_2 = distance*distance;
|
||||
initItem(ITEM_TRIGGER, xyz);
|
||||
m_original_rotation = btQuaternion(0, 0, 0, 1);
|
||||
|
||||
16
src/items/item.hpp
Normal file → Executable file
16
src/items/item.hpp
Normal file → Executable file
@@ -63,6 +63,7 @@ public:
|
||||
*/
|
||||
class ItemState
|
||||
{
|
||||
LEAK_CHECK();
|
||||
public:
|
||||
/**
|
||||
* The list of all items. Important for the switch item function:
|
||||
@@ -257,7 +258,6 @@ class Item : public ItemState, public NoCopy
|
||||
{
|
||||
|
||||
private:
|
||||
LEAK_CHECK();
|
||||
|
||||
/** Stores the original rotation of an item. This is used in
|
||||
* case of a switch to restore the rotation of a bubble gum
|
||||
@@ -305,7 +305,10 @@ private:
|
||||
|
||||
/** The closest point to the left and right of this item at which it
|
||||
* would not be collected. Used by the AI to avoid items. */
|
||||
Vec3 *m_avoidance_points[2];
|
||||
Vec3 *m_avoidance_points[2];
|
||||
|
||||
/** True if this item is predicted to exists. Used in networking only. */
|
||||
bool m_is_predicted;
|
||||
|
||||
void setType(ItemType type);
|
||||
void initItem(ItemType type, const Vec3 &xyz);
|
||||
@@ -313,7 +316,8 @@ private:
|
||||
|
||||
public:
|
||||
Item(ItemType type, const Vec3& xyz, const Vec3& normal,
|
||||
scene::IMesh* mesh, scene::IMesh* lowres_mesh);
|
||||
scene::IMesh* mesh, scene::IMesh* lowres_mesh,
|
||||
bool is_predicted=false);
|
||||
Item(const Vec3& xyz, float distance,
|
||||
TriggerItemListener* trigger);
|
||||
virtual ~Item ();
|
||||
@@ -368,6 +372,12 @@ public:
|
||||
/** Returns the XYZ position of the item. */
|
||||
const Vec3& getXYZ() const { return m_xyz; }
|
||||
// ------------------------------------------------------------------------
|
||||
/** Sets if this is a predicted item or not. */
|
||||
void setPredicted(bool p) { m_is_predicted = p; }
|
||||
// ------------------------------------------------------------------------
|
||||
/** Returns if this item is predicted or not. */
|
||||
bool isPredicted() const { return m_is_predicted; }
|
||||
// ------------------------------------------------------------------------
|
||||
/** Returns the index of the graph node this item is on. */
|
||||
int getGraphNode() const { return m_graph_node; }
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@@ -31,10 +31,17 @@
|
||||
*/
|
||||
ItemEventInfo::ItemEventInfo(BareNetworkString *buffer, int *count)
|
||||
{
|
||||
m_type = (EventType)buffer->getUInt8();
|
||||
m_ticks = buffer->getTime();
|
||||
m_kart_id = buffer->getInt8();
|
||||
m_index = buffer->getUInt16();
|
||||
*count -= 7;
|
||||
*count -= 8;
|
||||
if (m_type == IEI_NEW)
|
||||
{
|
||||
m_xyz = buffer->getVec3();
|
||||
*count -= 12;
|
||||
}
|
||||
|
||||
} // ItemEventInfo(BareNetworkString, int *count)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@@ -44,10 +51,10 @@ ItemEventInfo::ItemEventInfo(BareNetworkString *buffer, int *count)
|
||||
void ItemEventInfo::saveState(BareNetworkString *buffer)
|
||||
{
|
||||
assert(NetworkConfig::get()->isServer());
|
||||
buffer->addTime(m_ticks).addUInt8(m_kart_id).addUInt16(m_index);
|
||||
if(isNewItem())
|
||||
{
|
||||
}
|
||||
buffer->addUInt8(m_type).addTime(m_ticks).addUInt8(m_kart_id)
|
||||
.addUInt16(m_index);
|
||||
if(m_type == IEI_NEW)
|
||||
buffer->add(m_xyz);
|
||||
} // saveState
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +34,9 @@ class BareNetworkString;
|
||||
class ItemEventInfo
|
||||
{
|
||||
private:
|
||||
/** Type of this event. */
|
||||
enum EventType {IEI_COLLECT, IEI_NEW, IEI_SWITCH} m_type;
|
||||
|
||||
/** Time at which this event happens. */
|
||||
int m_ticks;
|
||||
|
||||
@@ -56,7 +59,7 @@ public:
|
||||
ItemEventInfo(int ticks, int index, int kart_id)
|
||||
: m_ticks(ticks), m_index(index), m_kart_id(kart_id)
|
||||
{
|
||||
assert(kart_id >= 0);
|
||||
m_type = IEI_COLLECT;
|
||||
} // ItemEventInfo(collected existing item)
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
@@ -65,15 +68,17 @@ public:
|
||||
* need to encode the new item type.
|
||||
*/
|
||||
ItemEventInfo(int ticks, ItemState::ItemType type, int index,
|
||||
const Vec3 &xyz)
|
||||
: m_ticks(ticks), m_index(index), m_kart_id(-1), m_xyz(xyz)
|
||||
int kart_id, const Vec3 &xyz)
|
||||
: m_ticks(ticks), m_index(index), m_kart_id(kart_id), m_xyz(xyz)
|
||||
{
|
||||
m_type = IEI_NEW;
|
||||
} // ItemEventInfo(new item)
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
/** Constructor for switching items. */
|
||||
ItemEventInfo(int ticks) : m_ticks(ticks), m_kart_id(-2)
|
||||
ItemEventInfo(int ticks) : m_ticks(ticks)
|
||||
{
|
||||
m_type = IEI_SWITCH;
|
||||
} // ItemEventInfo(switch)
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
@@ -82,13 +87,13 @@ public:
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
/** Returns if this event represents a new item. */
|
||||
bool isNewItem() const { return m_kart_id == -1; }
|
||||
bool isNewItem() const { return m_type == IEI_NEW; }
|
||||
// --------------------------------------------------------------------
|
||||
/** Returns true if this event represents collection of an item. */
|
||||
bool isItemCollection() const { return m_kart_id >= 0; }
|
||||
bool isItemCollection() const { return m_type == IEI_COLLECT; }
|
||||
// --------------------------------------------------------------------
|
||||
/** Returns true if this event represent a switch usage. */
|
||||
bool isSwitch() const { return m_kart_id == -2; }
|
||||
bool isSwitch() const { return m_type == IEI_SWITCH; }
|
||||
// --------------------------------------------------------------------
|
||||
/** Returns the index of this item. */
|
||||
int getIndex() const { return m_index; }
|
||||
@@ -100,7 +105,6 @@ public:
|
||||
* to be called when this event is an item collection. */
|
||||
int getKartId() const
|
||||
{
|
||||
assert(isItemCollection());
|
||||
return m_kart_id;
|
||||
} // getKartId
|
||||
// --------------------------------------------------------------------
|
||||
@@ -111,6 +115,13 @@ public:
|
||||
assert(isNewItem());
|
||||
return m_xyz;
|
||||
} // getXYZ
|
||||
// --------------------------------------------------------------------
|
||||
/** Returns the type of this item. Note at this stage only bubble gums
|
||||
* can be created during a race. */
|
||||
ItemState::ItemType getNewItemType() const
|
||||
{
|
||||
return ItemState::ITEM_BUBBLEGUM;
|
||||
} // getNewItemType
|
||||
|
||||
}; // class ItemEventInfo
|
||||
|
||||
|
||||
8
src/items/item_manager.cpp
Normal file → Executable file
8
src/items/item_manager.cpp
Normal file → Executable file
@@ -256,13 +256,15 @@ unsigned int ItemManager::insertItem(Item *item)
|
||||
* bubblegum).
|
||||
* \param type Type of the item.
|
||||
* \param kart The kart that drops the new item.
|
||||
* \param xyz Can be used to overwrite the item location (used in networking).
|
||||
*/
|
||||
Item* ItemManager::dropNewItem(ItemState::ItemType type, AbstractKart *kart)
|
||||
Item* ItemManager::dropNewItem(ItemState::ItemType type,
|
||||
AbstractKart *kart, const Vec3 *xyz)
|
||||
{
|
||||
Vec3 hit_point;
|
||||
Vec3 normal;
|
||||
const Material* material_hit;
|
||||
Vec3 pos = kart->getXYZ();
|
||||
Vec3 pos = xyz ? *xyz : kart->getXYZ();
|
||||
Vec3 to = pos + kart->getTrans().getBasis() * Vec3(0, -10000, 0);
|
||||
Track::getCurrentTrack()->getTriangleMesh().castRay(pos, to,
|
||||
&hit_point,
|
||||
@@ -286,8 +288,8 @@ Item* ItemManager::dropNewItem(ItemState::ItemType type, AbstractKart *kart)
|
||||
Item* item = new Item(type, pos, normal, m_item_mesh[mesh_type],
|
||||
m_item_lowres_mesh[mesh_type]);
|
||||
|
||||
insertItem(item);
|
||||
if(kart != NULL) item->setParent(kart);
|
||||
insertItem(item);
|
||||
if(m_switch_ticks>=0)
|
||||
{
|
||||
ItemState::ItemType new_type = m_switch_to[item->getType()];
|
||||
|
||||
2
src/items/item_manager.hpp
Normal file → Executable file
2
src/items/item_manager.hpp
Normal file → Executable file
@@ -118,7 +118,7 @@ public:
|
||||
virtual Item* placeItem (ItemState::ItemType type, const Vec3& xyz,
|
||||
const Vec3 &normal);
|
||||
virtual Item* dropNewItem (ItemState::ItemType type,
|
||||
AbstractKart* parent);
|
||||
AbstractKart* parent, const Vec3 *xyz=NULL);
|
||||
virtual Item* placeTrigger (const Vec3& xyz, float distance,
|
||||
TriggerItemListener* listener);
|
||||
void update (int ticks);
|
||||
|
||||
@@ -90,7 +90,8 @@ unsigned int NetworkItemManager::insertItem(Item *item)
|
||||
unsigned int index = ItemManager::insertItem(item);
|
||||
if(index>=m_confirmed_state.size())
|
||||
{
|
||||
m_confirmed_state.push_back(NULL);
|
||||
ItemState *is = new ItemState(*item);
|
||||
m_confirmed_state.push_back(is);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -109,6 +110,7 @@ void NetworkItemManager::collectedItem(Item *item, AbstractKart *kart)
|
||||
{
|
||||
if(NetworkConfig::get()->isServer())
|
||||
{
|
||||
// The server saves the collected item as item event info
|
||||
m_item_events.lock();
|
||||
m_item_events.getData().emplace_back(World::getWorld()->getTimeTicks(),
|
||||
item->getItemId(),
|
||||
@@ -118,8 +120,7 @@ void NetworkItemManager::collectedItem(Item *item, AbstractKart *kart)
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we are predicting (i.e. not rewinding), the client
|
||||
// predicts item collection:
|
||||
// The client predicts item collection:
|
||||
ItemManager::collectedItem(item, kart);
|
||||
}
|
||||
} // collectedItem
|
||||
@@ -127,18 +128,30 @@ void NetworkItemManager::collectedItem(Item *item, AbstractKart *kart)
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Called when a new item is created, e.g. bubble gum.
|
||||
* \param type Type of the item.
|
||||
* \param parent In case of a dropped item used to avoid that a kart
|
||||
* \param kart In case of a dropped item used to avoid that a kart
|
||||
* is affected by its own items.
|
||||
* \param xyz Location of the item. If specified will override the
|
||||
* kart position (used in networking only).
|
||||
*/
|
||||
Item* NetworkItemManager::dropNewItem(ItemState::ItemType type,
|
||||
AbstractKart *kart)
|
||||
AbstractKart *kart, const Vec3 *xyz)
|
||||
{
|
||||
Item *item = ItemManager::dropNewItem(type, kart);
|
||||
Item *item = ItemManager::dropNewItem(type, kart, xyz);
|
||||
if(!item) return NULL;
|
||||
|
||||
if (NetworkConfig::get()->isClient())
|
||||
{
|
||||
// If this is called when replaying a server event, the calling
|
||||
// function restoreState will set the item to be not-predicted.
|
||||
item->setPredicted(true);
|
||||
return item;
|
||||
}
|
||||
|
||||
// Server: store the data for this event:
|
||||
m_item_events.lock();
|
||||
m_item_events.getData().emplace_back(World::getWorld()->getTimeTicks(), type,
|
||||
item->getItemId(),
|
||||
m_item_events.getData().emplace_back(World::getWorld()->getTimeTicks(),
|
||||
type, item->getItemId(),
|
||||
kart->getWorldKartId(),
|
||||
kart->getXYZ() );
|
||||
m_item_events.unlock();
|
||||
return item;
|
||||
@@ -203,8 +216,7 @@ BareNetworkString* NetworkItemManager::saveState()
|
||||
uint16_t n = (uint16_t)m_item_events.getData().size();
|
||||
if(n==0)
|
||||
{
|
||||
BareNetworkString *s =
|
||||
new BareNetworkString();
|
||||
BareNetworkString *s = new BareNetworkString();
|
||||
m_item_events.unlock();
|
||||
return s;
|
||||
}
|
||||
@@ -245,13 +257,42 @@ void NetworkItemManager::forwardTime(int ticks)
|
||||
*/
|
||||
void NetworkItemManager::restoreState(BareNetworkString *buffer, int count)
|
||||
{
|
||||
assert(NetworkConfig::get()->isClient());
|
||||
// The state at World::getTimeTicks() needs to be restored. The confirmed
|
||||
// state in this instance was taken at m_confirmed_state_time. So first
|
||||
// apply the new events to the confirmed state till we reach the end of
|
||||
// all new events (which must be <= getTimeTicks() (since the server will
|
||||
// only send events till the specified state time). Then forward the
|
||||
// state to getTimeTicks().
|
||||
// state in this instance was taken at m_confirmed_state_time. First
|
||||
// forward this confirmed state to the current time (i.e. world time).
|
||||
// This is done in several steps:
|
||||
// 1) First remove all client-side predicted items from the list of all
|
||||
// items, and store them for now in a predictem_item cache. Predicted
|
||||
// item only happen between m_confirmed_state_time and 'now'.
|
||||
// 2) Apply all events included in this state to the confirmed state.
|
||||
// a) When a collection event is found, adjust the confirmed item state
|
||||
// only (this state will later be copied to the current item state).
|
||||
// b) When a new item is created, search in the item cache to see
|
||||
// if a predicted item for this slot already exists to speed up
|
||||
// item creation (if not, create a new item). Put this new item
|
||||
// into the current item state as well as in the confirmed state
|
||||
// (in the same index position).
|
||||
// 3) Once all new events have been applied to the confirmed state the
|
||||
// time must be <= world time. Forward the confirmed state to
|
||||
// world time, and update m_confirmed_state_time to the world time.
|
||||
// From here the replay can happen.
|
||||
|
||||
// 1) Remove predicted items:
|
||||
for (unsigned int i=0; i<m_all_items.size(); i++)
|
||||
{
|
||||
Item *item = m_all_items[i];
|
||||
if(item && item->isPredicted())
|
||||
{
|
||||
Log::verbose("NIM", "Deleting item %d at %d",
|
||||
item->getItemId(), World::getWorld()->getTimeTicks());
|
||||
delete item;
|
||||
m_all_items[i] = NULL;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Apply all events to current confirmed state:
|
||||
int current_time = m_confirmed_state_time;
|
||||
while(count > 0)
|
||||
{
|
||||
@@ -272,14 +313,22 @@ void NetworkItemManager::restoreState(BareNetworkString *buffer, int count)
|
||||
if (dt>0) forwardTime(dt);
|
||||
|
||||
// TODO: apply the various events types, atm only collection is supported:
|
||||
ItemState *item_state = m_confirmed_state[iei.getIndex()];
|
||||
if(iei.isItemCollection())
|
||||
{
|
||||
ItemState *item_state = m_confirmed_state[iei.getIndex()];
|
||||
// An item on the track was collected:
|
||||
AbstractKart *kart = World::getWorld()->getKart(iei.getKartId());
|
||||
m_confirmed_state[iei.getIndex()]->collected(kart);
|
||||
}
|
||||
|
||||
else if(iei.isNewItem())
|
||||
{
|
||||
AbstractKart *kart = World::getWorld()->getKart(iei.getKartId());
|
||||
Item *item = dropNewItem(ItemState::ITEM_BUBBLEGUM,
|
||||
kart, &iei.getXYZ() );
|
||||
item->setPredicted(false);
|
||||
Log::verbose("NIM", "item %d not predicted at %d due to server info",
|
||||
item->getItemId(), World::getWorld()->getTimeTicks());
|
||||
}
|
||||
current_time = iei.getTicks();
|
||||
} // while count >0
|
||||
|
||||
@@ -297,8 +346,7 @@ void NetworkItemManager::restoreState(BareNetworkString *buffer, int count)
|
||||
{
|
||||
Item *item = m_all_items[i];
|
||||
const ItemState *is = m_confirmed_state[i];
|
||||
if(is)
|
||||
*(ItemState*)item = *is;
|
||||
if (is && item) *(ItemState*)item = *is;
|
||||
}
|
||||
|
||||
// Now we save the current local
|
||||
|
||||
@@ -68,8 +68,8 @@ public:
|
||||
virtual void reset();
|
||||
virtual void setItemConfirmationTime(int host_id, int ticks) OVERRIDE;
|
||||
virtual void collectedItem(Item *item, AbstractKart *kart) OVERRIDE;
|
||||
virtual Item* dropNewItem(ItemState::ItemType type,
|
||||
AbstractKart *kart) OVERRIDE;
|
||||
virtual Item* dropNewItem(ItemState::ItemType type, AbstractKart *kart,
|
||||
const Vec3 *xyz=NULL) OVERRIDE;
|
||||
virtual BareNetworkString* saveState() OVERRIDE;
|
||||
virtual void restoreState(BareNetworkString *buffer, int count) OVERRIDE;
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user