mirror of
https://github.com/rwengine/openrw.git
synced 2024-09-03 09:09:47 +02:00
Implement Hierarchy of game ZoneData
This allows querying of the most specific ZoneData for a given point
This commit is contained in:
parent
f12186dede
commit
bb6698e373
@ -1,15 +1,16 @@
|
||||
#pragma once
|
||||
#ifndef _ZONEDATA_HPP_
|
||||
#define _ZONEDATA_HPP_
|
||||
#ifndef RWENGINE_DATA_ZONEDATA_HPP
|
||||
#define RWENGINE_DATA_ZONEDATA_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <glm/glm.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define ZONE_GANG_COUNT 13
|
||||
|
||||
/**
|
||||
* \class Zone
|
||||
* A Zone entry
|
||||
* Zone Data loaded from IPL/zon files
|
||||
*/
|
||||
struct ZoneData {
|
||||
/**
|
||||
@ -37,30 +38,87 @@ struct ZoneData {
|
||||
/**
|
||||
* Text of the zone?
|
||||
*/
|
||||
std::string Text;
|
||||
std::string text = {};
|
||||
|
||||
/**
|
||||
* Gang spawn density for daytime (8:00-19:00)
|
||||
*/
|
||||
unsigned int gangDensityDay[ZONE_GANG_COUNT];
|
||||
unsigned int gangDensityDay[ZONE_GANG_COUNT] = {};
|
||||
|
||||
/**
|
||||
* Gang spawn density for nighttime (19:00-8:00)
|
||||
*/
|
||||
unsigned int gangDensityNight[ZONE_GANG_COUNT];
|
||||
unsigned int gangDensityNight[ZONE_GANG_COUNT] = {};
|
||||
|
||||
/**
|
||||
* Gang car spawn density for daytime (8:00-19:00)
|
||||
*/
|
||||
unsigned int gangCarDensityDay[ZONE_GANG_COUNT];
|
||||
unsigned int gangCarDensityDay[ZONE_GANG_COUNT] = {};
|
||||
|
||||
/**
|
||||
* Gang car spawn density for nighttime (19:00-8:00)
|
||||
*/
|
||||
unsigned int gangCarDensityNight[ZONE_GANG_COUNT];
|
||||
unsigned int gangCarDensityNight[ZONE_GANG_COUNT] = {};
|
||||
|
||||
unsigned int pedGroupDay;
|
||||
unsigned int pedGroupNight;
|
||||
unsigned int pedGroupDay = 0;
|
||||
unsigned int pedGroupNight = 0;
|
||||
|
||||
/**
|
||||
* Pointer to parent zone in zone array
|
||||
*/
|
||||
ZoneData* parent_ = nullptr;
|
||||
|
||||
/**
|
||||
* Totally contained zones
|
||||
*/
|
||||
std::vector<ZoneData*> children_ = {};
|
||||
|
||||
static bool isZoneContained(const ZoneData& inner, const ZoneData& outer) {
|
||||
return glm::all(glm::greaterThanEqual(inner.min, outer.min)) &&
|
||||
glm::all(glm::lessThanEqual(inner.max, outer.max));
|
||||
}
|
||||
|
||||
bool containsPoint(const glm::vec3& point) const {
|
||||
return glm::all(glm::greaterThanEqual(point, min)) &&
|
||||
glm::all(glm::lessThanEqual(point, max));
|
||||
}
|
||||
|
||||
ZoneData* findLeafAtPoint(const glm::vec3& point) {
|
||||
for (ZoneData* child : children_) {
|
||||
auto descendent = child->findLeafAtPoint(point);
|
||||
if (descendent) {
|
||||
return descendent;
|
||||
}
|
||||
}
|
||||
return containsPoint(point) ? this : nullptr;
|
||||
}
|
||||
|
||||
bool insertZone(ZoneData& inner) {
|
||||
if (!isZoneContained(inner, *this)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (ZoneData* child : children_) {
|
||||
if (child->insertZone(inner)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// inner is a child of outer
|
||||
|
||||
// Move any zones that are really within inner to inner
|
||||
auto it = std::stable_partition(
|
||||
children_.begin(), children_.end(),
|
||||
[&](ZoneData* a) { return !inner.insertZone(*a); });
|
||||
children_.erase(it, children_.end());
|
||||
|
||||
children_.push_back(&inner);
|
||||
inner.parent_ = this;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
using ZoneDataList = std::vector<ZoneData>;
|
||||
|
||||
#endif
|
||||
|
@ -56,6 +56,10 @@ void GameData::load() {
|
||||
|
||||
loadIFP("ped.ifp");
|
||||
|
||||
// Clear existing zones
|
||||
gamezones = ZoneDataList{
|
||||
{"CITYZON", 0, {-4000.f, -4000.f, -500.f}, {4000.f, 4000.f, 500.f}, 0}};
|
||||
|
||||
loadLevelFile("data/default.dat");
|
||||
loadLevelFile("data/gta3.dat");
|
||||
}
|
||||
@ -164,20 +168,24 @@ void GameData::loadIPL(const std::string& path) {
|
||||
bool GameData::loadZone(const std::string& path) {
|
||||
LoaderIPL ipll;
|
||||
|
||||
if (ipll.load(path)) {
|
||||
if (ipll.zones.size() > 0) {
|
||||
for (auto& z : ipll.zones) {
|
||||
zones.insert({z.name, z});
|
||||
}
|
||||
logger->info("Data", "Loaded " + std::to_string(ipll.zones.size()) +
|
||||
" zones from " + path);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Load the zones
|
||||
if (!ipll.load(path)) {
|
||||
logger->error("Data", "Failed to load zones from " + path);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
gamezones.insert(gamezones.end(), ipll.zones.begin(), ipll.zones.end());
|
||||
|
||||
// Build zone hierarchy
|
||||
for (ZoneData& zone : gamezones) {
|
||||
zone.children_.clear();
|
||||
if (&zone == &gamezones.front()) {
|
||||
continue;
|
||||
}
|
||||
gamezones[0].insertZone(zone);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum ColSection {
|
||||
|
@ -43,6 +43,7 @@ private:
|
||||
|
||||
Logger* logger;
|
||||
LoaderDFF dffLoader;
|
||||
|
||||
public:
|
||||
/**
|
||||
* ctor
|
||||
@ -193,10 +194,22 @@ public:
|
||||
*/
|
||||
std::map<std::string, LoaderIMG> archives;
|
||||
|
||||
/**
|
||||
* Map Zones
|
||||
*/
|
||||
std::map<std::string, ZoneData> zones;
|
||||
ZoneDataList gamezones;
|
||||
|
||||
ZoneDataList mapzones;
|
||||
|
||||
ZoneData* findZone(const std::string& name) {
|
||||
auto it =
|
||||
std::find_if(gamezones.begin(), gamezones.end(),
|
||||
[&](const ZoneData& a) { return a.name == name; });
|
||||
return it != gamezones.end() ? &(*it) : nullptr;
|
||||
}
|
||||
|
||||
ZoneData* findZoneAt(const glm::vec3& pos) {
|
||||
RW_CHECK(!gamezones.empty(), "No game zones loaded");
|
||||
ZoneData* zone = gamezones[0].findLeafAtPoint(pos);
|
||||
return zone;
|
||||
}
|
||||
|
||||
std::unordered_map<ModelID, std::unique_ptr<BaseModelInfo>> modelinfo;
|
||||
|
||||
|
@ -124,4 +124,4 @@ bool LoaderIPL::load(const std::string& filename) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ public:
|
||||
std::vector<std::shared_ptr<InstanceData>> m_instances;
|
||||
|
||||
/// List of Zones
|
||||
std::vector<ZoneData> zones;
|
||||
ZoneDataList zones;
|
||||
};
|
||||
|
||||
#endif // LoaderIPL_h__
|
||||
|
@ -108,11 +108,11 @@ inline bool objectInRadiusNear(const ScriptArguments& args, GameObject* object,
|
||||
|
||||
template <class Tobj>
|
||||
inline bool objectInZone(const ScriptArguments& args, Tobj object,
|
||||
const ScriptString zone) {
|
||||
auto zfind = args.getWorld()->data->zones.find(zone);
|
||||
if (zfind != args.getWorld()->data->zones.end()) {
|
||||
auto& min = zfind->second.min;
|
||||
auto& max = zfind->second.max;
|
||||
const ScriptString name) {
|
||||
auto zone = args.getWorld()->data->findZone(name);
|
||||
if (zone) {
|
||||
auto& min = zone->min;
|
||||
auto& max = zone->max;
|
||||
return objectInBounds(object, min, max);
|
||||
}
|
||||
return false;
|
||||
|
@ -3694,12 +3694,11 @@ void opcode_0152(const ScriptArguments& args, const ScriptString arg1, const Scr
|
||||
RW_UNUSED(arg15);
|
||||
RW_UNUSED(arg16);
|
||||
RW_UNUSED(arg17);
|
||||
auto& zones = args.getWorld()->data->zones;
|
||||
auto it = zones.find(arg1);
|
||||
if (it != zones.end()) {
|
||||
auto density = (it->second.gangCarDensityNight);
|
||||
if (arg1) {
|
||||
density = it->second.gangCarDensityDay;
|
||||
auto zone = args.getWorld()->data->findZone(arg1);
|
||||
if (zone) {
|
||||
auto density = (zone->gangCarDensityNight);
|
||||
if (arg2) {
|
||||
density = zone->gangCarDensityDay;
|
||||
}
|
||||
auto count = args.getParameters().size();
|
||||
for (auto g = 0u; g < count - 2; ++g) {
|
||||
@ -3824,12 +3823,11 @@ void opcode_015c(const ScriptArguments& args, const ScriptString areaName, const
|
||||
RW_UNUSED(arg9);
|
||||
RW_UNUSED(arg10);
|
||||
RW_UNUSED(arg11);
|
||||
auto& zones = args.getWorld()->data->zones;
|
||||
auto it = zones.find(areaName);
|
||||
if (it != zones.end()) {
|
||||
auto density = (it->second.gangCarDensityNight);
|
||||
auto zone = args.getWorld()->data->findZone(areaName);
|
||||
if (zone) {
|
||||
auto density = (zone->gangCarDensityNight);
|
||||
if (arg2) {
|
||||
density = it->second.gangCarDensityDay;
|
||||
density = zone->gangCarDensityDay;
|
||||
}
|
||||
auto count = args.getParameters().size();
|
||||
for (auto g = 0u; g < count - 2; ++g) {
|
||||
@ -8101,13 +8099,12 @@ void opcode_02dd(const ScriptArguments& args, const ScriptString areaName, Scrip
|
||||
RW_UNIMPLEMENTED_OPCODE(0x02dd);
|
||||
RW_UNUSED(areaName);
|
||||
RW_UNUSED(character);
|
||||
const auto& zones = args.getWorld()->data->zones;
|
||||
|
||||
std::string zname(args[0].string);
|
||||
|
||||
// Only try to find a character if this is a known zone
|
||||
auto zfind = zones.find(zname);
|
||||
if(zfind != zones.end()) {
|
||||
auto zone = args.getWorld()->data->findZone(areaName);
|
||||
if(zone) {
|
||||
|
||||
// Create a list of candidate characters by iterating and checking if the char is in this zone
|
||||
std::vector<std::pair<GameObjectID, GameObject*>> candidates;
|
||||
@ -8122,8 +8119,8 @@ void opcode_02dd(const ScriptArguments& args, const ScriptString areaName, Scrip
|
||||
|
||||
// Check if character is in this zone
|
||||
auto cp = character->getPosition();
|
||||
auto& min = zfind->second.min;
|
||||
auto& max = zfind->second.max;
|
||||
auto& min = zone->min;
|
||||
auto& max = zone->max;
|
||||
if (cp.x > min.x && cp.y > min.y && cp.z > min.z &&
|
||||
cp.x < max.x && cp.y < max.y && cp.z < max.z) {
|
||||
candidates.push_back(p);
|
||||
@ -9246,17 +9243,17 @@ void opcode_0324(const ScriptArguments& args, const ScriptString arg1, const Scr
|
||||
RW_UNUSED(arg1);
|
||||
RW_UNUSED(arg2);
|
||||
RW_UNUSED(arg3);
|
||||
auto it = args.getWorld()->data->zones.find(args[0].string);
|
||||
if( it != args.getWorld()->data->zones.end() )
|
||||
auto zone = args.getWorld()->data->findZone(arg1);
|
||||
if (zone)
|
||||
{
|
||||
auto day = args[1].integer == 1;
|
||||
if( day )
|
||||
{
|
||||
it->second.pedGroupDay = args[2].integer;
|
||||
zone->pedGroupDay = args[2].integer;
|
||||
}
|
||||
else
|
||||
{
|
||||
it->second.pedGroupNight = args[2].integer;
|
||||
zone->pedGroupNight = args[2].integer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -232,7 +232,9 @@ void DebugState::tick(float dt) {
|
||||
void DebugState::draw(GameRenderer* r) {
|
||||
// Draw useful information like camera position.
|
||||
std::stringstream ss;
|
||||
ss << "Camera Position: " << glm::to_string(_debugCam.position);
|
||||
ss << "Camera Position: " << glm::to_string(_debugCam.position) << "\n";
|
||||
auto zone = getWorld()->data->findZoneAt(_debugCam.position);
|
||||
ss << (zone ? zone->name : "No Zone") << "\n";
|
||||
|
||||
TextRenderer::TextInfo ti;
|
||||
ti.text = GameStringUtil::fromString(ss.str());
|
||||
|
@ -28,6 +28,7 @@ set(TEST_SOURCES
|
||||
"test_Input.cpp"
|
||||
"test_lifetime.cpp"
|
||||
"test_loaderdff.cpp"
|
||||
"test_LoaderIPL.cpp"
|
||||
"test_Logger.cpp"
|
||||
"test_menu.cpp"
|
||||
"test_object.cpp"
|
||||
@ -45,6 +46,7 @@ set(TEST_SOURCES
|
||||
"test_VisualFX.cpp"
|
||||
"test_weapon.cpp"
|
||||
"test_world.cpp"
|
||||
"test_ZoneData.cpp"
|
||||
|
||||
# Hack in rwgame sources until there's a per-target test suite
|
||||
"${CMAKE_SOURCE_DIR}/rwgame/GameConfig.cpp"
|
||||
|
21
tests/test_LoaderIPL.cpp
Normal file
21
tests/test_LoaderIPL.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <loaders/LoaderIPL.hpp>
|
||||
#include "test_globals.hpp"
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(LoaderIPLTests)
|
||||
|
||||
#if RW_TEST_WITH_DATA
|
||||
BOOST_AUTO_TEST_CASE(test_load_zones) {
|
||||
LoaderIPL loader;
|
||||
const auto& gdpath = Global::get().getGamePath();
|
||||
BOOST_REQUIRE(loader.load(gdpath + "/data/gta3.zon"));
|
||||
|
||||
BOOST_REQUIRE(loader.zones.size() > 2);
|
||||
|
||||
auto& zone1 = loader.zones[0];
|
||||
BOOST_CHECK_EQUAL(zone1.name, "ROADBR1");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
33
tests/test_ZoneData.cpp
Normal file
33
tests/test_ZoneData.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <data/ZoneData.hpp>
|
||||
#include "test_globals.hpp"
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(ZoneDataTests)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_contains_point) {
|
||||
ZoneData zone;
|
||||
zone.min = glm::vec3(10.f, 10.f, -5.f);
|
||||
zone.max = glm::vec3(30.f, 40.f, 5.f);
|
||||
|
||||
BOOST_CHECK(zone.containsPoint({15.f, 15.f, 0.f}));
|
||||
BOOST_CHECK(zone.containsPoint({10.f, 10.f, 0.f}));
|
||||
BOOST_CHECK(zone.containsPoint({30.f, 35.f, 0.f}));
|
||||
BOOST_CHECK(!zone.containsPoint({35.f, 30.f, 0.f}));
|
||||
BOOST_CHECK(!zone.containsPoint({0.f, 0.f, 0.f}));
|
||||
BOOST_CHECK(!zone.containsPoint({-15.f, -15.f, 0.f}));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_hierarchy) {
|
||||
ZoneData zone;
|
||||
zone.min = glm::vec3(-10.f,-10.f,-5.f);
|
||||
zone.max = glm::vec3( 10.f, 10.f, 5.f);
|
||||
ZoneData leaf;
|
||||
leaf.min = glm::vec3(0.f, 0.f,-5.f);
|
||||
leaf.max = glm::vec3(10.f,10.f, 5.f);
|
||||
BOOST_CHECK(zone.insertZone(leaf));
|
||||
|
||||
BOOST_CHECK_EQUAL(zone.findLeafAtPoint({-5.f, 0.f, 0.f}), &zone);
|
||||
BOOST_CHECK_EQUAL(zone.findLeafAtPoint({ 5.f, 5.f, 0.f}), &leaf);
|
||||
|
||||
}
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
Loading…
Reference in New Issue
Block a user