diff --git a/assets/blender/characters/CMakeLists.txt b/assets/blender/characters/CMakeLists.txt index f39db9a..4485f7c 100644 --- a/assets/blender/characters/CMakeLists.txt +++ b/assets/blender/characters/CMakeLists.txt @@ -48,7 +48,7 @@ set(VRM_IMPORTED_BLENDS # DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py ${VRM_IMPORTED_BLENDS} ${EDITED_BLEND_TARGETS} # WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set(FEMALE_OBJECTS "BodyTopRobe;BodyTop;BodyBottom;BodyFeet;Hair;Face;BackHair;Accessoty") -set(MALE_OBJECTS "BodyTopRobe;BodyTop;BodyBottom;BodyFeet;Hair;Face;BackHair;Accessory") +set(MALE_OBJECTS "BodyTopRobe;BodyTop;BodyBottomPants;BodyBottom;BodyFeetPants;BodyFeetPantsShoes;BodyFeet;Hair;Face;BackHair;Accessory") add_custom_command( OUTPUT ${CMAKE_BINARY_DIR}/characters/male/normal-male.glb COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES} diff --git a/assets/blender/characters/edited-normal-female.blend b/assets/blender/characters/edited-normal-female.blend index 5378f9e..f32c9d3 100644 --- a/assets/blender/characters/edited-normal-female.blend +++ b/assets/blender/characters/edited-normal-female.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28a6502ca805e566381b08dd23366b6ffbfd0e8accb7256f4b439e0bdabf75b0 -size 18566317 +oid sha256:f3a722d4acf4b4e17770b4696496852caa40c8b875a13c707c8e9c94269c0437 +size 18561336 diff --git a/assets/blender/characters/edited-normal-male.blend b/assets/blender/characters/edited-normal-male.blend index 89fe37d..2296327 100644 --- a/assets/blender/characters/edited-normal-male.blend +++ b/assets/blender/characters/edited-normal-male.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6604dd42adec3e370561788f0b11d7a8f102a87876581408b0ea4e0f585d3582 -size 14008165 +oid sha256:aeade10e4eff3dd13f78174bceb3317de312cbe7db6f36444bce25f85ce07328 +size 14308944 diff --git a/assets/blender/scripts/export_models2.py b/assets/blender/scripts/export_models2.py index 2af50b4..d75400e 100644 --- a/assets/blender/scripts/export_models2.py +++ b/assets/blender/scripts/export_models2.py @@ -7,6 +7,7 @@ import glob import shutil from mathutils import Vector, Matrix from math import radians, pi +import json sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) #from settings import ExportMappingFemale, ExportMappingMale, ExportMappingMaleBabyShape, ExportMappingMaleEdited, ExportMappingFemaleEdited, ExportMappingMaleTestShapeEdited, ExportMappingMaleBaseShapeEdited @@ -309,9 +310,23 @@ for mapping in[CommandLineMapping()]: if not mat.name.startswith(prefix): mat.name = prefix + mat.name print(f"Renamed material '{mat.name}' on object '{name}'") + # 3. Export custom properties to json + save_data = {} + for key in obj.keys(): + if key in ["age", "sex", "slot"]: + save_data[key] = obj[key] + if key.startswith("body_"): + save_data[key.replace("body_", "", 1)] = obj[key] + save_data["mesh"] = obj.data.name + ".mesh" + json_dir = os.path.dirname(mapping.gltf_path) + save_file = json_dir + "/body_part_" + obj.data.name + ".json" + json_filepath = os.path.join(json_dir, save_file) + with open(json_filepath, 'w') as f: + json.dump(save_data, f) + armobj = bpy.data.objects.get(mapping.armature_name) armobj.data.name = armobj.name - bpy.ops.ogre.export(filepath=mapping.gltf_path.replace(".glb", ".scene"), EX_SELECTED_ONLY=False, EX_SHARED_ARMATURE=True, EX_LOD_GENERATION='0', EX_GENERATE_TANGENTS='4') + bpy.ops.ogre.export(filepath=mapping.gltf_path.replace(".glb", ".scene"), EX_SELECTED_ONLY=False, EX_SHARED_ARMATURE=True, EX_LOD_GENERATION='0', EX_LOD_DISTANCE=20, EX_GENERATE_TANGENTS='4') bpy.ops.wm.read_homefile(use_empty=True) time.sleep(2) diff --git a/src/editor/main.cpp b/src/editor/main.cpp index c3f41e6..51a2687 100644 --- a/src/editor/main.cpp +++ b/src/editor/main.cpp @@ -387,14 +387,22 @@ public: } void locateResources() override { - Ogre::ResourceGroupManager::getSingleton().createResourceGroup( + Ogre::ResourceGroupManager::getSingleton().createResourceGroup( + "Characters", true); + Ogre::ResourceGroupManager::getSingleton().createResourceGroup( "Water", true); Ogre::ResourceGroupManager::getSingleton().createResourceGroup( "LuaScripts", false); Ogre::ResourceGroupManager::getSingleton().addResourceLocation( "./lua-scripts", "FileSystem", "LuaScripts", true, true); - OgreBites::ApplicationContext::locateResources(); + Ogre::ResourceGroupManager::getSingleton().addResourceLocation( + "./characters/male", "FileSystem", "Characters", false, + true); + Ogre::ResourceGroupManager::getSingleton().addResourceLocation( + "./characters/female", "FileSystem", "Characters", + false, true); + OgreBites::ApplicationContext::locateResources(); } void loadResources() override { diff --git a/src/gamedata/CharacterAIModule.cpp b/src/gamedata/CharacterAIModule.cpp index a6594e0..e7d2194 100644 --- a/src/gamedata/CharacterAIModule.cpp +++ b/src/gamedata/CharacterAIModule.cpp @@ -271,6 +271,7 @@ public: }; ActionNodeActions(int node, const Blackboard &prereq, int cost) { + ZoneScoped; OgreAssert( node < ECS::get().dynamicNodes.size(), "bad node " + Ogre::StringConverter::toString(node)); @@ -347,6 +348,7 @@ public: have_bits = true; } if (!have_bits) { + ZoneScopedN("Use"); std::cout << "use: " << props.dump(4) << std::endl; // OgreAssert(false, "props: " + props.dump(4)); @@ -396,8 +398,8 @@ private: } void activate() override { - std::cout << action->get_name(); - std::cout << "!"; + ZoneScoped; + ZoneTextF("%s", action->get_name().c_str()); delay = 1.0f; } @@ -414,6 +416,7 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs) static std::mutex ecs_mutex; ecs.module(); ecs.import (); + ecs.import (); ecs.component(); ecs.component().on_add([](flecs::entity e, TownAI &ai) { std::lock_guard lock(ecs_mutex); @@ -512,6 +515,7 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs) const TownNPCs &npcs) { ZoneScopedN("CreateBlackboards"); std::lock_guard lock(ecs_mutex); + OgreAssert(npcs.npcs.size() > 0, "npcs not crated"); createBlackboards(town, npcs, ai); }); ecs.system("UpdateDynamicActions") @@ -520,11 +524,15 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs) TownNPCs &npcs) { ZoneScopedN("UpdateDynamicActions"); std::lock_guard lock(ecs_mutex); + OgreAssert(npcs.npcs.size() > 0, "npcs not crated"); if (ai.nodeActions.size() > 0) return; if (alist.dynamicNodes.size() == 0) ECS::get_mut() .updateDynamicNodes(); + OgreAssert(alist.nodes.size() > 0, "bad nodes"); + if (alist.dynamicNodes.size() == 0) + return; OgreAssert(alist.dynamicNodes.size() > 0, "bad dynamic nodes"); int nodeIndex; @@ -550,6 +558,7 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs) .each([this](flecs::entity town, ActionNodeList &alist, TownAI &ai, TownNPCs &npcs) { ZoneScopedN("UpdateDynamicNodes"); + OgreAssert(npcs.npcs.size() > 0, "npcs not crated"); std::lock_guard lock(ecs_mutex); ECS::get_mut().updateDynamicNodes(); }); @@ -574,6 +583,7 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs) .kind(flecs::OnUpdate) .each([](flecs::entity e, TownNPCs &npcs) { ZoneScopedN("UpdateNPCPositions"); + OgreAssert(npcs.npcs.size() > 0, "npcs not crated"); for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) { auto &npc = npcs.npcs.at(it->first); @@ -591,6 +601,7 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs) .each([this](flecs::entity town, ActionNodeList &alist, TownAI &ai, const TownNPCs &npcs) { ZoneScopedN("UpdateBlackboards"); + OgreAssert(npcs.npcs.size() > 0, "npcs not crated"); Ogre::Root::getSingleton().getWorkQueue()->addTask([this, town, @@ -624,6 +635,10 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs) .each([&](flecs::entity town, TownAI &ai, const TownNPCs &npcs) { ZoneScopedN("PlanAI"); + OgreAssert(npcs.npcs.size() > 0, "npcs not crated"); + OgreAssert(ai.blackboards.size() > 0, + "blackboards not crated"); + OgreAssert(ai.memory.size() > 0, "memory not crated"); Ogre::Root::getSingleton().getWorkQueue()->addTask([this, town, npcs, @@ -646,6 +661,10 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs) .each([&](flecs::entity town, const EngineData &eng, TownNPCs &npcs, TownAI &ai) { ZoneScopedN("RunPLAN"); + OgreAssert(npcs.npcs.size() > 0, "npcs not crated"); + OgreAssert(ai.blackboards.size() > 0, + "blackboards not crated"); + OgreAssert(ai.memory.size() > 0, "memory not crated"); for (const auto &plans : ai.plans) { if (plan_exec.find(plans.first) != plan_exec.end()) { @@ -668,13 +687,19 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs) // std::cout << " Goal: "; plan.goal->goal.dump_bits(); for (const auto &action : plan.plan) { - ActionExec::PlanExecData data({ + TownNPCs::NPCData &npc = npcs.npcs.at( - plans.first), + plans.first); + Blackboard &bb = ai.blackboards.at( - plans.first), + plans.first); + nlohmann::json &mem = ai.memory.at( - plans.first), + plans.first); + ActionExec::PlanExecData data({ + npc, + bb, + mem, }); // TODO: executor factory is needed @@ -773,19 +798,26 @@ void CharacterAIModule::buildPlans(flecs::entity town, const TownNPCs &npcs, if (plan_tasks.size() > 0) { bool created = (plan_tasks.front())(); if (created) { - std::cout << plan_tasks.front().blackboard.index << " "; - std::cout << "Goal: " - << plan_tasks.front().goal.get_name(); - plan_tasks.front().goal.goal.dump_bits(); - std::cout << std::endl; - std::cout << "Path: "; - for (auto &action : plan_tasks.front().plan.plan) { - OgreAssert(action, "No action"); - std::cout << action->get_name() + " "; + ZoneTextF("%d: Goal: %s", + plan_tasks.front().blackboard.index, + plan_tasks.front().goal.get_name().c_str()); + { + std::cout << plan_tasks.front().blackboard.index + << " "; + std::cout << "Goal: " + << plan_tasks.front().goal.get_name(); + plan_tasks.front().goal.goal.dump_bits(); + std::cout << std::endl; + std::cout << "Path: "; + for (auto &action : + plan_tasks.front().plan.plan) { + OgreAssert(action, "No action"); + std::cout << action->get_name() + " "; + } + std::cout << " size: " + << plan_tasks.front().plan.plan.size() + << std::endl; } - std::cout << " size: " - << plan_tasks.front().plan.plan.size() - << std::endl; ai.plans[plan_tasks.front().blackboard.index].push_back( plan_tasks.front().plan); } diff --git a/src/gamedata/CharacterManagerModule.cpp b/src/gamedata/CharacterManagerModule.cpp index 0ef0605..13d5021 100644 --- a/src/gamedata/CharacterManagerModule.cpp +++ b/src/gamedata/CharacterManagerModule.cpp @@ -88,12 +88,12 @@ void createNPCActionNodes(flecs::entity town, int index) CharacterManagerModule::CharacterManagerModule(flecs::world &ecs) { ecs.module(); - ecs.import (); + ecs.component(); + ecs.import (); ecs.import (); ecs.import (); ecs.import (); ecs.component(); - ecs.component(); ecs.component(); ecs.system("UpdateCharacters") .kind(flecs::OnUpdate) @@ -178,15 +178,17 @@ CharacterManagerModule::createPlayer(const Ogre::Vector3 &position, OgreAssert(!player.is_valid(), "Player already created"); player = ECS::get().entity("player"); OgreAssert(player.is_valid(), "Can't create player"); - std::cout << "Begin player create" << std::endl; - player.add(); - ECS::get_mut().createCharacter( - player, position, rotation, "male_Face.mesh", - "male_Hair001.mesh", "male_BodyTop.mesh", - "male_BodyBottom.mesh", "male_BodyFeet.mesh"); - ECS::modified(); - std::cout << "End player create" << std::endl; - count++; + { + ZoneScopedN("PlayerCreate"); + + player.add(); + ECS::get_mut().createCharacter( + player, position, rotation, "male_Face.mesh", + "male_Hair001.mesh", "male_BodyTop.mesh", + "male_BodyBottom.mesh", "male_BodyFeet.mesh"); + ECS::modified(); + count++; + } return player; } @@ -221,41 +223,33 @@ void CharacterManagerModule::registerTownCharacters(flecs::entity town) nlohmann::json npcs = nlohmann::json::array(); if (town.has()) return; - if (j.find("npcs") != j.end()) - npcs = j["npcs"]; + if (j.find("npcs") == j.end()) + return; + npcs = j["npcs"]; std::cout << npcs.dump(4) << std::endl; + if (npcs.size() == 0) + return; int index = 0; std::map npcMap; for (auto &npc : npcs) { - 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" } - }; - int sex = npc["sex"].get(); Ogre::Vector3 npcPosition; Ogre::Quaternion npcOrientation; from_json(npc["position"], npcPosition); from_json(npc["orientation"], npcOrientation); TownNPCs::NPCData npcData; npcData.e = flecs::entity(); - npcData.modelFace = models[sex].face; - npcData.modelHair = models[sex].hair; - npcData.modelTop = models[sex].top; - npcData.modelBottom = models[sex].bottom; - npcData.modelFeet = models[sex].feet; + npcData.modelFace = npc["slot_face"].get(); + npcData.modelHair = npc["slot_hair"].get(); + npcData.modelTop = npc["slot_top"].get(); + npcData.modelBottom = npc["slot_bottom"].get(); + npcData.modelFeet = npc["slot_feet"].get(); npcData.orientation = npcOrientation; npcData.position = npcPosition; npcData.props = npc; npcMap[index] = npcData; index++; } + OgreAssert(npcMap.size() > 0, "no npcs registered"); town.set({ npcMap }); } diff --git a/src/gamedata/CharacterModule.cpp b/src/gamedata/CharacterModule.cpp index f206359..61f51a6 100644 --- a/src/gamedata/CharacterModule.cpp +++ b/src/gamedata/CharacterModule.cpp @@ -16,7 +16,53 @@ CharacterModule::CharacterModule(flecs::world &ecs) { ZoneScoped; struct TriggerPhysicsChange {}; - ecs.module(); + static std::vector part_names; + const std::vector &groups = + Ogre::ResourceGroupManager::getSingleton().getResourceGroups(); + if (part_names.size() == 0) { + int i; + for (i = 0; i < groups.size(); i++) { + std::vector names = + *Ogre::ResourceGroupManager::getSingleton() + .findResourceNames(groups[i], + "body_part_*.json"); + part_names.insert(part_names.end(), names.begin(), + names.end()); + } + } + body_parts = nlohmann::json::object(); + for (auto &g : part_names) { + Ogre::String group = Ogre::ResourceGroupManager::getSingleton() + .findGroupContainingResource(g); + Ogre::DataStreamPtr stream = + Ogre::ResourceGroupManager::getSingleton().openResource( + g, group); + Ogre::String json = stream->getAsString(); + nlohmann::json jdata = nlohmann::json::parse(json); + if (jdata.find("age") == jdata.end()) + continue; + if (jdata.find("sex") == jdata.end()) + continue; + if (jdata.find("slot") == jdata.end()) + continue; + if (jdata.find("mesh") == jdata.end()) + continue; + Ogre::String age = jdata["age"].get(); + Ogre::String sex = jdata["sex"].get(); + Ogre::String slot = jdata["slot"].get(); + Ogre::String mesh = jdata["mesh"].get(); + if (body_parts.find(age) == body_parts.end()) + body_parts[age] = nlohmann::json::object(); + if (body_parts[age].find(sex) == body_parts[age].end()) + body_parts[age][sex] = nlohmann::json::object(); + if (body_parts[age][sex].find(slot) == + body_parts[age][sex].end()) + body_parts[age][sex][slot] = nlohmann::json::array(); + body_parts[age][sex][slot].push_back(mesh); + Ogre::MeshManager::getSingleton().load(mesh, "Characters"); + } + std::cout << body_parts.dump(4) << std::endl; + ecs.module(); ecs.component(); ecs.component(); ecs.component() @@ -701,4 +747,19 @@ void CharacterModule::remapMeshToMasterSkeleton(Ogre::MeshPtr clothMesh, clothMesh->setSkeletonName(masterSkel->getName()); clothMesh->_compileBoneAssignments(); } + +void CharacterModule::getSlotMeshes(const Ogre::String &age, + const Ogre::String &sex, + const Ogre::String &slotName, + std::vector &meshes) +{ + OgreAssert(body_parts.find(age) != body_parts.end(), "bad age: " + age); + OgreAssert(body_parts[age].find(sex) != body_parts[age].end(), + "bad sex: " + sex); + OgreAssert(body_parts[age][sex].find(slotName) != + body_parts[age][sex].end(), + "bad slot: " + slotName); + for (auto &slots : body_parts[age][sex][slotName]) + meshes.push_back(slots.get()); +} } diff --git a/src/gamedata/CharacterModule.h b/src/gamedata/CharacterModule.h index ec78638..2b4e47d 100644 --- a/src/gamedata/CharacterModule.h +++ b/src/gamedata/CharacterModule.h @@ -1,6 +1,7 @@ #ifndef CHARACTER_MODULE_H_ #define CHARACTER_MODULE_H_ #include +#include #include #include "Components.h" namespace ECS @@ -39,23 +40,36 @@ struct CharacterModule { void updateCameraGoal(Camera &camera, Ogre::Real deltaYaw, Ogre::Real deltaPitch, Ogre::Real deltaZoom); void createCharacter(flecs::entity e, const Ogre::Vector3 &position, - const Ogre::Quaternion &rotation, const Ogre::String &faceModel, const Ogre::String &hairModel, - const Ogre::String &topModel, const Ogre::String &bottomModel, const Ogre::String &feetModel); + const Ogre::Quaternion &rotation, + const Ogre::String &faceModel, + const Ogre::String &hairModel, + const Ogre::String &topModel, + const Ogre::String &bottomModel, + const Ogre::String &feetModel); std::unordered_map characterNodes; - std::unordered_map characterEntitiesFace; - std::unordered_map characterEntitiesHair; + std::unordered_map + characterEntitiesFace; + std::unordered_map + characterEntitiesHair; std::unordered_map characterEntitiesTop; - std::unordered_map characterEntitiesBottom; - std::unordered_map characterEntitiesFeet; + std::unordered_map + characterEntitiesBottom; + std::unordered_map + characterEntitiesFeet; std::unordered_map characterModelsFace; std::unordered_map characterModelsHair; std::unordered_map characterModelsTop; std::unordered_map characterModelsBottom; std::unordered_map characterModelsFeet; std::unordered_map characterPositions; - std::unordered_map characterOrientations; + std::unordered_map + characterOrientations; + nlohmann::json body_parts; void remapMeshToMasterSkeleton(Ogre::MeshPtr clothMesh, - Ogre::MeshPtr masterMesh); + Ogre::MeshPtr masterMesh); + void getSlotMeshes(const Ogre::String &age, const Ogre::String &sex, + const Ogre::String &slotName, + std::vector &meshes); }; } #endif diff --git a/src/gamedata/PhysicsModule.cpp b/src/gamedata/PhysicsModule.cpp index f912a61..3cc1724 100644 --- a/src/gamedata/PhysicsModule.cpp +++ b/src/gamedata/PhysicsModule.cpp @@ -58,8 +58,8 @@ PhysicsModule::PhysicsModule(flecs::world &ecs) ecs.component().on_remove([](flecs::entity e, CharacterBody &body) { - JPH::Character *ch = - static_cast(body.ch.get()); + std::shared_ptr ch = + std::static_pointer_cast(body.ch); if (ch) { if (e.has()) e.remove(); diff --git a/src/gamedata/StaticGeometryModule.cpp b/src/gamedata/StaticGeometryModule.cpp index 08a7fa2..12a8c8a 100644 --- a/src/gamedata/StaticGeometryModule.cpp +++ b/src/gamedata/StaticGeometryModule.cpp @@ -13,8 +13,10 @@ #include "TerrainModule.h" #include "physics.h" #include "PhysicsModule.h" +#include "CharacterManagerModule.h" #include "items.h" #include "StaticGeometryModule.h" +#include "CharacterAIModule.h" #include namespace ECS @@ -28,6 +30,8 @@ StaticGeometryModule::StaticGeometryModule(flecs::world &ecs) { ZoneScoped; ecs.module(); + ecs.import (); + ecs.import (); ecs.component(); ecs.component(); ecs.component(); @@ -89,6 +93,19 @@ StaticGeometryModule::StaticGeometryModule(flecs::world &ecs) }); if (!Ogre::MeshLodGenerator::getSingletonPtr()) new Ogre::MeshLodGenerator(); + ecs.system("SetupTowns") + .kind(flecs::OnUpdate) + .without() + .without() + .each([&](flecs::entity e, TerrainItem &item) { + Ogre::String props = item.properties; + nlohmann::json jp = nlohmann::json::parse(props); + if (jp.find("type") == jp.end()) + return; + Ogre::String itemType = jp["type"].get(); + if (itemType == "town") + Geometry::registerTownItem(e); + }); ecs.system("AddGeometryQueue").kind(flecs::OnUpdate).run([&](flecs::iter &it) { ZoneScopedN("AddGeometryQueue"); std::list items; @@ -134,9 +151,8 @@ StaticGeometryModule::StaticGeometryModule(flecs::world &ecs) const TerrainItem &item) { items.push_back(e); }); - for (auto e : items) { + for (auto e : items) createItemGeometry(e); - } addQueue.pop_front(); } else { output.push_back(item); diff --git a/src/gamedata/items/items.cpp b/src/gamedata/items/items.cpp index 61db45d..52cde85 100644 --- a/src/gamedata/items/items.cpp +++ b/src/gamedata/items/items.cpp @@ -313,7 +313,7 @@ void createItemGeometry(flecs::entity e) e.set({ itemNode, geo }); } else if (itemType == "town") { OgreAssert(geo, "Can't create static geometry"); - createTown(e, itemNode, geo); + createTown(e, itemNode, geo); e.set({ 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); +} + } } diff --git a/src/gamedata/items/items.h b/src/gamedata/items/items.h index 9b21b59..bc0ab71 100644 --- a/src/gamedata/items/items.h +++ b/src/gamedata/items/items.h @@ -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, diff --git a/src/gamedata/items/town.cpp b/src/gamedata/items/town.cpp index 5f27d16..dcc571d 100644 --- a/src/gamedata/items/town.cpp +++ b/src/gamedata/items/town.cpp @@ -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 options; + Selector(const Ogre::String &label, + const std::vector &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 *options_m; + std::vector *options_f; + const Ogre::String &slot; + }; + static std::vector faces_a_m, hairs_a_m, tops_a_m, + bottoms_a_m, feet_a_m; + static std::vector 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().getSlotMeshes( + "adult", "male", g.slotBase, *g.options_m); + ECS::get_mut().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() == 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() == 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().c_str(), sizeof(firstName)); @@ -2173,61 +2271,77 @@ bool editNPCs(nlohmann::json &npcs) strncpy(tags, npc["tags"].get().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()); + if (npc.find("age") != npc.end()) + age.set_default(npc["age"].get()); + if (npc.find("sex") != npc.end()) + sex.selection = npc["sex"].get(); + 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()); + 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()); + 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(); - 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(); - 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() .createCharacterData( - models[sex].face, models[sex].hair, - models[sex].top, models[sex].bottom, - models[sex].feet, npcPosition, - npcOrientation); + npc["slot_face"].get(), + npc["slot_hair"].get(), + npc["slot_top"].get(), + npc["slot_bottom"].get(), + npc["slot_feet"].get(), + 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(); +#if 0 if (ECS::get() .has()) { 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().is_valid()) if (ECS::get().has()) { ECS::get_mut().createAI(e); ECS::modified(); } -} + createTownActionNodes(e); +} + +void createTownActionNodes(flecs::entity e) +{ + const TerrainItem &item = e.get(); + 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(); + if (jp.find("radius") != jp.end()) + radius = jp["radius"].get(); + 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(); + if (jb.find("depth") != jb.end()) + depth = jb["depth"].get(); + if (jb.find("width") != jb.end()) + width = jb["width"].get(); + if (jb.find("elevation") != jb.end()) + elevation = jb["elevation"].get(); + + 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 y = jfcell["y"].get(); + int z = jfcell["z"].get(); + 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(); + + 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(); + actionPosition.y = + action["position_y"] + .get(); + actionPosition.z = + action["position_z"] + .get(); + 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::ActionNode + anode; + anode.action = + action["action"] + .get(); + anode.action_text = + action["action_text"] + .get(); + anode.radius = + action["radius"] + .get(); + anode.height = + action["height"] + .get(); + 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>(); + } + } + } + } + } + } + } +} + } } diff --git a/src/gamedata/items/town.h b/src/gamedata/items/town.h index 431fa3f..1f29757 100644 --- a/src/gamedata/items/town.h +++ b/src/gamedata/items/town.h @@ -2,7 +2,8 @@ #define __TOWN_H__ #include #include -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 diff --git a/src/physics/physics.cpp b/src/physics/physics.cpp index 650217b..567542a 100644 --- a/src/physics/physics.cpp +++ b/src/physics/physics.cpp @@ -573,14 +573,13 @@ public: std::thread::hardware_concurrency() - 1) , mDebugRenderer(new DebugRenderer(scnMgr, cameraNode)) , object_vs_broadphase_layer_filter{} - , object_vs_object_layer_filter{} + , object_vs_object_layer_filter{} , debugDraw(false) { static int instanceCount = 0; OgreAssert(instanceCount == 0, "Bad initialisation"); instanceCount++; - // This is the max amount of rigid bodies that you can add to the physics system. If you try to add more you'll get an error. // Note: This value is low because this is a simple test. For a real project use something in the order of 65536. const uint cMaxBodies = 65536; @@ -1510,14 +1509,14 @@ public: { return characterBodies.find(id) != characterBodies.end(); } - void destroyCharacter(JPH::Character *ch) + void destroyCharacter(std::shared_ptr ch) { characterBodies.erase(characterBodies.find(ch->GetBodyID())); - characters.erase(ch); + characters.erase(ch.get()); Ogre::SceneNode *node = id2node[ch->GetBodyID()]; id2node.erase(ch->GetBodyID()); node2id.erase(node); - OGRE_DELETE ch; + ch = nullptr; } }; @@ -1539,17 +1538,17 @@ JoltPhysicsWrapper::JoltPhysicsWrapper(Ogre::SceneManager *scnMgr, // Install trace and assert callbacks JPH::Trace = TraceImpl; JPH_IF_ENABLE_ASSERTS(JPH::AssertFailed = AssertFailedImpl;) - // Create a factory, this class is responsible for creating instances of classes based on their name or hash and is mainly used for deserialization of saved data. // It is not directly used in this example but still required. JPH::Factory::sInstance = new JPH::Factory(); - // Register all physics types with the factory and install their collision handlers with the CollisionDispatch class. - // If you have your own custom shape types you probably need to register their handlers with the CollisionDispatch before calling this function. - // If you implement your own default material (PhysicsMaterial::sDefault) make sure to initialize it before this function or else this function will create one for you. + // Register all physics types with the factory and install their collision handlers with the CollisionDispatch class. + // If you have your own custom shape types you probably need to register their handlers with the CollisionDispatch before calling this function. + // If you implement your own default material (PhysicsMaterial::sDefault) make sure to initialize it before this function or else this function will create one for you. JPH::RegisterTypes(); - phys = std::make_unique(scnMgr, cameraNode, nullptr, &contacts); + phys = std::make_unique(scnMgr, cameraNode, nullptr, + &contacts); } JoltPhysicsWrapper::~JoltPhysicsWrapper() @@ -1824,7 +1823,7 @@ bool JoltPhysicsWrapper::bodyIsCharacter(JPH::BodyID id) const return phys->bodyIsCharacter(id); } -void JoltPhysicsWrapper::destroyCharacter(JPH::Character *ch) +void JoltPhysicsWrapper::destroyCharacter(std::shared_ptr ch) { phys->destroyCharacter(ch); } diff --git a/src/physics/physics.h b/src/physics/physics.h index 8e1a316..2faa71b 100644 --- a/src/physics/physics.h +++ b/src/physics/physics.h @@ -219,6 +219,6 @@ public: bool raycastQuery(Ogre::Vector3 startPoint, Ogre::Vector3 endPoint, Ogre::Vector3 &position, JPH::BodyID &id); bool bodyIsCharacter(JPH::BodyID id) const; - void destroyCharacter(JPH::Character *ch); + void destroyCharacter(std::shared_ptr ch); }; #endif