1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-11-26 04:12:41 +01:00

Merge pull request #265 from danhedron/ped-improvements

Ped improvements
This commit is contained in:
Daniel Evans 2017-02-08 20:44:28 +00:00 committed by GitHub
commit 66dd55620d
13 changed files with 382 additions and 27 deletions

View File

@ -39,6 +39,8 @@ set(RWENGINE_SOURCES
src/data/ModelData.cpp
src/data/ModelData.hpp
src/data/PathData.hpp
src/data/PedData.cpp
src/data/PedData.hpp
src/data/Skeleton.cpp
src/data/Skeleton.hpp
src/data/WeaponData.hpp

View File

@ -2,6 +2,7 @@
#include <ai/AIGraphNode.hpp>
#include <ai/CharacterController.hpp>
#include <core/Logger.hpp>
#include <engine/GameData.hpp>
#include <engine/GameState.hpp>
#include <engine/GameWorld.hpp>
#include <objects/CharacterObject.hpp>
@ -127,11 +128,19 @@ std::vector<GameObject*> TrafficDirector::populateNearby(
}
/// Hardcoded cop Pedestrian
std::vector<uint16_t> validPeds = {1};
validPeds.insert(validPeds.end(), {20, 11, 19, 5});
std::vector<uint16_t> peds = {1};
// Determine which zone the viewpoint is in
auto zone = world->data->findZoneAt(camera.position);
bool day = (world->state->basic.gameHour >= 8 &&
world->state->basic.gameHour <= 19);
int groupid = zone ? (day ? zone->pedGroupDay : zone->pedGroupNight) : 0;
const auto& group = world->data->pedgroups.at(groupid);
peds.insert(peds.end(), group.cbegin(), group.cend());
std::random_device rd;
std::default_random_engine re(rd());
std::uniform_int_distribution<> d(0, validPeds.size() - 1);
std::uniform_int_distribution<> d(0, peds.size() - 1);
const glm::vec3 kSpawnOffset{0.f, 0.f, 1.f};
int counter = availablePeds;
// maxSpawn can be -1 for "as many as possible"
@ -151,8 +160,8 @@ std::vector<GameObject*> TrafficDirector::populateNearby(
}
// Spawn a pedestrian from the available pool
auto ped = world->createPedestrian(
validPeds[d(re)], spawn->position + glm::vec3(0.f, 0.f, 1.f));
auto ped = world->createPedestrian(peds[d(re)],
spawn->position + kSpawnOffset);
ped->setLifetime(GameObject::TrafficLifetime);
ped->controller->setGoal(CharacterController::TrafficWander);
created.push_back(ped);

View File

@ -346,7 +346,7 @@ public:
PLAYER1 = 0,
PLAYER2,
PLAYER3,
PLAYER_4,
PLAYER4,
CIVMALE,
CIVFEMALE,
COP,
@ -365,11 +365,11 @@ public:
_UNNAMED,
PROSTITUTE,
SPECIAL,
_NUM_PEDTYPE
};
PedType pedtype_ = PLAYER1;
/// @todo this should be an index
std::string behaviour_;
int statindex_ = 0;
/// @todo this should be an index
std::string animgroup_;
/// The mask of vehicle classes this ped can drive
@ -381,7 +381,7 @@ public:
{"PLAYER1", PLAYER1},
{"PLAYER2", PLAYER2},
{"PLAYER3", PLAYER3},
{"PLAYER_4", PLAYER_4},
{"PLAYER4", PLAYER4},
{"CIVMALE", CIVMALE},
{"CIVFEMALE", CIVFEMALE},
{"COP", COP},
@ -397,7 +397,6 @@ public:
{"EMERGENCY", EMERGENCY},
{"FIREMAN", FIREMAN},
{"CRIMINAL", CRIMINAL},
{"_UNNAMED", _UNNAMED},
{"PROSTITUTE", PROSTITUTE},
{"SPECIAL", SPECIAL},
};

View File

@ -0,0 +1,35 @@
#include "PedData.hpp"
#include <unordered_map>
uint32_t PedRelationship::threatFromName(const std::string &name) {
static const std::unordered_map<std::string, uint32_t> kThreatMap{
{"PLAYER1", THREAT_PLAYER1}, // Player
{"PLAYER2", THREAT_PLAYER2}, // Unused
{"PLAYER3", THREAT_PLAYER3}, // Unused
{"PLAYER4", THREAT_PLAYER4}, // Unused
{"CIVMALE", THREAT_CIVMALE}, // Civilan
{"CIVFEMALE", THREAT_CIVFEMALE}, // Civilan
{"COP", THREAT_COP}, // Police
{"GANG1", THREAT_GANG1}, // Mafia
{"GANG2", THREAT_GANG2}, // Triad
{"GANG3", THREAT_GANG3}, // Diablo
{"GANG4", THREAT_GANG4}, // Yakuza
{"GANG5", THREAT_GANG5}, // Yardie
{"GANG6", THREAT_GANG6}, // Columbian
{"GANG7", THREAT_GANG7}, // Hood
{"GANG8", THREAT_GANG8}, // Unused
{"GANG9", THREAT_GANG9}, // Unused
{"EMERGENCY", THREAT_EMERGENCY}, // Emergency services
{"PROSTITUTE", THREAT_PROSTITUTE}, // ...
{"CRIMINAL", THREAT_CRIMINAL}, // Criminals
{"SPECIAL", THREAT_SPECIAL}, // SPECIAL
{"GUN", THREAT_GUN}, // Not sure
{"COP_CAR", THREAT_COP_CAR},
{"FAST_CAR", THREAT_FAST_CAR},
{"EXPLOSION", THREAT_EXPLOSION}, // Explosions
{"FIREMAN", THREAT_FIREMAN}, // Firemen?
{"DEADPEDS", THREAT_DEADPEDS}, // Dead bodies
};
return kThreatMap.at(name);
}

View File

@ -0,0 +1,73 @@
#ifndef RWENGINE_DATA_PEDDATA_HPP
#define RWENGINE_DATA_PEDDATA_HPP
#include <cstdint>
#include <string>
#include <vector>
class PedStats {
public:
int id_;
std::string name_;
float fleedistance_;
float rotaterate_;
float fear_;
float temper_;
float lawful_;
float sexy_;
float attackstrength_;
float defendweakness_;
uint32_t flags_;
};
using PedStatsList = std::vector<PedStats>;
class PedRelationship {
public:
enum {
THREAT_PLAYER1 = 1, // Player
THREAT_PLAYER2 = 2, // Unused
THREAT_PLAYER3 = 3, // Unused
THREAT_PLAYER4 = 4, // Unused
THREAT_CIVMALE = 16, // Civilan
THREAT_CIVFEMALE = 32, // Civilan
THREAT_COP = 64, // Police
THREAT_GANG1 = 128, // Mafia
THREAT_GANG2 = 256, // Triad
THREAT_GANG3 = 512, // Diablo
THREAT_GANG4 = 1024, // Yakuza
THREAT_GANG5 = 2048, // Yardie
THREAT_GANG6 = 4096, // Columbian
THREAT_GANG7 = 8192, // Hood
THREAT_GANG8 = 16384, // Unused
THREAT_GANG9 = 32768, // Unused
THREAT_EMERGENCY = 65536, // Emergency services
THREAT_PROSTITUTE = 131072, // ...
THREAT_CRIMINAL = 262144, // Criminals
THREAT_SPECIAL = 524288, // SPECIAL
THREAT_GUN = 1048576, // Not sure
THREAT_COP_CAR = 2097152,
THREAT_FAST_CAR = 4194304,
THREAT_EXPLOSION = 8388608, // Explosions
THREAT_FIREMAN = 16777216, // Firemen?
THREAT_DEADPEDS = 33554432, // Dead bodies
};
uint32_t id_ = 0;
// Unknown values
float a_ = 0.f;
float b_ = 0.f;
float c_ = 0.f;
float d_ = 0.f;
float e_ = 0.f;
uint32_t threatflags_ = 0;
uint32_t avoidflags_ = 0;
static uint32_t threatFromName(const std::string& name);
};
using PedGroup = std::vector<uint16_t>;
using PedGroupList = std::vector<PedGroup>;
#endif

View File

@ -53,6 +53,8 @@ void GameData::load() {
loadHandling("data/handling.cfg");
loadWaterpro("data/waterpro.dat");
loadWeaponDAT("data/weapon.dat");
loadPedStats("data/pedstats.dat");
loadPedRelations("data/ped.dat");
loadIFP("ped.ifp");
@ -62,6 +64,9 @@ void GameData::load() {
loadLevelFile("data/default.dat");
loadLevelFile("data/gta3.dat");
// Load ped groups after IDEs so they can resolve
loadPedGroups("data/pedgrp.dat");
}
void GameData::loadLevelFile(const std::string& path) {
@ -116,7 +121,7 @@ void GameData::loadIDE(const std::string& path) {
auto systempath = index.findFilePath(path).string();
LoaderIDE idel;
if (idel.load(systempath)) {
if (idel.load(systempath, pedstats)) {
std::move(idel.objects.begin(), idel.objects.end(),
std::inserter(modelinfo, modelinfo.end()));
} else {
@ -492,6 +497,115 @@ void GameData::loadWeaponDAT(const std::string& path) {
l.loadWeapons(syspath, weaponData);
}
void GameData::loadPedStats(const std::string& path) {
auto syspath = index.findFilePath(path).string();
std::ifstream fs(syspath.c_str());
if (!fs.is_open()) {
throw std::runtime_error("Failed to open " + path);
}
std::string line;
for (int i = 0; std::getline(fs, line);) {
if (line.empty() || line[0] == '#') {
continue;
}
// The name should be ignored, but we will use it anyway
PedStats stats;
stats.id_ = i++;
std::stringstream ss(line);
ss >> stats.name_;
ss >> stats.fleedistance_;
ss >> stats.rotaterate_;
ss >> stats.fear_;
ss >> stats.temper_;
ss >> stats.lawful_;
ss >> stats.sexy_;
ss >> stats.attackstrength_;
ss >> stats.defendweakness_;
ss >> stats.flags_;
pedstats.push_back(stats);
}
}
void GameData::loadPedRelations(const std::string& path) {
auto syspath = index.findFilePath(path).string();
std::ifstream fs(syspath.c_str());
if (!fs.is_open()) {
throw std::runtime_error("Failed to open " + path);
}
std::string line;
for (int index = 0; std::getline(fs, line);) {
if (line.empty() || line[0] == '#') {
continue;
}
std::stringstream ss(line);
if (isspace(line[0])) {
// Add this flags to the last index
ss >> line;
if (line == "Avoid") {
while (!ss.eof()) {
ss >> line;
auto flag = PedRelationship::threatFromName(line);
pedrels[index].avoidflags_ |= flag;
}
}
if (line == "Threat") {
while (!ss.eof()) {
ss >> line;
auto flag = PedRelationship::threatFromName(line);
pedrels[index].threatflags_ |= flag;
}
}
} else {
ss >> line;
index = PedModelInfo::findPedType(line);
PedRelationship& shp = pedrels[index];
shp.id_ = PedRelationship::threatFromName(line);
ss >> shp.a_;
ss >> shp.b_;
ss >> shp.c_;
ss >> shp.d_;
ss >> shp.e_;
}
}
}
void GameData::loadPedGroups(const std::string& path) {
auto syspath = index.findFilePath(path).string();
std::ifstream fs(syspath.c_str());
if (!fs.is_open()) {
throw std::runtime_error("Failed to open " + path);
}
std::string line;
while (std::getline(fs, line)) {
if (line.empty() || line[0] == '#') {
continue;
}
std::stringstream ss(line);
PedGroup group;
while (ss >> line) {
if (line.empty() || line[0] == '#') {
break;
}
if (line.back() == ',') {
line.resize(line.size()-1);
}
auto model = findModelObject(line);
if (int16_t(model) == -1) {
logger->error("Data", "Invalid model in ped group " + line);
continue;
}
group.push_back(model);
}
if (!group.empty()) {
pedgroups.emplace_back(std::move(group));
}
}
}
bool GameData::loadAudioStream(const std::string& name) {
auto systempath = index.findFilePath("audio/" + name).string();

View File

@ -5,6 +5,7 @@
class Logger;
#include <data/GameTexts.hpp>
#include <data/PedData.hpp>
#include <data/ZoneData.hpp>
#include <loaders/LoaderDFF.hpp>
#include <loaders/LoaderIDE.hpp>
@ -159,6 +160,21 @@ public:
*/
void loadWeaponDAT(const std::string& path);
/**
* Loads pedestrian stats from e.g. pedstats.dat
*/
void loadPedStats(const std::string& path);
/**
* Loads pedestrian relations e.g. peds.dat
*/
void loadPedRelations(const std::string& path);
/**
* Loads pedestrian groups e.g. pedgrp.dat for zone info
*/
void loadPedGroups(const std::string& path);
bool loadAudioStream(const std::string& name);
bool loadAudioClip(const std::string& name, const std::string& fileName);
@ -272,6 +288,21 @@ public:
std::vector<std::shared_ptr<WeaponData>> weaponData;
/**
* Pedstrian type stats
*/
std::vector<PedStats> pedstats;
/**
* Pedestrian relationships
*/
std::array<PedRelationship, PedModelInfo::_NUM_PEDTYPE> pedrels;
/**
* Pedestrian groups
*/
PedGroupList pedgroups;
/**
* @struct WaterArea
* Stores Water Rectangle Information

View File

@ -459,14 +459,14 @@ struct Block18Data {
};
struct Block19PedType {
BlockDword unknown1;
BlockDword bitstring_;
float unknown2;
float unknown3;
float unknown4;
float unknown5;
float unknown6;
BlockDword unknown7;
BlockDword unknown8;
float fleedistance;
float headingchangerate;
BlockDword threatflags_;
BlockDword avoidflags_;
};
struct Block19Data {
Block19PedType types[23];
@ -1190,11 +1190,16 @@ bool SaveGame::loadGame(GameState& state, const std::string& file) {
Block19Data pedTypeData;
READ_VALUE(pedTypeData);
#if RW_DEBUG && 0 // Nor this
#if RW_DEBUG
for (int i = 0; i < 23; ++i) {
if (pedTypeData.types[i].unknown1 & (0x1 << i) != 0) continue;
std::cout << pedTypeData.types[i].unknown1 << " "
<< pedTypeData.types[i].unknown7 << std::endl;
printf("%08x: %f %f %f %f %f threat %08x avoid %08x\n", pedTypeData.types[i].bitstring_,
pedTypeData.types[i].unknown2,
pedTypeData.types[i].unknown3,
pedTypeData.types[i].unknown4,
pedTypeData.types[i].fleedistance,
pedTypeData.types[i].headingchangerate,
pedTypeData.types[i].threatflags_,
pedTypeData.types[i].avoidflags_);
}
#endif

View File

@ -8,11 +8,21 @@
#include <sstream>
#include <string>
bool LoaderIDE::load(const std::string &filename) {
bool LoaderIDE::load(const std::string &filename, const PedStatsList &stats) {
std::ifstream str(filename);
if (!str.is_open()) return false;
auto find_stat_id = [&](const std::string &name) {
auto it =
std::find_if(stats.begin(), stats.end(),
[&](const PedStats &a) { return a.name_ == name; });
if (it == stats.end()) {
return -1;
}
return it->id_;
};
SectionTypes section = NONE;
while (!str.eof()) {
std::string line;
@ -155,7 +165,9 @@ bool LoaderIDE::load(const std::string &filename) {
getline(strstream, buff, ',');
peds->pedtype_ = PedModelInfo::findPedType(buff);
getline(strstream, peds->behaviour_, ',');
std::string behaviour;
getline(strstream, behaviour, ',');
peds->statindex_ = find_stat_id(behaviour);
getline(strstream, peds->animgroup_, ',');
getline(strstream, buff, ',');
@ -232,7 +244,7 @@ bool LoaderIDE::load(const std::string &filename) {
}
auto &object = objects[path.ID];
auto simple = dynamic_cast<SimpleModelInfo*>(object.get());
auto simple = dynamic_cast<SimpleModelInfo *>(object.get());
simple->paths.push_back(path);
break;

View File

@ -3,6 +3,7 @@
#include <data/ModelData.hpp>
#include <data/PathData.hpp>
#include <data/PedData.hpp>
#include <glm/glm.hpp>
#include <iostream>
#include <map>
@ -22,7 +23,7 @@ public:
};
// Load the IDE data into memory
bool load(const std::string& filename);
bool load(const std::string& filename, const PedStatsList& stats);
/**
* @brief objects loaded during the call to load()

View File

@ -525,7 +525,10 @@ void RWGame::tick(float dt) {
currentCam.getView());
// Use the current camera position to spawn pedestrians.
world->cleanupTraffic(currentCam);
world->createTraffic(currentCam);
// Only create new traffic outside cutscenes
if (!state.currentCutscene) {
world->createTraffic(currentCam);
}
}
}
}

View File

@ -25,6 +25,77 @@ BOOST_AUTO_TEST_CASE(test_object_data) {
BOOST_CHECK_EQUAL(def->flags, 0);
}
}
BOOST_AUTO_TEST_CASE(test_ped_stats) {
GameData gd(&Global::get().log, Global::getGamePath());
gd.load();
BOOST_REQUIRE(gd.pedstats.size() > 2);
auto& stat1 = gd.pedstats[0];
auto& stat2 = gd.pedstats[1];
BOOST_CHECK_EQUAL(stat1.id_, 0);
BOOST_CHECK_EQUAL(stat1.fleedistance_, 0.0f);
BOOST_CHECK_EQUAL(stat1.defendweakness_, 0.4f);
BOOST_CHECK_EQUAL(stat1.flags_, 6);
BOOST_CHECK_EQUAL(stat2.id_, 1);
BOOST_CHECK_EQUAL(stat2.fleedistance_, 20.0f);
BOOST_CHECK_EQUAL(stat2.defendweakness_, 1.0f);
BOOST_CHECK_EQUAL(stat2.flags_, 2);
}
BOOST_AUTO_TEST_CASE(test_ped_stat_info) {
GameData gd(&Global::get().log, Global::getGamePath());
gd.load();
BOOST_REQUIRE(gd.pedstats.size() > 2);
BOOST_REQUIRE(gd.modelinfo.find(1) != gd.modelinfo.end());
auto it = gd.modelinfo.find(1);
auto cop = static_cast<PedModelInfo*>(it->second.get());
auto& stat_cop = gd.pedstats[1];
BOOST_CHECK_EQUAL(cop->statindex_, stat_cop.id_);
}
BOOST_AUTO_TEST_CASE(test_ped_relations) {
GameData gd(&Global::get().log, Global::getGamePath());
gd.load();
auto& rel_cop = gd.pedrels[PedModelInfo::COP];
auto& rel_crim = gd.pedrels[PedModelInfo::CRIMINAL];
BOOST_CHECK_EQUAL(rel_cop.id_, PedRelationship::THREAT_COP);
BOOST_CHECK_EQUAL(rel_cop.threatflags_,
PedRelationship::THREAT_GUN |
PedRelationship::THREAT_EXPLOSION |
PedRelationship::THREAT_DEADPEDS);
BOOST_CHECK_EQUAL(rel_crim.id_, PedRelationship::THREAT_CRIMINAL);
BOOST_CHECK_EQUAL(rel_crim.threatflags_,
PedRelationship::THREAT_GUN |
PedRelationship::THREAT_COP |
PedRelationship::THREAT_COP_CAR |
PedRelationship::THREAT_EXPLOSION);
}
BOOST_AUTO_TEST_CASE(test_ped_groups) {
GameData gd(&Global::get().log, Global::getGamePath());
gd.load();
BOOST_REQUIRE(gd.pedgroups.size() > 2);
const auto& def = gd.pedgroups[0];
const auto& red = gd.pedgroups[1];
BOOST_REQUIRE_GE(def.size(), 8);
BOOST_CHECK_EQUAL(def[0], 30);
BOOST_REQUIRE_GE(red.size(), 8);
BOOST_CHECK_EQUAL(red[0], 34);
}
#endif
BOOST_AUTO_TEST_SUITE_END()

View File

@ -9,7 +9,7 @@ BOOST_AUTO_TEST_CASE(test_object_data) {
{
LoaderIDE l;
l.load(Global::get().getGamePath() + "/data/maps/generic.ide");
l.load(Global::get().getGamePath() + "/data/maps/generic.ide", {});
BOOST_ASSERT(l.objects.find(1100) != l.objects.end());
@ -28,7 +28,7 @@ BOOST_AUTO_TEST_CASE(test_object_data) {
{
LoaderIDE l;
l.load(Global::get().getGamePath() + "/data/default.ide");
l.load(Global::get().getGamePath() + "/data/default.ide", {});
BOOST_ASSERT(l.objects.find(90) != l.objects.end());