From 2fb58f9fbf1d770f4ca89519140b2248af630396 Mon Sep 17 00:00:00 2001 From: Sandu Liviu Catalin Date: Mon, 19 Jun 2017 04:09:35 +0300 Subject: [PATCH] Initial implementation of the area system. --- cbp/Module.cbp | 2 + source/Areas.cpp | 449 +++++++++++++++++++++++++ source/Areas.hpp | 670 ++++++++++++++++++++++++++++++++++++++ source/Core.cpp | 4 + source/Core.hpp | 39 ++- source/CoreEntity.cpp | 10 + source/CoreEvents.cpp | 108 +++++- source/CoreFuncs.cpp | 14 + source/CoreInst.cpp | 10 + source/CoreUtils.cpp | 8 + source/Entity/Player.cpp | 80 +++++ source/Entity/Player.hpp | 15 + source/Entity/Vehicle.cpp | 80 +++++ source/Entity/Vehicle.hpp | 15 + source/Register.cpp | 2 + source/SqBase.hpp | 5 + 16 files changed, 1507 insertions(+), 4 deletions(-) create mode 100644 source/Areas.cpp create mode 100644 source/Areas.hpp diff --git a/cbp/Module.cbp b/cbp/Module.cbp index 5b4ddf33..86ca4eaf 100644 --- a/cbp/Module.cbp +++ b/cbp/Module.cbp @@ -450,6 +450,8 @@ + + diff --git a/source/Areas.cpp b/source/Areas.cpp new file mode 100644 index 00000000..0d972515 --- /dev/null +++ b/source/Areas.cpp @@ -0,0 +1,449 @@ +// ------------------------------------------------------------------------------------------------ +#include "Areas.hpp" + +// ------------------------------------------------------------------------------------------------ +#include + +// ------------------------------------------------------------------------------------------------ +namespace SqMod { + +// ------------------------------------------------------------------------------------------------ +SQMODE_DECL_TYPENAME(AreaTypename, _SC("SqArea")) + +// ------------------------------------------------------------------------------------------------ +AreaManager AreaManager::s_Inst; + +// ------------------------------------------------------------------------------------------------ +void Area::AddArray(const Sqrat::Array &) +{ + //todo... +} + +// ------------------------------------------------------------------------------------------------ +bool Area::Manage() +{ + // Are we connected to any cells? + if (!mCells.empty()) + { + STHROWF("The area is already managed"); + } + // Is there something to be managed? + else if (mPoints.size() < 3) + { + STHROWF("Areas need at least 3 points to be managed"); + } + // We expect this to be called only from the script so that the first parameter in the vm + // is the area instance + LightObj obj(1, DefaultVM::Get()); + // Attempt to manage this area + AreaManager::Get().InsertArea(*this, obj); + // Return whether the area is now managed by any cells + return !mCells.empty(); +} + +// ------------------------------------------------------------------------------------------------ +bool Area::Unmanage() +{ + // Are we connected to any cells? + if (mCells.empty()) + { + return true; // Already unmanaged + } + // Attempt to unmanage this area + AreaManager::Get().RemoveArea(*this); + // Return whether the area is not managed by any cells + return mCells.empty(); +} + +// ------------------------------------------------------------------------------------------------ +bool Area::IsInside(float x, float y) const +{ + // Is the an area to test? + if (mPoints.size() < 3) + { + return false; // Can't possibly be in an area that doesn't exist + } + // http://sidvind.com/wiki/Point-in-polygon:_Jordan_Curve_Theorem + // The points creating the polygon + float x1, x2; + // How many times the ray crosses a line segment + int crossings = 0; + // Iterate through each line + for (Uint32 i = 0, n = static_cast< Uint32 >(mPoints.size()); i < n; ++i) + { + Points::const_reference a = mPoints[i]; + Points::const_reference b = mPoints[(i + 1) % n]; + // This is done to ensure that we get the same result when + // the line goes from left to right and right to left. + if (a.x < b.x) + { + x1 = a.x; + x2 = b.x; + } + else + { + x1 = b.x; + x2 = a.x; + } + // First check if the ray is able to cross the line + if (x > x1 && x <= x2 && (y < a.y || y <= b.y)) + { + // Calculate the equation of the line + const float dx = (b.x - a.x); + const float dy = (b.y - a.y); + float k; + + if (fabs(dx) < 0.000001f) + { + k = 0xffffffff; + } + else + { + k = (dy / dx); + } + + const float m = (a.y - k * a.x); + const float y2 = (k * x + m); + // Does the ray cross the line? + if (y <= y2) + { + ++crossings; + } + } + } + // Return if the crossings are not even + return (crossings % 2 == 1); +} + +// ------------------------------------------------------------------------------------------------ +AreaManager::AreaManager(size_t sz) + : m_Queue(), m_ProcList(), m_Grid{} +{ + // Negative half grid size (left) + int l = (-GRIDH * CELLD); + // Positive half grid size minus one cell (bottom) + int b = (abs(l) - CELLD); + // Negative half grid size minus one cell (right) + int r = (l + CELLD); + // Positive half grid size (top) + int t = abs(l); + // Initialize the grid cells + for (int y = 0; y < GRIDN; ++y) + { + for (int x = 0; x < GRIDN; ++x) + { + // Grab a reference to the cell + AreaCell & c = m_Grid[y][x]; + // Configure the range of the cell + c.mL = static_cast< float >(l); + c.mB = static_cast< float >(b); + c.mR = static_cast< float >(r); + c.mT = static_cast< float >(t); + // Reserve area memory if requested + c.mAreas.reserve(sz); + // Reset the locks on this area + c.mLocks = 0; + // Advance the left side + l = r; + // Advance the right side + r += CELLD; + // Should we advance to the next row? + if (r > (GRIDH * CELLD)) + { + // Reset the left side + l = (-GRIDH * CELLD); + // Reset the right side + r = (l + CELLD); + // Advance the bottom + b -= CELLD; + // Advance the top + t -= CELLD; + } + } + } + // Reserve some space in the queue + m_Queue.reserve(128); + m_ProcList.reserve(128); +} + +// ------------------------------------------------------------------------------------------------ +void AreaManager::Insert(AreaCell & c, Area & a, LightObj & obj) +{ + // Is this cell currently locked? + if (c.mLocks) + { + m_Queue.emplace_back(c, a, obj); // Queue this request for now + } + else + { + c.mAreas.emplace_back(&a, obj); + } + // Associate the area with this cell so it can't be managed again (even while in the queue) + a.mCells.push_back(&c); +} + +// ------------------------------------------------------------------------------------------------ +void AreaManager::Remove(AreaCell & c, Area & a) +{ + // Is this cell currently locked? + if (c.mLocks) + { + m_Queue.emplace_back(c, a); // Queue this request for now + } + else + { + // Attempt to locate this area in the cell + AreaCell::Areas::iterator itr = std::find_if(c.mAreas.begin(), c.mAreas.end(), + [&a](AreaCell::Areas::reference p) -> bool { + return (p.first == &a); + }); + // Have we found it? + if (itr != c.mAreas.end()) + { + c.mAreas.erase(itr); // Erase it + } + } + // Dissociate the area with this cell so it can be managed again (even while in the queue) + Area::Cells::iterator itr = std::find(a.mCells.begin(), a.mCells.end(), &c); + // Was is associated? + if (itr != a.mCells.end()) + { + a.mCells.erase(itr); // Dissociate them + } +} + +// ------------------------------------------------------------------------------------------------ +void AreaManager::ProcQueue() +{ + // Look for actions that can be completed + for (Queue::iterator itr = m_Queue.begin(); itr != m_Queue.end(); ++itr) + { + // Was this cell unlocked in the meantime? + if (itr->mCell->mLocks <= 0) + { + m_ProcList.push_back(itr); + } + } + // Process the actions that are ready + for (auto & itr : m_ProcList) + { + // Was this a remove request? + if (itr->mObj.IsNull()) + { + Remove(*(itr->mCell), *(itr->mArea)); + } + else + { + Insert(*(itr->mCell), *(itr->mArea), itr->mObj); + } + } + // Remove processed requests + for (auto & itr : m_ProcList) + { + m_Queue.erase(itr); + } + // Actions were processed + m_ProcList.clear(); +} + +// ------------------------------------------------------------------------------------------------ +void AreaManager::Clear() +{ + // Clear the cells + for (AreaCell (&row)[GRIDN] : m_Grid) + { + for (AreaCell & c : row) + { + c.mAreas.clear(); + } + } + // Clear the queue as well + m_Queue.clear(); + m_ProcList.clear(); +} + +// ------------------------------------------------------------------------------------------------ +void AreaManager::InsertArea(Area & a, LightObj & obj) +{ + // See if this area is already managed + if (!a.mCells.empty() || a.mPoints.empty()) + { + return; // Already managed or nothing to manage + } + // Go through each cell and check if the area touches it + //for (AreaCell (&row)[GRIDN] : m_Grid) + for (int y = 0; y < GRIDN; ++y) + { + //for (AreaCell & c : row) + for (int x = 0; x < GRIDN; ++x) + { + AreaCell & c = m_Grid[y][x]; + // Does the bounding box of this cell intersect with the one of the area? + if (a.mL <= c.mR && c.mL <= a.mR && a.mB <= c.mT && c.mB <= a.mT) + { + Insert(c, a, obj); // Attempt to insert the area into this cell + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void AreaManager::RemoveArea(Area & a) +{ + // Just remove the associated cells + for (auto c : a.mCells) + { + Remove(*c, a); + } +} + +// ------------------------------------------------------------------------------------------------ +Vector2i AreaManager::LocateCell(float x, float y) +{ + // Transform the world coordinates into a cell coordinates + // and cast to integral after rounding the value + int xc = static_cast< int >(std::round(x / CELLD)); + int yc = static_cast< int >(std::round(y / CELLD)); + // Grab the absolute cell coordinates for range checking + const int xca = std::abs(xc); + const int yca = std::abs(yc); + // Make sure the cell coordinates are within range + if (xca > (GRIDH+1) || yca > (GRIDH+1)) + { + return Vector2i(NOCELL, NOCELL); // Out of our scanning area + } + // Clamp the x coordinate if necessary + if (xca >= (GRIDH)) + { + xc = xc < 0 ? -(GRIDH-1) : (GRIDH-1); + } + // Clamp the y coordinate if necessary + if (yca >= (GRIDH)) + { + yc = xc < 0 ? -(GRIDH-1) : (GRIDH-1); + } + // Return the identified cell row and column + return Vector2i(GRIDH+xc, GRIDH-yc); +} + +// ------------------------------------------------------------------------------------------------ +static void Areas_TestPointEx(Object & env, Function & func, float x, float y) +{ + // Is the function valid? + if (func.IsNull()) + { + STHROWF("Invalid callback object"); + } + // Should we use a custom environment? + else if (!env.IsNull()) + { + func = Function(env.GetVM(), env, func.GetFunc()); + } + // Begin testing + AreaManager::Get().TestPoint([&func](AreaCell::Areas::reference ap) -> void { + func.Execute(ap.second); + }, x, y); +} + +// ------------------------------------------------------------------------------------------------ +static void Areas_TestPoint(Object & env, Function & func, const Vector2 & v) +{ + Areas_TestPointEx(env, func, v.x, v.y); +} + +// ------------------------------------------------------------------------------------------------ +static void Areas_TestPointOnEx(Object & ctx, Object & env, Function & func, float x, float y) +{ + // Is the function valid? + if (func.IsNull()) + { + STHROWF("Invalid callback object"); + } + // Should we use a custom environment? + else if (!env.IsNull()) + { + func = Function(env.GetVM(), env, func.GetFunc()); + } + // Begin testing + AreaManager::Get().TestPoint([&ctx, &func](AreaCell::Areas::reference ap) -> void { + func.Execute(ctx, ap.second); + }, x, y); +} + +// ------------------------------------------------------------------------------------------------ +static void Areas_TestPointOn(Object & ctx, Object & env, Function & func, const Vector2 & v) +{ + Areas_TestPointOnEx(ctx, env, func, v.x, v.y); +} + +// ------------------------------------------------------------------------------------------------ +static Vector2i Areas_LocatePointCell(const Vector2 & v) +{ + return AreaManager::Get().LocateCell(v.x, v.y); +} + +// ------------------------------------------------------------------------------------------------ +static Vector2i Areas_LocatePointCellEx(float x, float y) +{ + return AreaManager::Get().LocateCell(x, y); +} + +// ------------------------------------------------------------------------------------------------ +void TerminateAreas() +{ + AreaManager::Get().Clear(); +} + +// ================================================================================================ +void Register_Areas(HSQUIRRELVM vm) +{ + RootTable(vm).Bind(_SC("SqArea"), + Class< Area >(vm, AreaTypename::Str) + // Constructors + .Ctor() + .FmtCtor() + .FmtCtor< SQInteger >() + .Ctor< const Vector2 &, const Vector2 &, const Vector2 & >() + .FmtCtor< const Vector2 &, const Vector2 &, const Vector2 & >() + .FmtCtor< const Vector2 &, const Vector2 &, const Vector2 &, SQInteger >() + .Ctor< float, float, float, float, float, float >() + .FmtCtor< float, float, float, float, float, float >() + .FmtCtor< float, float, float, float, float, float, SQInteger >() + // Meta-methods + .SquirrelFunc(_SC("_typename"), &AreaTypename::Fn) + .Func(_SC("_tostring"), &Area::ToString) + // Member Properties + .Prop(_SC("Name"), &Area::GetName, &Area::SetName) + .Prop(_SC("ID"), &Area::GetID, &Area::SetID) + .Prop(_SC("Locked"), &Area::IsLocked) + .Prop(_SC("IsLocked"), &Area::IsLocked) + .Prop(_SC("Center"), &Area::GetCenter) + .Prop(_SC("BoundingBox"), &Area::GetBoundingBox) + .Prop(_SC("Empty"), &Area::Empty) + .Prop(_SC("Size"), &Area::Size) + .Prop(_SC("Points"), &Area::Size) + .Prop(_SC("Capacity"), &Area::Capacity) + // Member Methods + .FmtFunc(_SC("SetName"), &Area::ApplyName) + .Func(_SC("SetID"), &Area::ApplyID) + .Func(_SC("Clear"), &Area::Clear) + .Func(_SC("Reserve"), &Area::Reserve) + .Func(_SC("Add"), &Area::AddPoint) + .Func(_SC("AddEx"), &Area::AddPointEx) + .Func(_SC("AddArray"), &Area::AddArray) + .Func(_SC("Test"), &Area::Test) + .Func(_SC("TestEx"), &Area::TestEx) + .Func(_SC("Manage"), &Area::Manage) + .Func(_SC("Unmanage"), &Area::Unmanage) + // Static Functions + .StaticFunc(_SC("GlobalTest"), &Areas_TestPoint) + .StaticFunc(_SC("GlobalTestEx"), &Areas_TestPointEx) + .StaticFunc(_SC("GlobalTestOn"), &Areas_TestPointOn) + .StaticFunc(_SC("GlobalTestOnEx"), &Areas_TestPointOnEx) + .StaticFunc(_SC("LocatePointCell"), &Areas_LocatePointCell) + .StaticFunc(_SC("LocatePointCellEx"), &Areas_LocatePointCellEx) + .StaticFunc(_SC("UnmanageAll"), &TerminateAreas) + ); +} + +} // Namespace:: SqMod diff --git a/source/Areas.hpp b/source/Areas.hpp new file mode 100644 index 00000000..9bc56afa --- /dev/null +++ b/source/Areas.hpp @@ -0,0 +1,670 @@ +#ifndef _AREAS_HPP_ +#define _AREAS_HPP_ + +// ------------------------------------------------------------------------------------------------ +#include "Base/Shared.hpp" +#include "Base/Vector2.hpp" +#include "Base/Vector4.hpp" +#include "Base/Vector2i.hpp" + +// ------------------------------------------------------------------------------------------------ +#include +#include + +// ------------------------------------------------------------------------------------------------ +namespace SqMod { + +/* ------------------------------------------------------------------------------------------------ + * Various information associated with an area cell. +*/ +struct AreaCell +{ + // -------------------------------------------------------------------------------------------- + typedef std::pair< Area *, LightObj > AreaPair; // A reference to an area object. + typedef std::vector< AreaPair > Areas; // A list of area objects. + + // -------------------------------------------------------------------------------------------- + float mL, mB, mR, mT; // Left-Bottom, Right-Top components of the cell bounding box. + // -------------------------------------------------------------------------------------------- + Areas mAreas; // Areas that intersect with the cell. + // -------------------------------------------------------------------------------------------- + int mLocks; // The amount of locks on the cell. + + /* -------------------------------------------------------------------------------------------- + * Default constructor. + */ + AreaCell() + : mL(0), mB(0), mR(0), mT(0), mAreas(0), mLocks(0) + { + //... + } +}; + +/* ------------------------------------------------------------------------------------------------ + * Area implementation used to store area points. +*/ +struct Area +{ + typedef std::vector< Vector2 > Points; + typedef std::vector< AreaCell * > Cells; + // -------------------------------------------------------------------------------------------- + static constexpr float DEF_L = std::numeric_limits< float >::infinity(); + static constexpr float DEF_B = std::numeric_limits< float >::infinity(); + static constexpr float DEF_R = -std::numeric_limits< float >::infinity(); + static constexpr float DEF_T = -std::numeric_limits< float >::infinity(); + // -------------------------------------------------------------------------------------------- + float mL, mB, mR, mT; // Left-Bottom, Right-Top components of the bounding box. + // -------------------------------------------------------------------------------------------- + Points mPoints; // Collection of points that make up the area. + // -------------------------------------------------------------------------------------------- + SQInteger mID; // The user identifier given to this area. + // -------------------------------------------------------------------------------------------- + Cells mCells; // The cells covered by this area. + // -------------------------------------------------------------------------------------------- + String mName; // The user name given to this area. + + /* -------------------------------------------------------------------------------------------- + * Default constructor. + */ + Area() + : mL(DEF_L), mB(DEF_B), mR(DEF_R), mT(DEF_T), mPoints(), mID(0), mCells(), mName() + { + //... + } + /* -------------------------------------------------------------------------------------------- + * Named constructor. + */ + Area(const StackStrF & name) + : Area(16, name) + { + //... + } + /* -------------------------------------------------------------------------------------------- + * Default constructor. + */ + Area(SQInteger sz, const StackStrF & name) + : mL(DEF_L), mB(DEF_B), mR(DEF_R), mT(DEF_T), mPoints(), mID(0), mCells() + , mName(name.mPtr, name.mLen <= 0 ? 0 : name.mLen) + + { + // Should we reserve some space for points in advance? + if (sz > 0) + { + mPoints.reserve(static_cast< size_t >(sz)); + } + } + /* -------------------------------------------------------------------------------------------- + * Vector2 constructor. + */ + Area(const Vector2 & a, const Vector2 & b, const Vector2 & c) + : Area(a.x, a.y, b.x, b.y, c.x, c.y, 16, StackStrF()) + { + //... + } + + /* -------------------------------------------------------------------------------------------- + * Vector2 constructor with name. + */ + Area(const Vector2 & a, const Vector2 & b, const Vector2 & c, const StackStrF & name) + : Area(a.x, a.y, b.x, b.y, c.x, c.y, 16, name) + { + //... + } + + /* -------------------------------------------------------------------------------------------- + * Vector2 constructor with name and memory to reserve. + */ + Area(const Vector2 & a, const Vector2 & b, const Vector2 & c, SQInteger sz, const StackStrF & name) + : Area(a.x, a.y, b.x, b.y, c.x, c.y, sz, name) + { + //... + } + + /* -------------------------------------------------------------------------------------------- + * Extended constructor. + */ + Area(float ax, float ay, float bx, float by, float cx, float cy) + : Area(ax, ay, bx, by, cx, cy, 16, StackStrF()) + { + //... + } + + /* -------------------------------------------------------------------------------------------- + * Extended constructor with name. + */ + Area(float ax, float ay, float bx, float by, float cx, float cy, const StackStrF & name) + : Area(ax, ay, bx, by, cx, cy, 16, name) + { + //... + } + + /* -------------------------------------------------------------------------------------------- + * Base constructor. + */ + Area(float ax, float ay, float bx, float by, float cx, float cy, SQInteger sz, const StackStrF & name) + : mL(DEF_L), mB(DEF_B), mR(DEF_R), mT(DEF_T), mPoints(), mID(0), mCells() + , mName(name.mPtr, name.mLen <= 0 ? 0 : name.mLen) + { + // Should we reserve some space for points in advance? + if (sz > 0) + { + mPoints.reserve(static_cast< size_t >(sz)); + } + // Insert the given points + AddPointEx(ax, ay); + AddPointEx(bx, by); + AddPointEx(cx, cy); + } + + /* -------------------------------------------------------------------------------------------- + * Copy constructor. + */ + Area(const Area & o) + : mL(o.mL), mB(o.mB), mR(o.mR), mT(o.mT), mPoints(o.mPoints), mID(o.mID), mCells(0), mName(o.mName) + { + //... + } + + /* -------------------------------------------------------------------------------------------- + * Move constructor. (disabled) + */ + Area(Area && o) = delete; + + /* -------------------------------------------------------------------------------------------- + * Destructor. + */ + ~Area() + { + //... + } + + /* -------------------------------------------------------------------------------------------- + * Copy assignment operator. (disabled) + */ + Area & operator = (const Area & o) = delete; + + /* -------------------------------------------------------------------------------------------- + * Move assignment operator. (disabled) + */ + Area & operator = (Area && o) = delete; + + /* -------------------------------------------------------------------------------------------- + * Used by the script engine to convert this instance to a string. + */ + const String & ToString() const + { + return mName; + } + + /* -------------------------------------------------------------------------------------------- + * Checks if the area is locked from changes and throws an exception if it is. + */ + void CheckLock() + { + // Are we connected to any cells? + if (!mCells.empty()) + { + STHROWF("The area cannot be modified while being managed"); + } + } + + /* -------------------------------------------------------------------------------------------- + * Retrieve the name of this area. + */ + const String & GetName() const + { + return mName; + } + + /* -------------------------------------------------------------------------------------------- + * Modify the name of this area. + */ + void SetName(const StackStrF & name) + { + if (name.mLen <= 0) + { + mName.clear(); + } + else + { + mName.assign(name.mPtr, static_cast< size_t >(name.mLen)); + } + } + + + /* -------------------------------------------------------------------------------------------- + * Modify the name of this area. (allows chaining function calls) + */ + Area & ApplyName(const StackStrF & name) + { + SetName(name); + return *this; + } + + /* -------------------------------------------------------------------------------------------- + * Retrieve the identifier of this area. + */ + SQInteger GetID() const + { + return mID; + } + + /* -------------------------------------------------------------------------------------------- + * Modify the identifier of this area. + */ + void SetID(SQInteger id) + { + mID = id; + } + + /* -------------------------------------------------------------------------------------------- + * Modify the identifier of this area. (allows chaining function calls) + */ + Area & ApplyID(SQInteger id) + { + mID = id; + return *this; + } + + /* -------------------------------------------------------------------------------------------- + * Check if the area is locked from changes + */ + bool IsLocked() const + { + return mCells.empty(); + } + + /* -------------------------------------------------------------------------------------------- + * Retrieve the center of this area. + */ + Vector2 GetCenter() const + { + return Vector2((mL * 0.5f) + (mR * 0.5f), (mB * 0.5f) + (mT * 0.5f)); + } + + /* -------------------------------------------------------------------------------------------- + * Retrieve the bounding box of this area. + */ + Vector4 GetBoundingBox() const + { + return Vector4(mL, mB, mR, mT); + } + + /* -------------------------------------------------------------------------------------------- + * See whether the area has no points. + */ + bool Empty() const + { + return mPoints.empty(); + } + + /* -------------------------------------------------------------------------------------------- + * Retrieve the number of points in this area. + */ + SQInteger Size() const + { + return ConvTo< SQInteger >::From(mPoints.size()); + } + + /* -------------------------------------------------------------------------------------------- + * Retrieve the number of points this area has allocated for. + */ + SQInteger Capacity() const + { + return ConvTo< SQInteger >::From(mPoints.capacity()); + } + + /* -------------------------------------------------------------------------------------------- + * Clear all points in this area. + */ + void Clear() + { + CheckLock(); + // Perform the requested action + mPoints.clear(); + } + + /* -------------------------------------------------------------------------------------------- + * Clear all points in this area. + */ + void Reserve(SQInteger sz) + { + // Perform the requested action + if (sz > 0) + { + mPoints.reserve(static_cast< size_t >(sz)); + } + } + + /* -------------------------------------------------------------------------------------------- + * Add a 2D vector to the point list. + */ + void AddPoint(const Vector2 & v) + { + CheckLock(); + // Perform the requested action + mPoints.emplace_back(v); + // Update the bounding box + Expand(v.x, v.y); + } + + /* -------------------------------------------------------------------------------------------- + * Add a point to the point list. + */ + void AddPointEx(float x, float y) + { + CheckLock(); + // Perform the requested action + mPoints.emplace_back(x, y); + // Update the bounding box + Expand(x, y); + } + + /* -------------------------------------------------------------------------------------------- + * Add an array of points to the point list. + */ + void AddArray(const Sqrat::Array & a); + + /* -------------------------------------------------------------------------------------------- + * Test if a point is inside the bounding box and then the area. + */ + bool Test(const Vector2 & v) + { + // Is the given point in this bounding box at least? + if (mL <= v.x && mR >= v.x && mB <= v.y && mT >= v.y) + { + return IsInside(v.x, v.y); + } + // Not in this area + return false; + } + + /* -------------------------------------------------------------------------------------------- + * Test if a point is inside the bounding box and then the area. + */ + bool TestEx(float x, float y) + { + // Is the given point in this bounding box at least? + if (mL <= x && mR >= x && mB <= y && mT >= y) + { + return IsInside(x, y); + } + // Not in this area + return false; + } + + /* -------------------------------------------------------------------------------------------- + * Add this area to the manager to be scanned. MUST BE CALLED ONLY FROM SCRIPT! + */ + bool Manage(); + + /* -------------------------------------------------------------------------------------------- + * Remove this area from the manager to no longer be scanned. + */ + bool Unmanage(); + +protected: + + /* -------------------------------------------------------------------------------------------- + * Test if a point is inside the area. + */ + bool IsInside(float x, float y) const; + + /* -------------------------------------------------------------------------------------------- + * Expand the bounding box area to include the given point. + */ + void Expand(float x, float y) + { + mL = std::fmin(mL, x); + mB = std::fmin(mB, y); + mR = std::fmax(mR, x); + mT = std::fmax(mT, y); + } +}; + +/* ------------------------------------------------------------------------------------------------ + * Manager responsible for storing and partitioning areas. +*/ +class AreaManager +{ +private: + + // -------------------------------------------------------------------------------------------- + static AreaManager s_Inst; // Manager instance. + + /* -------------------------------------------------------------------------------------------- + * Base constructor. + */ + AreaManager(size_t sz = 16); + + /* -------------------------------------------------------------------------------------------- + * Copy constructor. (disabled) + */ + AreaManager(const AreaManager & o) = delete; + + /* -------------------------------------------------------------------------------------------- + * Move constructor. (disabled) + */ + AreaManager(AreaManager && o) = delete; + + /* -------------------------------------------------------------------------------------------- + * Destructor. + */ + ~AreaManager() + { + //... + } + + /* -------------------------------------------------------------------------------------------- + * Copy assignment operator. (disabled) + */ + AreaManager & operator = (const AreaManager & o) = delete; + + /* -------------------------------------------------------------------------------------------- + * Move assignment operator. (disabled) + */ + AreaManager & operator = (AreaManager && o) = delete; + +protected: + + /* -------------------------------------------------------------------------------------------- + * Helper used to make sure a cell is properly processed after leaving the scope. + */ + struct CellGuard + { + AreaCell & mCell; + /* ---------------------------------------------------------------------------------------- + * Base constructor. + */ + CellGuard(AreaCell & cell) + : mCell(cell) + { + ++(cell.mLocks); // Place a lock on the cell to prevent iterator invalidation + } + /* ---------------------------------------------------------------------------------------- + * Destructor. + */ + ~CellGuard() + { + // Remove the lock from the cell so it can be processed + --(mCell.mLocks); + // Process requested actions during the lock + AreaManager::Get().ProcQueue(); + } + }; + + // -------------------------------------------------------------------------------------------- + static constexpr int GRIDN = 16; // Number of horizontal and vertical number of cells. + static constexpr int GRIDH = GRIDN/2; // Half of the grid horizontal and vertical size. + static constexpr int CELLS = GRIDN*GRIDN; // Total number of cells in the grid. + static constexpr int CELLH = CELLS/2; // Half total number of cells in the grid. + static constexpr int CELLD = 256; // Area covered by a cell in the world. + static constexpr int NOCELL = std::numeric_limits< int >::max(); // Inexistent cell index. + + /* -------------------------------------------------------------------------------------------- + * Helper used to queue a certain action if the cell is locked. + */ + struct QueueElement + { + // ---------------------------------------------------------------------------------------- + AreaCell * mCell; // The cell to be affected. + Area * mArea; // The area that made the request. + LightObj mObj; // Strong reference to the object. + /* ---------------------------------------------------------------------------------------- + * Base constructor. + */ + QueueElement(AreaCell & cell, Area & area) + : mCell(&cell), mArea(&area), mObj() + { + //... + } + /* ---------------------------------------------------------------------------------------- + * Base constructor. + */ + QueueElement(AreaCell & cell, Area & area, LightObj & obj) + : mCell(&cell), mArea(&area), mObj(obj) + { + //... + } + + /* -------------------------------------------------------------------------------------------- + * Copy constructor. (disabled) + */ + QueueElement(const QueueElement & o) = delete; + + /* -------------------------------------------------------------------------------------------- + * Move constructor. + */ + QueueElement(QueueElement && o) + : mCell(o.mCell), mArea(o.mArea), mObj(std::move(o.mObj)) + { + // Take ownership + o.mCell = nullptr; + o.mArea = nullptr; + } + + /* -------------------------------------------------------------------------------------------- + * Destructor. + */ + ~QueueElement() + { + //... + } + + /* -------------------------------------------------------------------------------------------- + * Copy assignment operator. (disabled) + */ + QueueElement & operator = (const QueueElement & o) = delete; + + /* -------------------------------------------------------------------------------------------- + * Move assignment operator. + */ + QueueElement & operator = (QueueElement && o) + { + // Avoid self assignment + if (this != &o) + { + // Transfer values + mCell = o.mCell; + mArea = o.mArea; + mObj = std::move(o.mObj); + // Take ownership + o.mCell = nullptr; + o.mArea = nullptr; + } + + return *this; + } + }; + // -------------------------------------------------------------------------------------------- + typedef std::vector< QueueElement > Queue; // Queued actions. + typedef std::vector< Queue::iterator > ProcList; // Elements in the queue redy to process. + + /* -------------------------------------------------------------------------------------------- + * Attempt to insert an area into a cell or queue the action if not possible. + */ + void Insert(AreaCell & c, Area & a, LightObj & obj); + + /* -------------------------------------------------------------------------------------------- + * Attempt to remove an area from a cell or queue the action if not possible. + */ + void Remove(AreaCell & c, Area & a); + +private: + + // -------------------------------------------------------------------------------------------- + Queue m_Queue; // Actions currently queued. + ProcList m_ProcList; // Actions ready to be completed. + // -------------------------------------------------------------------------------------------- + AreaCell m_Grid[GRIDN][GRIDN]; // A grid of area lists. + +public: + + /* -------------------------------------------------------------------------------------------- + * Retrieve the core instance. + */ + static AreaManager & Get() + { + return s_Inst; + } + + /* -------------------------------------------------------------------------------------------- + * Attempt to process elements in the queue that can be completed. + */ + void ProcQueue(); + + /* -------------------------------------------------------------------------------------------- + * Clear all cell lists and release any script references. + */ + void Clear(); + + /* -------------------------------------------------------------------------------------------- + * Add an area to be managed. + */ + void InsertArea(Area & a, LightObj & obj); + + /* -------------------------------------------------------------------------------------------- + * Add an area to be managed. + */ + void RemoveArea(Area & a); + + /* -------------------------------------------------------------------------------------------- + * Clear all cell lists and release any script references. + */ + Vector2i LocateCell(float x, float y); + + /* -------------------------------------------------------------------------------------------- + * Test a point to see whether it intersects with any areas + */ + template < typename F > void TestPoint(F && f, float x, float y) + { + // Transform the world coordinates into a cell coordinates + const Vector2i cc(LocateCell(x, y)); + // Were these coordinates valid? + if (cc.x == NOCELL) + { + return; // Not our problem + } + // Retrieve a reference to the identified cell + AreaCell & c = m_Grid[cc.y][cc.x]; + // Is this cell empty? + if (c.mAreas.empty()) + { + return; // Nothing to test + } + // Guard the cell while processing + const CellGuard cg(c); + // Finally, begin processing the areas in this cell + for (auto & a : c.mAreas) + { + if (a.first->TestEx(x, y)) + { + f(a); + } + } + } +}; + +} // Namespace:: SqMod + +#endif // _AREAS_HPP_ diff --git a/source/Core.cpp b/source/Core.cpp index adc8526a..4e841950 100644 --- a/source/Core.cpp +++ b/source/Core.cpp @@ -37,6 +37,7 @@ extern bool RegisterAPI(HSQUIRRELVM vm); // ------------------------------------------------------------------------------------------------ extern void InitializeTasks(); extern void InitializeRoutines(); +extern void TerminateAreas(); extern void TerminateTasks(); extern void TerminateRoutines(); extern void TerminateCommands(); @@ -146,6 +147,7 @@ Core::Core() , m_ReloadPayload() , m_IncomingNameBuffer(nullptr) , m_IncomingNameCapacity(0) + , m_AreasEnabled(false) , m_Debugging(false) , m_Executed(false) , m_Shutdown(false) @@ -480,6 +482,8 @@ void Core::Terminate(bool shutdown) TerminateCommands(); // Release all resources from signals TerminateSignals(); + // Release all managed areas + TerminateAreas(); // In case there's a payload for reload m_ReloadPayload.Release(); // Release null objects in case any reference to valid objects is stored in them diff --git a/source/Core.hpp b/source/Core.hpp index d277d224..353da6e5 100644 --- a/source/Core.hpp +++ b/source/Core.hpp @@ -66,6 +66,9 @@ private: protected: + // -------------------------------------------------------------------------------------------- + typedef std::vector< std::pair< Area *, LightObj > > AreaList; // List of colided areas. + /* -------------------------------------------------------------------------------------------- * Helper structure used to identify a blip entity instance on the server. */ @@ -449,6 +452,9 @@ protected: CPlayer * mInst; // Pointer to the actual instance used to interact this entity. LightObj mObj; // Script object of the instance used to interact this entity. + // ---------------------------------------------------------------------------------------- + AreaList mAreas; // Areas the player is currently in. + // ---------------------------------------------------------------------------------------- SQInteger mTrackPosition; // The number of times to track position changes. SQInteger mTrackHeading; // The number of times to track heading changes. @@ -548,6 +554,8 @@ protected: SignalPair mOnWantedLevel; SignalPair mOnImmunity; SignalPair mOnAlpha; + SignalPair mOnEnterArea; + SignalPair mOnLeaveArea; }; /* -------------------------------------------------------------------------------------------- @@ -600,6 +608,9 @@ protected: CVehicle * mInst; // Pointer to the actual instance used to interact this entity. LightObj mObj; // Script object of the instance used to interact this entity. + // ---------------------------------------------------------------------------------------- + AreaList mAreas; // Areas the vehicle is currently in. + // ---------------------------------------------------------------------------------------- SQInteger mTrackPosition; // The number of times to track position changes. SQInteger mTrackRotation; // The number of times to track rotation changes. @@ -637,6 +648,8 @@ protected: SignalPair mOnDamageData; SignalPair mOnRadio; SignalPair mOnHandlingRule; + SignalPair mOnEnterArea; + SignalPair mOnLeaveArea; }; public: @@ -688,6 +701,7 @@ private: size_t m_IncomingNameCapacity; // Incoming connection name size. // -------------------------------------------------------------------------------------------- + bool m_AreasEnabled; // Whether area tracking is enabled. bool m_Debugging; // Enable debugging features, if any. bool m_Executed; // Whether the scripts were executed. bool m_Shutdown; // Whether the server currently shutting down. @@ -695,7 +709,6 @@ private: bool m_LockPostLoadSignal; // Lock post load signal container. bool m_LockUnloadSignal; // Lock unload signal container. bool m_EmptyInit; // Whether to initialize without any scripts. - // -------------------------------------------------------------------------------------------- Int32 m_Verbosity; // Restrict the amount of outputted information. @@ -778,6 +791,22 @@ public: return m_Shutdown; } + /* -------------------------------------------------------------------------------------------- + * See whether area tracking should be enabled on newlly created entities. + */ + bool AreasEnabled() const + { + return m_AreasEnabled; + } + + /* -------------------------------------------------------------------------------------------- + * Toggle whether area tracking should be enabled on newlly created entities. + */ + void AreasEnabled(bool toggle) + { + m_AreasEnabled = toggle; + } + /* -------------------------------------------------------------------------------------------- * Retrieve the value of the specified option. */ @@ -1168,6 +1197,8 @@ public: void EmitPlayerWantedLevel(Int32 player_id, Int32 old_level, Int32 new_level); void EmitPlayerImmunity(Int32 player_id, Int32 old_immunity, Int32 new_immunity); void EmitPlayerAlpha(Int32 player_id, Int32 old_alpha, Int32 new_alpha, Int32 fade); + void EmitPlayerEnterArea(Int32 player_id, LightObj & area_obj); + void EmitPlayerLeaveArea(Int32 player_id, LightObj & area_obj); void EmitVehicleColor(Int32 vehicle_id, Int32 changed); void EmitVehicleHealth(Int32 vehicle_id, Float32 old_health, Float32 new_health); void EmitVehiclePosition(Int32 vehicle_id); @@ -1180,6 +1211,8 @@ public: void EmitVehicleDamageData(Int32 vehicle_id, Uint32 old_data, Uint32 new_data); void EmitVehicleRadio(Int32 vehicle_id, Int32 old_radio, Int32 new_radio); void EmitVehicleHandlingRule(Int32 vehicle_id, Int32 rule, Float32 old_data, Float32 new_data); + void EmitVehicleEnterArea(Int32 player_id, LightObj & area_obj); + void EmitVehicleLeaveArea(Int32 player_id, LightObj & area_obj); void EmitServerOption(Int32 option, bool value, Int32 header, LightObj & payload); void EmitScriptReload(Int32 header, LightObj & payload); void EmitScriptLoaded(); @@ -1321,6 +1354,8 @@ public: SignalPair mOnPlayerWantedLevel; SignalPair mOnPlayerImmunity; SignalPair mOnPlayerAlpha; + SignalPair mOnPlayerEnterArea; + SignalPair mOnPlayerLeaveArea; SignalPair mOnVehicleColor; SignalPair mOnVehicleHealth; SignalPair mOnVehiclePosition; @@ -1333,6 +1368,8 @@ public: SignalPair mOnVehicleDamageData; SignalPair mOnVehicleRadio; SignalPair mOnVehicleHandlingRule; + SignalPair mOnVehicleEnterArea; + SignalPair mOnVehicleLeaveArea; SignalPair mOnServerOption; SignalPair mOnScriptReload; SignalPair mOnScriptLoaded; diff --git a/source/CoreEntity.cpp b/source/CoreEntity.cpp index 367d084b..728bd972 100644 --- a/source/CoreEntity.cpp +++ b/source/CoreEntity.cpp @@ -420,6 +420,11 @@ Core::VehicleInst & Core::AllocVehicle(Int32 id, bool owned, Int32 header, Light { inst.mFlags ^= ENF_OWNED; } + // Should we enable area tracking? + if (m_AreasEnabled) + { + inst.mFlags |= ENF_AREA_TRACK; + } // Initialize the instance events inst.InitEvents(); // Let the script callbacks know about this entity @@ -795,6 +800,11 @@ void Core::ConnectPlayer(Int32 id, Int32 header, LightObj & payload) } // Assign the specified entity identifier inst.mID = id; + // Should we enable area tracking? + if (m_AreasEnabled) + { + inst.mFlags |= ENF_AREA_TRACK; + } // Initialize the position _Func->GetPlayerPosition(id, &inst.mLastPosition.x, &inst.mLastPosition.y, &inst.mLastPosition.z); // Initialize the remaining attributes diff --git a/source/CoreEvents.cpp b/source/CoreEvents.cpp index 296c3150..7e9df195 100644 --- a/source/CoreEvents.cpp +++ b/source/CoreEvents.cpp @@ -1,5 +1,6 @@ // ------------------------------------------------------------------------------------------------ #include "Core.hpp" +#include "Areas.hpp" #include "Signal.hpp" #include "Base/Buffer.hpp" #include "Library/Utils/Buffer.hpp" @@ -879,6 +880,22 @@ void Core::EmitPlayerAlpha(Int32 player_id, Int32 old_alpha, Int32 new_alpha, In (*mOnPlayerAlpha.first)(_player.mObj, old_alpha, new_alpha, fade); } +// ------------------------------------------------------------------------------------------------ +void Core::EmitPlayerEnterArea(Int32 player_id, LightObj & area_obj) +{ + PlayerInst & _player = m_Players.at(player_id); + (*_player.mOnEnterArea.first)(area_obj); + (*mOnPlayerEnterArea.first)(_player.mObj, area_obj); +} + +// ------------------------------------------------------------------------------------------------ +void Core::EmitPlayerLeaveArea(Int32 player_id, LightObj & area_obj) +{ + PlayerInst & _player = m_Players.at(player_id); + (*_player.mOnLeaveArea.first)(area_obj); + (*mOnPlayerLeaveArea.first)(_player.mObj, area_obj); +} + // ------------------------------------------------------------------------------------------------ void Core::EmitVehicleColor(Int32 vehicle_id, Int32 changed) { @@ -972,8 +989,24 @@ void Core::EmitVehicleRadio(Int32 vehicle_id, Int32 old_radio, Int32 new_radio) void Core::EmitVehicleHandlingRule(Int32 vehicle_id, Int32 rule, Float32 old_data, Float32 new_data) { VehicleInst & _vehicle = m_Vehicles.at(vehicle_id); - (*mOnVehicleHandlingRule.first)(_vehicle.mObj, rule, old_data, new_data); (*_vehicle.mOnHandlingRule.first)(rule, old_data, new_data); + (*mOnVehicleHandlingRule.first)(_vehicle.mObj, rule, old_data, new_data); +} + +// ------------------------------------------------------------------------------------------------ +void Core::EmitVehicleEnterArea(Int32 vehicle_id, LightObj & area_obj) +{ + VehicleInst & _vehicle = m_Vehicles.at(vehicle_id); + (*_vehicle.mOnEnterArea.first)(area_obj); + (*mOnVehicleEnterArea.first)(_vehicle.mObj, area_obj); +} + +// ------------------------------------------------------------------------------------------------ +void Core::EmitVehicleLeaveArea(Int32 vehicle_id, LightObj & area_obj) +{ + VehicleInst & _vehicle = m_Vehicles.at(vehicle_id); + (*_vehicle.mOnLeaveArea.first)(area_obj); + (*mOnVehicleLeaveArea.first)(_vehicle.mObj, area_obj); } // ------------------------------------------------------------------------------------------------ @@ -1150,6 +1183,39 @@ void Core::EmitPlayerUpdate(Int32 player_id, vcmpPlayerUpdate update_type) // Now emit the event EmitPlayerPosition(player_id); } + // Should we check for area collision + if (inst.mFlags & ENF_AREA_TRACK) + { + // Eliminate existing areas first, if the player is not in them anymore + inst.mAreas.erase(std::remove_if(inst.mAreas.begin(), inst.mAreas.end(), + [this, pos, player_id](AreaList::reference ap) -> bool { + // Is this player still in this area? + if (!ap.first->TestEx(pos.x, pos.y)) + { + // Emit the script event + this->EmitPlayerLeaveArea(player_id, ap.second); + // Remove this area + return true; + } + // Still in this area + return false; + }), inst.mAreas.end()); + // See if the player entered any new areas + AreaManager::Get().TestPoint([this, &inst, player_id](AreaList::reference ap) -> void { + // Was the player in this area before? + if (std::find_if(inst.mAreas.begin(), inst.mAreas.end(), + [a = ap.first](AreaList::reference ap) -> bool { + return (a == ap.first); + }) == inst.mAreas.end()) + { + // The player just entered this area so emit the event + this->EmitPlayerEnterArea(player_id, ap.second); + // Now store this area so we know when the player leaves + inst.mAreas.emplace_back(ap); + } + // The player was in this area before so ignore it + }, pos.x, pos.y); + } // Update the tracked value inst.mLastPosition = pos; } @@ -1218,9 +1284,45 @@ void Core::EmitVehicleUpdate(Int32 vehicle_id, vcmpVehicleUpdate update_type) // Now emit the event EmitVehiclePosition(vehicle_id); } + // New vehicle position + Vector3 pos; + // Retrieve the current vehicle position + _Func->GetVehiclePosition(vehicle_id, &pos.x, &pos.y, &pos.z); + // Should we check for area collision + if (inst.mFlags & ENF_AREA_TRACK) + { + // Eliminate existing areas first, if the vehicle is not in them anymore + inst.mAreas.erase(std::remove_if(inst.mAreas.begin(), inst.mAreas.end(), + [this, pos, vehicle_id](AreaList::reference ap) -> bool { + // Is this vehicle still in this area? + if (!ap.first->TestEx(pos.x, pos.y)) + { + // Emit the script event + this->EmitVehicleLeaveArea(vehicle_id, ap.second); + // Remove this area + return true; + } + // Still in this area + return false; + }), inst.mAreas.end()); + // See if the vehicle entered any new areas + AreaManager::Get().TestPoint([this, &inst, vehicle_id](AreaList::reference ap) -> void { + // Was the vehicle in this area before? + if (std::find_if(inst.mAreas.begin(), inst.mAreas.end(), + [a = ap.first](AreaList::reference ap) -> bool { + return (a == ap.first); + }) == inst.mAreas.end()) + { + // The vehicle just entered this area so emit the event + this->EmitVehicleEnterArea(vehicle_id, ap.second); + // Now store this area so we know when the vehicle leaves + inst.mAreas.emplace_back(ap); + } + // The vehicle was in this area before so ignore it + }, pos.x, pos.y); + } // Update the tracked value - _Func->GetVehiclePosition(vehicle_id, &inst.mLastPosition.x, - &inst.mLastPosition.y, &inst.mLastPosition.z); + inst.mLastPosition = pos; } break; case vcmpVehicleUpdateHealth: { diff --git a/source/CoreFuncs.cpp b/source/CoreFuncs.cpp index 105e34a0..be5dfe39 100644 --- a/source/CoreFuncs.cpp +++ b/source/CoreFuncs.cpp @@ -119,6 +119,18 @@ static void SetState(Int32 value) return Core::Get().SetState(value); } +// ------------------------------------------------------------------------------------------------ +static bool GetAreasEnabled() +{ + return Core::Get().AreasEnabled(); +} + +// ------------------------------------------------------------------------------------------------ +static void SetAreasEnabled(bool toggle) +{ + Core::Get().AreasEnabled(toggle); +} + // ------------------------------------------------------------------------------------------------ static CSStr GetOption(CSStr name) { @@ -306,6 +318,8 @@ void Register_Core(HSQUIRRELVM vm) .Func(_SC("GetReloadPayload"), &SqGetReloadPayload) .Func(_SC("GetState"), &GetState) .Func(_SC("SetState"), &SetState) + .Func(_SC("AreasEnabled"), &GetAreasEnabled) + .Func(_SC("SetAreasEnabled"), &SetAreasEnabled) .Func(_SC("GetOption"), &GetOption) .Func(_SC("GetOptionOr"), &GetOptionOr) .Func(_SC("SetOption"), &SetOption) diff --git a/source/CoreInst.cpp b/source/CoreInst.cpp index c19916d7..09adf1ee 100644 --- a/source/CoreInst.cpp +++ b/source/CoreInst.cpp @@ -357,6 +357,7 @@ void Core::PlayerInst::ResetInstance() { mID = -1; mFlags = ENF_DEFAULT; + mAreas.clear(); mTrackPosition = 0; mTrackHeading = 0; mTrackPositionHeader = 0; @@ -376,6 +377,7 @@ void Core::VehicleInst::ResetInstance() { mID = -1; mFlags = ENF_DEFAULT; + mAreas.clear(); mTrackPosition = 0; mTrackRotation = 0; mLastPrimaryColor = -1; @@ -642,6 +644,8 @@ void Core::PlayerInst::InitEvents() InitSignalPair(mOnWantedLevel, mEvents, "WantedLevel"); InitSignalPair(mOnImmunity, mEvents, "Immunity"); InitSignalPair(mOnAlpha, mEvents, "Alpha"); + InitSignalPair(mOnEnterArea, mEvents, "EnterArea"); + InitSignalPair(mOnLeaveArea, mEvents, "LeaveArea"); } // ------------------------------------------------------------------------------------------------ @@ -718,6 +722,8 @@ void Core::PlayerInst::DropEvents() ResetSignalPair(mOnWantedLevel); ResetSignalPair(mOnImmunity); ResetSignalPair(mOnAlpha); + ResetSignalPair(mOnEnterArea); + ResetSignalPair(mOnLeaveArea); mEvents.Release(); } @@ -756,6 +762,8 @@ void Core::VehicleInst::InitEvents() InitSignalPair(mOnDamageData, mEvents, "DamageData"); InitSignalPair(mOnRadio, mEvents, "Radio"); InitSignalPair(mOnHandlingRule, mEvents, "HandlingRule"); + InitSignalPair(mOnEnterArea, mEvents, "EnterArea"); + InitSignalPair(mOnLeaveArea, mEvents, "LeaveArea"); } // ------------------------------------------------------------------------------------------------ @@ -781,6 +789,8 @@ void Core::VehicleInst::DropEvents() ResetSignalPair(mOnDamageData); ResetSignalPair(mOnRadio); ResetSignalPair(mOnHandlingRule); + ResetSignalPair(mOnEnterArea); + ResetSignalPair(mOnLeaveArea); mEvents.Release(); } diff --git a/source/CoreUtils.cpp b/source/CoreUtils.cpp index 7d55b9c3..295c0676 100644 --- a/source/CoreUtils.cpp +++ b/source/CoreUtils.cpp @@ -165,6 +165,8 @@ void Core::InitEvents() InitSignalPair(mOnPlayerWantedLevel, m_Events, "PlayerWantedLevel"); InitSignalPair(mOnPlayerImmunity, m_Events, "PlayerImmunity"); InitSignalPair(mOnPlayerAlpha, m_Events, "PlayerAlpha"); + InitSignalPair(mOnPlayerEnterArea, m_Events, "PlayerEnterArea"); + InitSignalPair(mOnPlayerLeaveArea, m_Events, "PlayerLeaveArea"); InitSignalPair(mOnVehicleColor, m_Events, "VehicleColor"); InitSignalPair(mOnVehicleHealth, m_Events, "VehicleHealth"); InitSignalPair(mOnVehiclePosition, m_Events, "VehiclePosition"); @@ -177,6 +179,8 @@ void Core::InitEvents() InitSignalPair(mOnVehicleDamageData, m_Events, "VehicleDamageData"); InitSignalPair(mOnVehicleRadio, m_Events, "VehicleRadio"); InitSignalPair(mOnVehicleHandlingRule, m_Events, "VehicleHandlingRule"); + InitSignalPair(mOnVehicleEnterArea, m_Events, "VehicleEnterArea"); + InitSignalPair(mOnVehicleLeaveArea, m_Events, "VehicleLeaveArea"); InitSignalPair(mOnServerOption, m_Events, "ServerOption"); InitSignalPair(mOnScriptReload, m_Events, "ScriptReload"); InitSignalPair(mOnScriptLoaded, m_Events, "ScriptLoaded"); @@ -293,6 +297,8 @@ void Core::DropEvents() ResetSignalPair(mOnPlayerWantedLevel); ResetSignalPair(mOnPlayerImmunity); ResetSignalPair(mOnPlayerAlpha); + ResetSignalPair(mOnPlayerEnterArea); + ResetSignalPair(mOnPlayerLeaveArea); ResetSignalPair(mOnVehicleColor); ResetSignalPair(mOnVehicleHealth); ResetSignalPair(mOnVehiclePosition); @@ -305,6 +311,8 @@ void Core::DropEvents() ResetSignalPair(mOnVehicleDamageData); ResetSignalPair(mOnVehicleRadio); ResetSignalPair(mOnVehicleHandlingRule); + ResetSignalPair(mOnVehicleEnterArea); + ResetSignalPair(mOnVehicleLeaveArea); ResetSignalPair(mOnServerOption); ResetSignalPair(mOnScriptReload); ResetSignalPair(mOnScriptLoaded); diff --git a/source/Entity/Player.cpp b/source/Entity/Player.cpp index 093e00d0..75937797 100644 --- a/source/Entity/Player.cpp +++ b/source/Entity/Player.cpp @@ -6,6 +6,7 @@ #include "Base/Vector3.hpp" #include "Library/Utils/Buffer.hpp" #include "Core.hpp" +#include "Areas.hpp" #include "Tasks.hpp" // ------------------------------------------------------------------------------------------------ @@ -1431,6 +1432,83 @@ LightObj & CPlayer::CreateCheckpoint(Int32 world, bool sphere, const Vector3 & p color.r, color.g, color.b, color.a, radius, header, payload); } +// ------------------------------------------------------------------------------------------------ +bool CPlayer::GetCollideAreas() const +{ + // Validate the managed identifier + Validate(); + // Return the requested information + return (Core::Get().GetPlayer(m_ID).mFlags & ENF_AREA_TRACK); +} + +void CPlayer::SetCollideAreas(bool toggle) const +{ + // Validate the managed identifier + Validate(); + // Perform the requested operation + if (toggle) + { + Core::Get().GetPlayer(m_ID).mFlags |= ENF_AREA_TRACK; + } + else + { + // Obtain the actual entity instance + auto & inst = Core::Get().GetPlayer(m_ID); + // Is this option even enabled? + if (!(inst.mFlags & ENF_AREA_TRACK)) + { + return; // Not enabled to begin with + } + // Disable the option + inst.mFlags ^= ENF_AREA_TRACK; + // Clear current areas + inst.mAreas.clear(); + } +} + +void CPlayer::SetAreasCollide(bool toggle) const +{ + // Validate the managed identifier + Validate(); + // Perform the requested operation + if (toggle) + { + Core::Get().GetPlayer(m_ID).mFlags |= ENF_AREA_TRACK; + } + else + { + // Obtain the actual entity instance + auto & inst = Core::Get().GetPlayer(m_ID); + // Is this option even enabled? + if (!(inst.mFlags & ENF_AREA_TRACK)) + { + return; // Not enabled to begin with + } + // Disable the option + inst.mFlags ^= ENF_AREA_TRACK; + // Is the player currently in any areas? + if (inst.mAreas.empty()) + { + return; // Nothing to test + } + + Vector3 pos; + // Obtain the current position of this instance + _Func->GetPlayerPosition(m_ID, &pos.x, &pos.y, &pos.z); + // Do a final check to see if the player left any area + for (auto & ap : inst.mAreas) + { + // Is the player still in this area? + if (!ap.first->TestEx(pos.x, pos.y)) + { + Core::Get().EmitPlayerLeaveArea(m_ID, ap.second); // Emit the script event + } + } + // Clear current areas + inst.mAreas.clear(); + } +} + // ------------------------------------------------------------------------------------------------ Int32 CPlayer::GetAuthority() const { @@ -2423,6 +2501,7 @@ void Register_CPlayer(HSQUIRRELVM vm) .Prop(_SC("TouchedObject"), &CPlayer::StandingOnObject) .Prop(_SC("Away"), &CPlayer::IsAway) .Prop(_SC("Spec"), &CPlayer::GetSpectator, &CPlayer::SetSpectator) + .Prop(_SC("CollideAreas"), &CPlayer::GetCollideAreas, &CPlayer::SetCollideAreas) .Prop(_SC("Authority"), &CPlayer::GetAuthority, &CPlayer::SetAuthority) .Prop(_SC("TrackPosition"), &CPlayer::GetTrackPosition, &CPlayer::SetTrackPosition) .Prop(_SC("TrackHeading"), &CPlayer::GetTrackHeading, &CPlayer::SetTrackHeading) @@ -2472,6 +2551,7 @@ void Register_CPlayer(HSQUIRRELVM vm) .Func(_SC("Spectate"), &CPlayer::SetSpectator) .Func(_SC("Redirect"), &CPlayer::Redirect) .Func(_SC("PlaySound"), &CPlayer::PlaySound) + .Func(_SC("AreasCollide"), &CPlayer::SetAreasCollide) .Func(_SC("GetMsgPrefix"), &CPlayer::GetMessagePrefix) .FmtFunc(_SC("SetMsgPrefix"), &CPlayer::SetMessagePrefix) .Func(_SC("SetTrackPosition"), &CPlayer::SetTrackPositionEx) diff --git a/source/Entity/Player.hpp b/source/Entity/Player.hpp index adb2acdc..e20038f2 100644 --- a/source/Entity/Player.hpp +++ b/source/Entity/Player.hpp @@ -765,6 +765,21 @@ public: LightObj & CreateCheckpoint(Int32 world, bool sphere, const Vector3 & pos, const Color4 & color, Float32 radius, Int32 header, LightObj & payload) const; + /* -------------------------------------------------------------------------------------------- + * See whether the managed player entity collides with user defined areas. + */ + bool GetCollideAreas() const; + + /* -------------------------------------------------------------------------------------------- + * Set whether the managed player entity can collide with user defined areas. + */ + void SetCollideAreas(bool toggle) const; + + /* -------------------------------------------------------------------------------------------- + * Set whether the managed player entity can collide with user defined areas (with last test). + */ + void SetAreasCollide(bool toggle) const; + /* -------------------------------------------------------------------------------------------- * Retrieve the authority level of the managed player entity. */ diff --git a/source/Entity/Vehicle.cpp b/source/Entity/Vehicle.cpp index 3b7cd0c6..af1b7cba 100644 --- a/source/Entity/Vehicle.cpp +++ b/source/Entity/Vehicle.cpp @@ -5,6 +5,7 @@ #include "Base/Vector2.hpp" #include "Base/Vector3.hpp" #include "Core.hpp" +#include "Areas.hpp" #include "Tasks.hpp" // ------------------------------------------------------------------------------------------------ @@ -1119,6 +1120,83 @@ bool CVehicle::Embark(CPlayer & player, Int32 slot, bool allocate, bool warp) co != vcmpErrorRequestDenied); } +// ------------------------------------------------------------------------------------------------ +bool CVehicle::GetCollideAreas() const +{ + // Validate the managed identifier + Validate(); + // Return the requested information + return (Core::Get().GetVehicle(m_ID).mFlags & ENF_AREA_TRACK); +} + +void CVehicle::SetCollideAreas(bool toggle) const +{ + // Validate the managed identifier + Validate(); + // Perform the requested operation + if (toggle) + { + Core::Get().GetVehicle(m_ID).mFlags |= ENF_AREA_TRACK; + } + else + { + // Obtain the actual entity instance + auto & inst = Core::Get().GetVehicle(m_ID); + // Is this option even enabled? + if (!(inst.mFlags & ENF_AREA_TRACK)) + { + return; // Not enabled to begin with + } + // Disable the option + inst.mFlags ^= ENF_AREA_TRACK; + // Clear current areas + inst.mAreas.clear(); + } +} + +void CVehicle::SetAreasCollide(bool toggle) const +{ + // Validate the managed identifier + Validate(); + // Perform the requested operation + if (toggle) + { + Core::Get().GetVehicle(m_ID).mFlags |= ENF_AREA_TRACK; + } + else + { + // Obtain the actual entity instance + auto & inst = Core::Get().GetVehicle(m_ID); + // Is this option even enabled? + if (!(inst.mFlags & ENF_AREA_TRACK)) + { + return; // Not enabled to begin with + } + // Disable the option + inst.mFlags ^= ENF_AREA_TRACK; + // Is the vehicle currently in any areas? + if (inst.mAreas.empty()) + { + return; // Nothing to test + } + + Vector3 pos; + // Obtain the current position of this instance + _Func->GetVehiclePosition(m_ID, &pos.x, &pos.y, &pos.z); + // Do a final check to see if the vehicle left any area + for (auto & ap : inst.mAreas) + { + // Is the vehicle still in this area? + if (!ap.first->TestEx(pos.x, pos.y)) + { + Core::Get().EmitVehicleLeaveArea(m_ID, ap.second); // Emit the script event + } + } + // Clear current areas + inst.mAreas.clear(); + } +} + // ------------------------------------------------------------------------------------------------ SQInteger CVehicle::GetTrackPosition() const { @@ -1820,6 +1898,7 @@ void Register_CVehicle(HSQUIRRELVM vm) .Prop(_SC("HorizontalTurretRotation"), &CVehicle::GetHorizontalTurretRotation) .Prop(_SC("VerTurretRotation"), &CVehicle::GetVerticalTurretRotation) .Prop(_SC("VerticalTurretRotation"), &CVehicle::GetVerticalTurretRotation) + .Prop(_SC("CollideAreas"), &CVehicle::GetCollideAreas, &CVehicle::SetCollideAreas) .Prop(_SC("TrackPosition"), &CVehicle::GetTrackPosition, &CVehicle::SetTrackPosition) .Prop(_SC("TrackRotation"), &CVehicle::GetTrackRotation, &CVehicle::SetTrackRotation) .Prop(_SC("LastPrimaryColor"), &CVehicle::GetLastPrimaryColor) @@ -1885,6 +1964,7 @@ void Register_CVehicle(HSQUIRRELVM vm) .Func(_SC("SetHandlingRule"), &CVehicle::SetHandlingRule) .Func(_SC("ResetHandlingRule"), &CVehicle::ResetHandlingRule) .Func(_SC("ResetHandlings"), &CVehicle::ResetHandlings) + .Func(_SC("AreasCollide"), &CVehicle::SetAreasCollide) // Member Overloads .Overload< void (CVehicle::*)(const Vector3 &, bool) const > (_SC("SetPos"), &CVehicle::SetPositionEx) diff --git a/source/Entity/Vehicle.hpp b/source/Entity/Vehicle.hpp index 26fc7cd9..1d39ee65 100644 --- a/source/Entity/Vehicle.hpp +++ b/source/Entity/Vehicle.hpp @@ -612,6 +612,21 @@ public: */ bool Embark(CPlayer & player, Int32 slot, bool allocate, bool warp) const; + /* -------------------------------------------------------------------------------------------- + * See whether the managed vehicle entity collides with user defined areas. + */ + bool GetCollideAreas() const; + + /* -------------------------------------------------------------------------------------------- + * Set whether the managed vehicle entity can collide with user defined areas. + */ + void SetCollideAreas(bool toggle) const; + + /* -------------------------------------------------------------------------------------------- + * Set whether the managed vehicle entity can collide with user defined areas (with last test). + */ + void SetAreasCollide(bool toggle) const; + /* -------------------------------------------------------------------------------------------- * Retrieve the amount of tracked position changes for the managed vehicle entity. */ diff --git a/source/Register.cpp b/source/Register.cpp index f110e47f..cc9c1f74 100644 --- a/source/Register.cpp +++ b/source/Register.cpp @@ -53,6 +53,7 @@ extern void RegisterTask(HSQUIRRELVM vm); // ------------------------------------------------------------------------------------------------ extern void Register_Misc(HSQUIRRELVM vm); +extern void Register_Areas(HSQUIRRELVM vm); extern void Register_Signal(HSQUIRRELVM vm); // ------------------------------------------------------------------------------------------------ @@ -95,6 +96,7 @@ bool RegisterAPI(HSQUIRRELVM vm) RegisterTask(vm); Register_Misc(vm); + Register_Areas(vm); Register_Signal(vm); return true; diff --git a/source/SqBase.hpp b/source/SqBase.hpp index 4d1fe36d..39d0f724 100644 --- a/source/SqBase.hpp +++ b/source/SqBase.hpp @@ -243,6 +243,11 @@ class Core; class Logger; class Signal; +// ------------------------------------------------------------------------------------------------ +class AreaManager; +struct AreaCell; +struct Area; + // ------------------------------------------------------------------------------------------------ class CmdManager; class CmdListener;