1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-11-23 02:42:39 +01:00

Merge pull request #266 from danhedron/map-zones-1

Add zone queries and load save zones
This commit is contained in:
Daniel Evans 2017-01-30 21:08:48 +00:00 committed by GitHub
commit 1308342f8f
12 changed files with 251 additions and 59 deletions

View File

@ -1,15 +1,16 @@
#pragma once #ifndef RWENGINE_DATA_ZONEDATA_HPP
#ifndef _ZONEDATA_HPP_ #define RWENGINE_DATA_ZONEDATA_HPP
#define _ZONEDATA_HPP_
#include <algorithm>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <memory>
#include <string> #include <string>
#include <vector>
#define ZONE_GANG_COUNT 13 #define ZONE_GANG_COUNT 13
/** /**
* \class Zone * Zone Data loaded from IPL/zon files
* A Zone entry
*/ */
struct ZoneData { struct ZoneData {
/** /**
@ -37,30 +38,87 @@ struct ZoneData {
/** /**
* Text of the zone? * Text of the zone?
*/ */
std::string Text; std::string text = {};
/** /**
* Gang spawn density for daytime (8:00-19:00) * 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) * 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) * 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) * 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 pedGroupDay = 0;
unsigned int pedGroupNight; 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;
}
}; };
using ZoneDataList = std::vector<ZoneData>;
#endif #endif

View File

@ -56,6 +56,10 @@ void GameData::load() {
loadIFP("ped.ifp"); 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/default.dat");
loadLevelFile("data/gta3.dat"); loadLevelFile("data/gta3.dat");
} }
@ -164,20 +168,24 @@ void GameData::loadIPL(const std::string& path) {
bool GameData::loadZone(const std::string& path) { bool GameData::loadZone(const std::string& path) {
LoaderIPL ipll; LoaderIPL ipll;
if (ipll.load(path)) { // Load the zones
if (ipll.zones.size() > 0) { if (!ipll.load(path)) {
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 {
logger->error("Data", "Failed to load zones from " + 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 { enum ColSection {

View File

@ -43,6 +43,7 @@ private:
Logger* logger; Logger* logger;
LoaderDFF dffLoader; LoaderDFF dffLoader;
public: public:
/** /**
* ctor * ctor
@ -193,10 +194,22 @@ public:
*/ */
std::map<std::string, LoaderIMG> archives; std::map<std::string, LoaderIMG> archives;
/** ZoneDataList gamezones;
* Map Zones
*/ ZoneDataList mapzones;
std::map<std::string, ZoneData> zones;
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; std::unordered_map<ModelID, std::unique_ptr<BaseModelInfo>> modelinfo;

View File

@ -371,9 +371,11 @@ struct Block11Zone {
}; };
struct Block11ZoneInfo { struct Block11ZoneInfo {
BlockWord density; BlockWord density;
// This will likely be the gang BlockWord unknown1[16];
// car and ped densities +more BlockWord peddensity;
uint8_t unknown1[56]; BlockWord copdensity;
BlockWord gangpeddensity[9];
BlockWord pedgroup;
}; };
struct Block11AudioZone { struct Block11AudioZone {
BlockWord zoneId; BlockWord zoneId;
@ -890,6 +892,10 @@ bool SaveGame::loadGame(GameState& state, const std::string& file) {
Block11ZoneInfo& info = zoneData.dayNightInfo[z]; Block11ZoneInfo& info = zoneData.dayNightInfo[z];
READ_VALUE(info.density) READ_VALUE(info.density)
READ_VALUE(info.unknown1) READ_VALUE(info.unknown1)
READ_VALUE(info.peddensity)
READ_VALUE(info.copdensity)
READ_VALUE(info.gangpeddensity)
READ_VALUE(info.pedgroup);
} }
READ_VALUE(zoneData.numNavZones); READ_VALUE(zoneData.numNavZones);
READ_VALUE(zoneData.numZoneInfos); READ_VALUE(zoneData.numZoneInfos);
@ -919,6 +925,31 @@ bool SaveGame::loadGame(GameState& state, const std::string& file) {
for (int z = 0; z < zoneData.numNavZones; ++z) { for (int z = 0; z < zoneData.numNavZones; ++z) {
Block11Zone& zone = zoneData.navZones[z]; Block11Zone& zone = zoneData.navZones[z];
std::cout << " " << zone.name << std::endl; std::cout << " " << zone.name << std::endl;
auto& dayinfo = zoneData.dayNightInfo[zone.dayZoneInfo];
std::cout << " DAY " << dayinfo.density << " " << dayinfo.peddensity
<< " " << dayinfo.copdensity << " "
<< " [";
for (BlockDword gang : dayinfo.gangpeddensity) {
std::cout << " " << gang;
}
std::cout << "] " << dayinfo.pedgroup << std::endl;
for (BlockDword dw : dayinfo.unknown1) {
std::cout << " " << dw;
}
std::cout << std::endl;
auto& nightinfo = zoneData.dayNightInfo[zone.nightZoneInfo];
std::cout << " NIGHT " << nightinfo.density << " "
<< nightinfo.peddensity << " " << nightinfo.copdensity << " "
<< " [";
for (BlockDword gang : nightinfo.gangpeddensity) {
std::cout << " " << gang;
}
std::cout << "] " << nightinfo.pedgroup << std::endl;
for (BlockDword dw : nightinfo.unknown1) {
std::cout << " " << dw;
}
std::cout << std::endl;
} }
for (int z = 0; z < zoneData.numMapZones; ++z) { for (int z = 0; z < zoneData.numMapZones; ++z) {
Block11Zone& zone = zoneData.mapZones[z]; Block11Zone& zone = zoneData.mapZones[z];
@ -926,6 +957,33 @@ bool SaveGame::loadGame(GameState& state, const std::string& file) {
} }
#endif #endif
// Clear existing zone data
auto& gamezones = state.world->data->gamezones;
gamezones.clear();
for (int z = 0; z < zoneData.numNavZones; ++z) {
Block11Zone& zone = zoneData.navZones[z];
Block11ZoneInfo& day = zoneData.dayNightInfo[zone.dayZoneInfo];
Block11ZoneInfo& night = zoneData.dayNightInfo[zone.nightZoneInfo];
ZoneData zd;
zd.name = zone.name;
zd.type = zone.type;
zd.min = zone.coordA;
zd.max = zone.coordB;
zd.island = zone.level;
// @toodo restore gang density
zd.pedGroupDay = day.pedgroup;
zd.pedGroupNight = night.pedgroup;
gamezones.push_back(zd);
}
// Re-build zone hierarchy
for (ZoneData& zone : gamezones) {
if (&zone == &gamezones[0]) {
continue;
}
gamezones[0].insertZone(zone);
}
// Block 12 // Block 12
BlockSize gangBlockSize; BlockSize gangBlockSize;
BLOCK_HEADER(gangBlockSize) BLOCK_HEADER(gangBlockSize)

View File

@ -20,7 +20,7 @@ public:
std::vector<std::shared_ptr<InstanceData>> m_instances; std::vector<std::shared_ptr<InstanceData>> m_instances;
/// List of Zones /// List of Zones
std::vector<ZoneData> zones; ZoneDataList zones;
}; };
#endif // LoaderIPL_h__ #endif // LoaderIPL_h__

View File

@ -108,11 +108,11 @@ inline bool objectInRadiusNear(const ScriptArguments& args, GameObject* object,
template <class Tobj> template <class Tobj>
inline bool objectInZone(const ScriptArguments& args, Tobj object, inline bool objectInZone(const ScriptArguments& args, Tobj object,
const ScriptString zone) { const ScriptString name) {
auto zfind = args.getWorld()->data->zones.find(zone); auto zone = args.getWorld()->data->findZone(name);
if (zfind != args.getWorld()->data->zones.end()) { if (zone) {
auto& min = zfind->second.min; auto& min = zone->min;
auto& max = zfind->second.max; auto& max = zone->max;
return objectInBounds(object, min, max); return objectInBounds(object, min, max);
} }
return false; return false;

View File

@ -3694,12 +3694,11 @@ void opcode_0152(const ScriptArguments& args, const ScriptString arg1, const Scr
RW_UNUSED(arg15); RW_UNUSED(arg15);
RW_UNUSED(arg16); RW_UNUSED(arg16);
RW_UNUSED(arg17); RW_UNUSED(arg17);
auto& zones = args.getWorld()->data->zones; auto zone = args.getWorld()->data->findZone(arg1);
auto it = zones.find(arg1); if (zone) {
if (it != zones.end()) { auto density = (zone->gangCarDensityNight);
auto density = (it->second.gangCarDensityNight); if (arg2) {
if (arg1) { density = zone->gangCarDensityDay;
density = it->second.gangCarDensityDay;
} }
auto count = args.getParameters().size(); auto count = args.getParameters().size();
for (auto g = 0u; g < count - 2; ++g) { 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(arg9);
RW_UNUSED(arg10); RW_UNUSED(arg10);
RW_UNUSED(arg11); RW_UNUSED(arg11);
auto& zones = args.getWorld()->data->zones; auto zone = args.getWorld()->data->findZone(areaName);
auto it = zones.find(areaName); if (zone) {
if (it != zones.end()) { auto density = (zone->gangCarDensityNight);
auto density = (it->second.gangCarDensityNight);
if (arg2) { if (arg2) {
density = it->second.gangCarDensityDay; density = zone->gangCarDensityDay;
} }
auto count = args.getParameters().size(); auto count = args.getParameters().size();
for (auto g = 0u; g < count - 2; ++g) { 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_UNIMPLEMENTED_OPCODE(0x02dd);
RW_UNUSED(areaName); RW_UNUSED(areaName);
RW_UNUSED(character); RW_UNUSED(character);
const auto& zones = args.getWorld()->data->zones;
std::string zname(args[0].string); std::string zname(args[0].string);
// Only try to find a character if this is a known zone // Only try to find a character if this is a known zone
auto zfind = zones.find(zname); auto zone = args.getWorld()->data->findZone(areaName);
if(zfind != zones.end()) { if(zone) {
// Create a list of candidate characters by iterating and checking if the char is in this 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; 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 // Check if character is in this zone
auto cp = character->getPosition(); auto cp = character->getPosition();
auto& min = zfind->second.min; auto& min = zone->min;
auto& max = zfind->second.max; auto& max = zone->max;
if (cp.x > min.x && cp.y > min.y && cp.z > min.z && 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) { cp.x < max.x && cp.y < max.y && cp.z < max.z) {
candidates.push_back(p); candidates.push_back(p);
@ -9246,17 +9243,17 @@ void opcode_0324(const ScriptArguments& args, const ScriptString arg1, const Scr
RW_UNUSED(arg1); RW_UNUSED(arg1);
RW_UNUSED(arg2); RW_UNUSED(arg2);
RW_UNUSED(arg3); RW_UNUSED(arg3);
auto it = args.getWorld()->data->zones.find(args[0].string); auto zone = args.getWorld()->data->findZone(arg1);
if( it != args.getWorld()->data->zones.end() ) if (zone)
{ {
auto day = args[1].integer == 1; auto day = args[1].integer == 1;
if( day ) if( day )
{ {
it->second.pedGroupDay = args[2].integer; zone->pedGroupDay = args[2].integer;
} }
else else
{ {
it->second.pedGroupNight = args[2].integer; zone->pedGroupNight = args[2].integer;
} }
} }
} }

View File

@ -232,7 +232,9 @@ void DebugState::tick(float dt) {
void DebugState::draw(GameRenderer* r) { void DebugState::draw(GameRenderer* r) {
// Draw useful information like camera position. // Draw useful information like camera position.
std::stringstream ss; 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; TextRenderer::TextInfo ti;
ti.text = GameStringUtil::fromString(ss.str()); ti.text = GameStringUtil::fromString(ss.str());

View File

@ -28,6 +28,7 @@ set(TEST_SOURCES
"test_Input.cpp" "test_Input.cpp"
"test_lifetime.cpp" "test_lifetime.cpp"
"test_loaderdff.cpp" "test_loaderdff.cpp"
"test_LoaderIPL.cpp"
"test_Logger.cpp" "test_Logger.cpp"
"test_menu.cpp" "test_menu.cpp"
"test_object.cpp" "test_object.cpp"
@ -45,6 +46,7 @@ set(TEST_SOURCES
"test_VisualFX.cpp" "test_VisualFX.cpp"
"test_weapon.cpp" "test_weapon.cpp"
"test_world.cpp" "test_world.cpp"
"test_ZoneData.cpp"
# Hack in rwgame sources until there's a per-target test suite # Hack in rwgame sources until there's a per-target test suite
"${CMAKE_SOURCE_DIR}/rwgame/GameConfig.cpp" "${CMAKE_SOURCE_DIR}/rwgame/GameConfig.cpp"

21
tests/test_LoaderIPL.cpp Normal file
View 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
View 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()