Fixed AIs and crashes

This commit is contained in:
2026-02-21 00:55:39 +03:00
parent eab5ed0794
commit ccf451336d
17 changed files with 627 additions and 129 deletions

View File

@@ -313,7 +313,7 @@ void createItemGeometry(flecs::entity e)
e.set<TerrainItemNode>({ itemNode, geo });
} else if (itemType == "town") {
OgreAssert(geo, "Can't create static geometry");
createTown(e, itemNode, geo);
createTown(e, itemNode, geo);
e.set<TerrainItemNode>({ itemNode, geo });
std::cout << " town created: " << e.id() << std::endl;
} else {
@@ -381,5 +381,10 @@ flecs::entity createMeshGeometry(const Ogre::String &meshName,
return e;
}
void registerTownItem(flecs::entity e)
{
registerTown(e);
}
}
}

View File

@@ -57,6 +57,7 @@ struct harbourMaker {
void createItemGeometry(flecs::entity e);
void destroyItemGeometry(flecs::entity e);
void updateItemGeometry(flecs::entity e);
void registerTownItem(flecs::entity e);
flecs::entity createMeshGeometry(const Ogre::String &meshName,
flecs::entity parente,
Ogre::SceneNode *sceneNode,

View File

@@ -18,6 +18,7 @@
#include "PhysicsModule.h"
#include "LuaData.h"
#include "PlayerActionModule.h"
#include "CharacterModule.h"
#include "CharacterManagerModule.h"
#include "CharacterAIModule.h"
#include "items.h"
@@ -2142,13 +2143,95 @@ void runAllScriptsForTown(flecs::entity e)
j["districts"] = districts;
StaticGeometryModule::setItemProperties(e, j.dump());
}
struct Selector {
int selection;
Ogre::String result;
Ogre::String label;
std::vector<Ogre::String> options;
Selector(const Ogre::String &label,
const std::vector<Ogre::String> &options)
: label(label)
, options(options)
, selection(-1)
{
}
bool select()
{
bool changed = false;
if (selection < 0)
selection = 0;
if (selection >= options.size())
selection = (options.size() > 0) ? options.size() - 1 :
0;
if (options.size() == 0) {
ImGui::Text("None: %s", label.c_str());
return false;
}
if (ImGui::BeginCombo(label.c_str(),
options[selection].c_str())) {
int i;
for (i = 0; i < options.size(); i++) {
bool selected = selection == i;
if (ImGui::Selectable(options[i].c_str(),
selected)) {
selection = i;
changed = true;
}
}
ImGui::EndCombo();
}
if (changed || result.empty())
result = options[selection];
return changed;
}
void set_default(const Ogre::String &def)
{
int index = -1;
auto pos = std::find(options.begin(), options.end(), def);
if (pos == options.end())
index = -1;
else {
index = std::distance(options.begin(), pos);
selection = index;
result = options[index];
}
}
};
bool editNPCs(nlohmann::json &npcs)
{
struct slotEdit {
const Ogre::String &label;
const Ogre::String &slotBase;
std::vector<Ogre::String> *options_m;
std::vector<Ogre::String> *options_f;
const Ogre::String &slot;
};
static std::vector<Ogre::String> faces_a_m, hairs_a_m, tops_a_m,
bottoms_a_m, feet_a_m;
static std::vector<Ogre::String> faces_a_f, hairs_a_f, tops_a_f,
bottoms_a_f, feet_a_f;
ZoneScoped;
bool changed = false;
ImGui::Text("NPC");
int id = 0;
struct slotEdit pslots_a[] = {
{ "Face", "face", &faces_a_m, &faces_a_f, "slot_face" },
{ "Hair", "hair", &hairs_a_m, &hairs_a_f, "slot_hair" },
{ "Top", "top", &tops_a_m, &tops_a_f, "slot_top" },
{ "Bottom", "bottom", &bottoms_a_m, &bottoms_a_f,
"slot_bottom" },
{ "Feet", "feet", &feet_a_m, &feet_a_f, "slot_feet" },
};
for (auto g : pslots_a) {
g.options_m->clear();
g.options_f->clear();
ECS::get_mut<CharacterModule>().getSlotMeshes(
"adult", "male", g.slotBase, *g.options_m);
ECS::get_mut<CharacterModule>().getSlotMeshes(
"adult", "female", g.slotBase, *g.options_f);
}
for (auto &npc : npcs) {
Ogre::String meid = Ogre::StringConverter::toString(id);
static char firstName[64] = { 0 };
static char lastName[64] = { 0 };
static char nickName[64] = { 0 };
@@ -2163,6 +2246,21 @@ bool editNPCs(nlohmann::json &npcs)
npc["tags"] = "";
if (npc.find("sex") == npc.end())
npc["sex"] = 1;
if (npc["sex"].get<int>() == 0) {
for (const auto &m : pslots_a) {
if (npc.find(m.slot) == npc.end()) {
npc[m.slot] = m.options_m->at(0);
changed = true;
}
}
} else if (npc["sex"].get<int>() == 1) {
for (const auto &m : pslots_a) {
if (npc.find(m.slot) == npc.end()) {
npc[m.slot] = m.options_m->at(0);
changed = true;
}
}
}
strncpy(firstName, npc["firstName"].get<Ogre::String>().c_str(),
sizeof(firstName));
@@ -2173,61 +2271,77 @@ bool editNPCs(nlohmann::json &npcs)
strncpy(tags, npc["tags"].get<Ogre::String>().c_str(),
sizeof(tags));
ImGui::InputText(
("Last name##" + Ogre::StringConverter::toString(id))
.c_str(),
lastName, sizeof(lastName));
ImGui::InputText(("Last name##" + meid).c_str(), lastName,
sizeof(lastName));
if (ImGui::IsItemDeactivatedAfterEdit()) {
npc["lastName"] = Ogre::String(lastName);
changed = true;
}
ImGui::InputText(
("First name##" + Ogre::StringConverter::toString(id))
.c_str(),
firstName, sizeof(firstName));
ImGui::InputText(("First name##" + meid).c_str(), firstName,
sizeof(firstName));
if (ImGui::IsItemDeactivatedAfterEdit()) {
npc["firstName"] = Ogre::String(firstName);
changed = true;
}
ImGui::InputText(
("Nickname##" + Ogre::StringConverter::toString(id))
.c_str(),
nickName, sizeof(nickName));
ImGui::InputText(("Nickname##" + meid).c_str(), nickName,
sizeof(nickName));
if (ImGui::IsItemDeactivatedAfterEdit()) {
npc["nickName"] = Ogre::String(nickName);
changed = true;
}
Selector race(("Race##" + meid).c_str(), { "human" });
Selector age(("Age##" + meid).c_str(), { "adult" });
Selector sex(("Sex##" + meid).c_str(), { "male", "female" });
if (npc.find("race") != npc.end())
race.set_default(npc["race"].get<Ogre::String>());
if (npc.find("age") != npc.end())
age.set_default(npc["age"].get<Ogre::String>());
if (npc.find("sex") != npc.end())
sex.selection = npc["sex"].get<int>();
if (race.select()) {
npc["race"] = race.result;
changed = true;
}
if (age.select()) {
npc["age"] = age.result;
changed = true;
}
/* sex is integer */
if (sex.select()) {
npc["sex"] = sex.selection;
changed = true;
}
if (sex.selection == 0) {
for (const auto &es : pslots_a) {
Selector sel((es.label + "##" + meid).c_str(),
*es.options_m);
sel.set_default(
npc[es.slot].get<Ogre::String>());
if (sel.select()) {
npc[es.slot] = sel.result;
changed = true;
}
}
} else if (sex.selection == 1) {
for (const auto &es : pslots_a) {
Selector sel((es.label + "##" + meid).c_str(),
*es.options_f);
sel.set_default(
npc[es.slot].get<Ogre::String>());
if (sel.select()) {
npc[es.slot] = sel.result;
changed = true;
}
}
}
ImGui::InputText(
("Tags##" + Ogre::StringConverter::toString(id)).c_str(),
tags, sizeof(tags));
ImGui::InputText(("Tags##" + meid).c_str(), tags, sizeof(tags));
if (ImGui::IsItemDeactivatedAfterEdit()) {
npc["tags"] = Ogre::String(tags);
changed = true;
}
int selection = npc["sex"].get<int>();
const char *items[] = { "Male", "Female" };
if (ImGui::Combo(("Sex##" + Ogre::StringConverter::toString(id))
.c_str(),
&selection, items, 2))
npc["sex"] = selection;
if (ImGui::SmallButton(
("Spawn##" + Ogre::StringConverter::toString(id))
.c_str())) {
int sex = npc["sex"].get<int>();
struct desc {
const char *face, *hair, *top, *bottom, *feet;
};
struct desc models[] = {
{ "male_Face.mesh", "male_Hair001.mesh",
"male_BodyTop.mesh", "male_BodyBottom.mesh",
"male_BodyFeet.mesh" },
{ "female_Face.mesh", "female_Hair001.mesh",
"female_BodyTop.mesh",
"female_BodyBottom.mesh",
"female_BodyFeet.mesh" }
};
if (ImGui::SmallButton(("Spawn##" + meid).c_str())) {
Ogre::Vector3 npcPosition;
Ogre::Quaternion npcOrientation;
from_json(npc["position"], npcPosition);
@@ -2236,10 +2350,12 @@ bool editNPCs(nlohmann::json &npcs)
// FIXME: create TownCharacterManager and register NPCs through there
ECS::get_mut<CharacterManagerModule>()
.createCharacterData(
models[sex].face, models[sex].hair,
models[sex].top, models[sex].bottom,
models[sex].feet, npcPosition,
npcOrientation);
npc["slot_face"].get<Ogre::String>(),
npc["slot_hair"].get<Ogre::String>(),
npc["slot_top"].get<Ogre::String>(),
npc["slot_bottom"].get<Ogre::String>(),
npc["slot_feet"].get<Ogre::String>(),
npcPosition, npcOrientation);
}
if (ImGui::SmallButton(
("Delete##" + Ogre::StringConverter::toString(id))
@@ -2258,9 +2374,39 @@ bool editNPCs(nlohmann::json &npcs)
ImGui::InputText("First name", firstName, sizeof(firstName));
static char tags[256] = { 0 };
ImGui::InputText("Tags", tags, sizeof(tags));
static int selection = 0;
const char *items[] = { "Male", "Female" };
ImGui::Combo("Sex", &selection, items, 2);
static Selector race("Race##new_npc", { "human" });
static Selector age("Age##new_npc", { "adult" });
static Selector sex("Sex##new_npc", { "male", "female" });
changed = changed || race.select();
changed = changed || age.select();
changed = changed || sex.select();
static Selector sel_face("Face##new_npc", {});
static Selector sel_hair("Hair##new_npc", {});
static Selector sel_top("Top##new_npc", {});
static Selector sel_bottom("Bottom##new_npc", {});
static Selector sel_feet("Feet##new_npc", {});
if (changed || sel_face.options.size() == 0) {
if (sex.selection == 0) {
sel_face = Selector("Face##new_npc", faces_a_m);
sel_hair = Selector("Hair##new_npc", hairs_a_m);
sel_top = Selector("Top##new_npc", tops_a_m);
sel_bottom = Selector("Bottom##new_npc",
bottoms_a_m);
sel_feet = Selector("Feet##new_npc", feet_a_m);
} else if (sex.selection == 1) {
sel_face = Selector("Face##new_npc", faces_a_f);
sel_hair = Selector("Hair##new_npc", hairs_a_f);
sel_top = Selector("Top##new_npc", tops_a_f);
sel_bottom = Selector("Bottom##new_npc",
bottoms_a_f);
sel_feet = Selector("Feet##new_npc", feet_a_f);
}
}
changed = changed || sel_face.select();
changed = changed || sel_hair.select();
changed = changed || sel_top.select();
changed = changed || sel_bottom.select();
changed = changed || sel_feet.select();
if (ImGui::SmallButton("Add NPC")) {
nlohmann::json npc;
npc["lastName"] = Ogre::String(lastName);
@@ -2274,7 +2420,12 @@ bool editNPCs(nlohmann::json &npcs)
.sceneNode->_getDerivedOrientation();
to_json(npc["position"], npcPosition);
to_json(npc["orientation"], npcOrientation);
npc["sex"] = selection;
npc["sex"] = sex.selection;
npc["slot_hair"] = sel_hair.result;
npc["slot_face"] = sel_face.result;
npc["slot_top"] = sel_top.result;
npc["slot_bottom"] = sel_bottom.result;
npc["slot_feet"] = sel_feet.result;
npc["health"] = 100;
npc["stamina"] = 100;
npcs.push_back(npc);
@@ -5977,6 +6128,7 @@ struct TownDecorateFurniture : TownTask {
float radius =
action["radius"]
.get<float>();
#if 0
if (ECS::get()
.has<ActionNodeList>()) {
ActionNodeList::ActionNode
@@ -6019,6 +6171,7 @@ struct TownDecorateFurniture : TownTask {
ECS::modified<
ActionNodeList>();
}
#endif
}
}
}
@@ -6123,12 +6276,209 @@ void createTown(flecs::entity e, Ogre::SceneNode *sceneNode,
geo->build();
});
});
}
void registerTown(flecs::entity e)
{
ZoneScoped;
registerTownNPCs(e);
if (ECS::get().entity<CharacterAIModule>().is_valid())
if (ECS::get().has<CharacterAIModule>()) {
ECS::get_mut<CharacterAIModule>().createAI(e);
ECS::modified<CharacterAIModule>();
}
}
createTownActionNodes(e);
}
void createTownActionNodes(flecs::entity e)
{
const TerrainItem &item = e.get<TerrainItem>();
Ogre::String props = item.properties;
nlohmann::json jprops = nlohmann::json::parse(props);
#if 0
Ogre::MaterialPtr townMaterial = createTownMaterial(e);
#endif
for (const auto &jdistrict : jprops["districts"]) {
const nlohmann::json &jp = jdistrict;
nlohmann::json jlots = nlohmann::json::array();
float baseHeight = 4.0f;
Ogre::Vector3 localPosition(0, 0, 0);
Ogre::Quaternion localRotation = Ogre::Quaternion::IDENTITY;
Ogre::Vector3 centerPosition = item.position;
Ogre::Quaternion centerOrientation = item.orientation;
float delevation = 0.0f;
float radius = 50.0f;
if (jp.find("elevation") != jp.end())
delevation = jp["elevation"].get<float>();
if (jp.find("radius") != jp.end())
radius = jp["radius"].get<float>();
from_json(jp["position"], localPosition);
from_json(jp["rotation"], localRotation);
centerPosition = centerPosition + localPosition +
Ogre::Vector3(0, delevation, 0);
centerOrientation = centerOrientation * localRotation;
if (jdistrict.find("lots") != jdistrict.end())
jlots = jdistrict["lots"];
for (const auto &jb : jlots) {
float angle = 0.0f;
int depth = 10;
int width = 10;
float distance = radius;
float elevation = 0.0f;
std::cout << jb.dump() << std::endl;
if (jb.find("angle") != jb.end())
angle = jb["angle"].get<float>();
if (jb.find("depth") != jb.end())
depth = jb["depth"].get<int>();
if (jb.find("width") != jb.end())
width = jb["width"].get<int>();
if (jb.find("elevation") != jb.end())
elevation = jb["elevation"].get<float>();
OgreAssert(width > 1 && depth > 1 && baseHeight > 1,
"Bad stuff happen");
Ogre::Quaternion rotation = Ogre::Quaternion(
Ogre::Degree(angle), Ogre::Vector3::UNIT_Y);
Ogre::Vector3 offset =
centerOrientation * rotation *
(Ogre::Vector3::UNIT_Z * distance);
Ogre::Vector3 worldCenterPosition =
centerPosition + offset +
Ogre::Vector3(0, elevation, 0);
Ogre::Quaternion worldCenterOrientation =
centerOrientation * rotation;
float outOffset = 1.05f;
if (jb.find("furniture_cells") != jb.end()) {
for (auto &jfcell : jb["furniture_cells"]) {
int x = jfcell["x"].get<int>();
int y = jfcell["y"].get<int>();
int z = jfcell["z"].get<int>();
nlohmann::json furniture =
jfcell["furniture"];
Ogre::Vector3 cellOffset(
x * 2.0f, y * 4.0f, z * 2.0f);
Ogre::Vector3 offsetX =
worldCenterOrientation *
Ogre::Vector3::UNIT_X *
(float)x * 2.0f;
Ogre::Vector3 offsetZ =
worldCenterOrientation *
Ogre::Vector3::UNIT_Z *
(float)z * 2.0f;
Ogre::Vector3 offsetY(0, y * 4.0f, 0);
static Ogre::String materialName1 = "";
static Ogre::String materialName2 = "";
int rotation = 2;
if (jfcell.find("rotation") !=
jfcell.end())
rotation = jfcell["rotation"]
.get<int>();
Ogre::Vector3 offset =
worldCenterOrientation *
Ogre::Vector3::UNIT_Z * 0.0f;
#if 0
Ogre::Vector3 furniturePosition =
worldCenterPosition + offsetX +
offsetZ + offsetY + offset;
Ogre::Quaternion furnitureOrientation =
worldCenterOrientation *
Ogre::Quaternion(
Ogre::Degree(
90.0f *
(float)rotation),
Ogre::Vector3::UNIT_Y);
#endif
if (furniture.find("actions") !=
furniture.end()) {
for (const auto &action :
furniture["actions"]) {
std::cout
<< "SENSOR: "
<< action.dump()
<< std::endl;
std::cout
<< furniture
.dump()
<< std::endl;
Ogre::Vector3
actionPosition;
actionPosition.x =
action["position_x"]
.get<float>();
actionPosition.y =
action["position_y"]
.get<float>();
actionPosition.z =
action["position_z"]
.get<float>();
Ogre::Quaternion worldSensorRotation =
worldCenterOrientation *
Ogre::Quaternion(
Ogre::Degree(
90.0f *
(float)rotation),
Ogre::Vector3::
UNIT_Y);
Ogre::Vector3 worldSensorPosition =
worldCenterPosition +
offsetX +
offsetZ +
offsetY +
offset +
worldSensorRotation *
actionPosition;
if (ECS::get()
.has<ActionNodeList>()) {
ActionNodeList::ActionNode
anode;
anode.action =
action["action"]
.get<Ogre::String>();
anode.action_text =
action["action_text"]
.get<Ogre::String>();
anode.radius =
action["radius"]
.get<float>();
anode.height =
action["height"]
.get<float>();
anode.props =
action;
anode.props
["town"] =
e.id();
anode.props
["index"] =
-1;
anode.position =
worldSensorPosition;
anode.rotation =
worldSensorRotation;
anode.dynamic =
false;
ECS::get_mut<
ActionNodeList>()
.addNode(
anode);
std::cout
<< "action: "
<< action.dump(
4)
<< std::endl;
ECS::modified<
ActionNodeList>();
}
}
}
}
}
}
}
}
}
}

View File

@@ -2,7 +2,8 @@
#define __TOWN_H__
#include <OgreMeshLodGenerator.h>
#include <flecs.h>
namespace Procedural {
namespace Procedural
{
class TriangleBuffer;
}
namespace ECS
@@ -21,6 +22,8 @@ void clampUV(flecs::entity e, Procedural::TriangleBuffer &tb,
Ogre::MaterialPtr createTownMaterial(flecs::entity e, bool force = false);
void createTown(flecs::entity e, Ogre::SceneNode *sceneNode,
Ogre::StaticGeometry *geo);
void registerTown(flecs::entity e);
void createTownActionNodes(flecs::entity e);
}
}
#endif