diff --git a/rwengine/include/engine/GameWorld.hpp b/rwengine/include/engine/GameWorld.hpp index 7078befe..2c29a60f 100644 --- a/rwengine/include/engine/GameWorld.hpp +++ b/rwengine/include/engine/GameWorld.hpp @@ -15,6 +15,7 @@ class GameState; class CutsceneObject; class WorkContext; +#include class GameObject; class CharacterObject; @@ -95,7 +96,12 @@ public: * Creates a pedestrian. */ CharacterObject* createPedestrian(const uint16_t id, const glm::vec3& pos, const glm::quat& rot = glm::quat()); - + + /** + * Inserts the given game object into the world. + */ + void insertObject(GameObject* object); + /** * Destroys an existing Object */ @@ -159,11 +165,9 @@ public: SoundManager sound; /** - * @brief objects All active GameObjects in the world. - * @todo add some mechanism to allow objects to be "locked" preventing deletion. - * @todo add deletion queue to allow objects to self delete. + * The active GameObjects within the world, mapped to their allocated ID */ - std::set objects; + std::map objects; std::set characters; diff --git a/rwengine/include/objects/GameObject.hpp b/rwengine/include/objects/GameObject.hpp index 46c418b0..e19f115d 100644 --- a/rwengine/include/objects/GameObject.hpp +++ b/rwengine/include/objects/GameObject.hpp @@ -3,6 +3,7 @@ #define _GAMEOBJECT_HPP_ #include +#include #include #include #include @@ -27,7 +28,7 @@ class GameObject { glm::vec3 _lastPosition; glm::quat _lastRotation; - + GameObjectID objectID; public: glm::vec3 position; glm::quat rotation; @@ -58,7 +59,7 @@ public: bool visible; GameObject(GameWorld* engine, const glm::vec3& pos, const glm::quat& rot, ModelRef model) - : _lastPosition(pos), _lastRotation(rot), position(pos), rotation(rot), + : _lastPosition(pos), _lastRotation(rot), objectID(-1), position(pos), rotation(rot), model(model), engine(engine), animator(nullptr), skeleton(nullptr), mHealth(0.f), inWater(false), _lastHeight(std::numeric_limits::max()), visible(true), lifetime(GameObject::UnknownLifetime) @@ -66,6 +67,12 @@ public: virtual ~GameObject(); + GameObjectID getGameObjectID() const { return objectID; } + /** + * Do not call this, use GameWorld::insertObject + */ + void setGameObjectID(GameObjectID id) { objectID = id; } + /** * @brief Enumeration of possible object types. */ diff --git a/rwengine/include/objects/ObjectTypes.hpp b/rwengine/include/objects/ObjectTypes.hpp new file mode 100644 index 00000000..56a3d057 --- /dev/null +++ b/rwengine/include/objects/ObjectTypes.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include + +/** + * all wordly GameObjects are associated with a 32-bit identifier + */ +typedef uint32_t GameObjectID; \ No newline at end of file diff --git a/rwengine/src/ai/PlayerController.cpp b/rwengine/src/ai/PlayerController.cpp index 5ccf1f86..a7adc11d 100644 --- a/rwengine/src/ai/PlayerController.cpp +++ b/rwengine/src/ai/PlayerController.cpp @@ -47,7 +47,8 @@ void PlayerController::enterNearestVehicle() auto world = character->engine; VehicleObject* nearest = nullptr; float d = 10.f; - for( GameObject* object : world->objects ) { + for( auto& p : world->objects ) { + auto object = p.second; if( object->type() == GameObject::Vehicle ) { float vd = glm::length( character->getPosition() - object->getPosition()); if( vd < d ) { diff --git a/rwengine/src/engine/GameWorld.cpp b/rwengine/src/engine/GameWorld.cpp index 7f8841da..f25cbefc 100644 --- a/rwengine/src/engine/GameWorld.cpp +++ b/rwengine/src/engine/GameWorld.cpp @@ -95,8 +95,8 @@ GameWorld::GameWorld(Logger* log, WorkContext* work, GameData* dat) GameWorld::~GameWorld() { - for(auto o : objects) { - delete o; + for(auto& p : objects) { + delete p.second; } delete dynamicsWorld; @@ -126,7 +126,8 @@ bool GameWorld::placeItems(const std::string& name) } // Attempt to Associate LODs. - for(GameObject* object : objects) { + for(auto& p: objects) { + auto object = p.second; if( object->type() == GameObject::Instance ) { InstanceObject* instance = static_cast(object); if( !instance->object->LOD ) { @@ -192,7 +193,7 @@ InstanceObject *GameWorld::createInstance(const uint16_t id, const glm::vec3& po oi, nullptr, dydata ); - objects.insert(instance); + insertObject(instance); if( shouldBeOnGrid(instance) ) { @@ -219,16 +220,16 @@ void GameWorld::createTraffic(const glm::vec3& near) void GameWorld::cleanupTraffic(const glm::vec3& focus) { - for ( GameObject* object : objects ) + for ( auto& p : objects ) { - if ( object->getLifetime() != GameObject::TrafficLifetime ) + if ( p.second->getLifetime() != GameObject::TrafficLifetime ) { continue; } - if ( glm::distance( focus, object->getPosition() ) >= 100.f ) + if ( glm::distance( focus, p.second->getPosition() ) >= 100.f ) { - destroyObjectQueued( object ); + destroyObjectQueued( p.second ); } } destroyQueuedObjects(); @@ -301,7 +302,7 @@ CutsceneObject *GameWorld::createCutsceneObject(const uint16_t id, const glm::ve pos, m); - objects.insert(instance); + insertObject( instance ); return instance; @@ -365,7 +366,7 @@ VehicleObject *GameWorld::createVehicle(const uint16_t id, const glm::vec3& pos, auto vehicle = new VehicleObject{ this, pos, rot, m, vti, info->second, prim, sec }; - objects.insert(vehicle); + insertObject( vehicle ); return vehicle; } @@ -404,7 +405,7 @@ CharacterObject* GameWorld::createPedestrian(const uint16_t id, const glm::vec3 if(m && m->resource) { auto ped = new CharacterObject( this, pos, rot, m, pt ); - objects.insert(ped); + insertObject(ped); characters.insert(ped); new DefaultAIController(ped); return ped; @@ -413,6 +414,19 @@ CharacterObject* GameWorld::createPedestrian(const uint16_t id, const glm::vec3 return nullptr; } +void GameWorld::insertObject(GameObject* object) +{ + // Find the lowest free GameObjectID. + GameObjectID availID = 1; + for( auto& p : objects ) + { + if( p.first == availID ) availID++; + } + + object->setGameObjectID( availID ); + objects[availID] = object; +} + void GameWorld::destroyObject(GameObject* object) { auto coord = worldToGrid(glm::vec2(object->getPosition())); @@ -423,7 +437,7 @@ void GameWorld::destroyObject(GameObject* object) auto index = (coord.x * WORLD_GRID_WIDTH) + coord.y; worldGrid[index].instances.erase(object); - auto iterator = objects.find(object); + auto iterator = objects.find(object->getGameObjectID()); if( iterator != objects.end() ) { delete object; objects.erase(iterator); @@ -653,7 +667,8 @@ void GameWorld::PhysicsTickCallback(btDynamicsWorld *physWorld, btScalar timeSte { GameWorld* world = static_cast(physWorld->getWorldUserInfo()); - for( GameObject* object : world->objects ) { + for( auto& p : world->objects ) { + GameObject* object = p.second; if( object->type() == GameObject::Vehicle ) { static_cast(object)->tickPhysics(timeStep); } @@ -708,7 +723,8 @@ void GameWorld::startCutscene() void GameWorld::clearCutscene() { - for(auto o : objects) { + for(auto& p : objects) { + auto o = p.second; if( o->type() == GameObject::Cutscene ) { destroyObjectQueued(o); } diff --git a/rwengine/src/items/WeaponItem.cpp b/rwengine/src/items/WeaponItem.cpp index f5ccadaf..ed4e46c1 100644 --- a/rwengine/src/items/WeaponItem.cpp +++ b/rwengine/src/items/WeaponItem.cpp @@ -121,7 +121,7 @@ void WeaponItem::fireProjectile() _wepData }); - _character->engine->objects.insert( projectile ); + _character->engine->insertObject( projectile ); } void WeaponItem::primary(bool active) diff --git a/rwengine/src/objects/ProjectileObject.cpp b/rwengine/src/objects/ProjectileObject.cpp index 5e751607..85a79fd6 100644 --- a/rwengine/src/objects/ProjectileObject.cpp +++ b/rwengine/src/objects/ProjectileObject.cpp @@ -49,7 +49,8 @@ void ProjectileObject::explode() const float damage = _info.weapon->damage; /// @todo accelerate this with bullet instead of doing this stupid loop. - for(auto& o : engine->objects) { + for(auto& p : engine->objects) { + auto o = p.second; if( o == this ) continue; switch( o->type() ) { case GameObject::Instance: diff --git a/rwengine/src/render/GameRenderer.cpp b/rwengine/src/render/GameRenderer.cpp index 65da475b..40802e77 100644 --- a/rwengine/src/render/GameRenderer.cpp +++ b/rwengine/src/render/GameRenderer.cpp @@ -297,7 +297,8 @@ void GameRenderer::renderWorld(GameWorld* world, const ViewCamera &camera, float renderer->pushDebugGroup("Objects"); renderer->pushDebugGroup("Dynamic"); - for( GameObject* object : world->objects ) { + for( auto& p : world->objects ) { + auto object = p.second; if(! object->visible ) { continue; diff --git a/rwengine/src/script/modules/ObjectModule.cpp b/rwengine/src/script/modules/ObjectModule.cpp index b4b7fa58..9fdc9e64 100644 --- a/rwengine/src/script/modules/ObjectModule.cpp +++ b/rwengine/src/script/modules/ObjectModule.cpp @@ -99,9 +99,9 @@ void game_create_character(const ScriptArguments& args) it != args.getWorld()->objects.end(); ++it) { - if( (*it)->type() == GameObject::Character && glm::distance(position, (*it)->getPosition()) < replaceThreshold ) + if( it->second->type() == GameObject::Character && glm::distance(position, it->second->getPosition()) < replaceThreshold ) { - args.getWorld()->destroyObjectQueued(*it); + args.getWorld()->destroyObjectQueued(it->second); } } @@ -139,9 +139,9 @@ void game_create_vehicle(const ScriptArguments& args) it != args.getWorld()->objects.end(); ++it) { - if( (*it)->type() == GameObject::Vehicle && glm::distance(position, (*it)->getPosition()) < replaceThreshold ) + if( it->second->type() == GameObject::Vehicle && glm::distance(position, it->second->getPosition()) < replaceThreshold ) { - args.getWorld()->destroyObjectQueued(*it); + args.getWorld()->destroyObjectQueued(it->second); } } @@ -538,8 +538,9 @@ bool game_objects_in_volume(const ScriptArguments& args) bool objects = args[9].integer; bool particles = args[10].integer; - for(GameObject* object : args.getWorld()->objects) + for(auto& pair : args.getWorld()->objects) { + GameObject* object = pair.second; switch( object->type() ) { case GameObject::Instance: @@ -682,7 +683,7 @@ void game_create_pickup(const ScriptArguments& args) auto pickup = new GenericPickup(args.getWorld(), pos, id, type); - args.getWorld()->objects.insert(pickup); + args.getWorld()->insertObject( pickup ); *args[5].handle = pickup; } @@ -842,7 +843,8 @@ void game_set_close_object_visible(const ScriptArguments& args) std::transform(model.begin(), model.end(), model.begin(), ::tolower); - for(auto o : args.getWorld()->objects) { + for(auto& p : args.getWorld()->objects) { + auto o = p.second; if( o->type() == GameObject::Instance ) { if( !o->model ) continue; if( o->model->name != model ) continue; @@ -899,7 +901,8 @@ void game_change_nearest_model(const ScriptArguments& args) auto nobj = args.getWorld()->data->findObjectType(newobjectid); /// @todo Objects need to adopt the new object ID, not just the model. - for(auto o : args.getWorld()->objects) { + for(auto p : args.getWorld()->objects) { + auto o = p.second; if( o->type() == GameObject::Instance ) { if( !o->model ) continue; if( o->model->name != oldmodel ) continue; diff --git a/rwengine/tests/test_GameWorld.cpp b/rwengine/tests/test_GameWorld.cpp new file mode 100644 index 00000000..80cf979e --- /dev/null +++ b/rwengine/tests/test_GameWorld.cpp @@ -0,0 +1,19 @@ +#include +#include +#include +#include + +BOOST_AUTO_TEST_SUITE(GameWorldTests) + +BOOST_AUTO_TEST_CASE(test_gameobject_id) +{ + GameData gd(&Global::get().log, &Global::get().work, Global::getGamePath()); + GameWorld gw(&Global::get().log, &Global::get().work, &gd); + + auto object1 = gw.createInstance(1337, glm::vec3(100.f, 0.f, 0.f)); + auto object2 = gw.createInstance(1337, glm::vec3(100.f, 0.f, 0.f)); + + BOOST_CHECK_NE( object1->getObjectID(), object2->getObjectID() ); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/rwgame/RWGame.cpp b/rwgame/RWGame.cpp index b03ad23f..54835c74 100644 --- a/rwgame/RWGame.cpp +++ b/rwgame/RWGame.cpp @@ -314,7 +314,8 @@ void RWGame::tick(float dt) } } - for( GameObject* object : world->objects ) { + for( auto& p : world->objects ) { + GameObject* object = p.second; object->_updateLastTransform(); object->tick(dt); } @@ -502,8 +503,9 @@ void RWGame::renderDebugStats(float time, Renderer::ProfileInfo& worldRenderTime // Count the number of interesting objects. int peds = 0, cars = 0; - for( GameObject* object : world->objects ) + for( auto& p : world->objects ) { + GameObject* object = p.second; switch ( object->type() ) { case GameObject::Character: peds++; break; diff --git a/rwgame/ingamestate.cpp b/rwgame/ingamestate.cpp index 3d8a522c..ba3c276a 100644 --- a/rwgame/ingamestate.cpp +++ b/rwgame/ingamestate.cpp @@ -36,7 +36,7 @@ void IngameState::startTest() glm::vec3 itemspawn( 276.5f, -609.f, 36.5f); for( auto& w : getWorld()->data->weaponData ) { if( w.first == "unarmed" ) continue; - getWorld()->objects.insert(new ItemPickup(getWorld(), itemspawn, + getWorld()->insertObject(new ItemPickup(getWorld(), itemspawn, w.second)); itemspawn.x += 2.5f; } diff --git a/tests/test_lifetime.cpp b/tests/test_lifetime.cpp index df4ec522..bba09230 100644 --- a/tests/test_lifetime.cpp +++ b/tests/test_lifetime.cpp @@ -8,18 +8,19 @@ BOOST_AUTO_TEST_SUITE(LifetimeTests) BOOST_AUTO_TEST_CASE(test_cleanup) { GameObject* f = Global::get().e->createInstance(1337, glm::vec3(0.f, 0.f, 1000.f)); - + auto id = f->getGameObjectID(); + f->setLifetime(GameObject::TrafficLifetime); { - auto search = Global::get().e->objects.find( f ); + auto search = Global::get().e->objects.find( id ); BOOST_CHECK( search != Global::get().e->objects.end() ); } Global::get().e->cleanupTraffic(glm::vec3(0.f, 0.f, 0.f)); { - auto search = Global::get().e->objects.find( f ); + auto search = Global::get().e->objects.find( id ); BOOST_CHECK( search == Global::get().e->objects.end() ); } } diff --git a/tests/test_pickup.cpp b/tests/test_pickup.cpp index 203672dd..1ab69801 100644 --- a/tests/test_pickup.cpp +++ b/tests/test_pickup.cpp @@ -31,7 +31,7 @@ BOOST_AUTO_TEST_CASE(test_pickup_interaction) TestPickup* p = new TestPickup(Global::get().e, { 30.f, 0.f, 0.f } ); - Global::get().e->objects.insert(p); + Global::get().e->insertObject(p); BOOST_CHECK( ! p->picked_up ); @@ -70,7 +70,7 @@ BOOST_AUTO_TEST_CASE(test_item_pickup) ItemPickup* p = new ItemPickup(Global::get().e, { 30.f, 0.f, 0.f }, item ); - Global::get().e->objects.insert(p); + Global::get().e->insertObject(p); // Check the characters inventory is empty. BOOST_CHECK( character->getInventory().empty() ); diff --git a/tests/test_weapon.cpp b/tests/test_weapon.cpp index f24981aa..7fd5f1f7 100644 --- a/tests/test_weapon.cpp +++ b/tests/test_weapon.cpp @@ -42,7 +42,7 @@ BOOST_AUTO_TEST_CASE(TestProjectile) wepdata }); - Global::get().e->objects.insert( projectile ); + Global::get().e->insertObject( projectile ); BOOST_CHECK( character->mHealth == 100.f ); @@ -75,7 +75,7 @@ BOOST_AUTO_TEST_CASE(TestProjectile) wepdata }); - Global::get().e->objects.insert( projectile ); + Global::get().e->insertObject( projectile ); BOOST_CHECK( character->mHealth == 100.f ); @@ -107,7 +107,7 @@ BOOST_AUTO_TEST_CASE(TestProjectile) wepdata }); - Global::get().e->objects.insert( projectile ); + Global::get().e->insertObject( projectile ); BOOST_CHECK( character->mHealth == 100.f );