Lua scripts package
This commit is contained in:
@@ -365,6 +365,7 @@ target_link_libraries(editSceneEditor
|
||||
RecastNavigation::DetourTileCache
|
||||
RecastNavigation::DetourCrowd
|
||||
RecastNavigation::DebugUtils
|
||||
PackageArchive
|
||||
lua
|
||||
)
|
||||
|
||||
@@ -628,6 +629,78 @@ target_include_directories(PackageTool PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# lua-scripts.package - package all Lua scripts into a single archive
|
||||
# ---------------------------------------------------------------------------
|
||||
# Creates lua-scripts.package from the staged lua-scripts build directory.
|
||||
# Files are stored with paths relative to lua-scripts/ (the "lua-scripts"
|
||||
# directory prefix is stripped from archive paths).
|
||||
#
|
||||
# The staged directory is assembled from two sources:
|
||||
# 1. Main lua-scripts (from ${CMAKE_BINARY_DIR}/lua-scripts, staged by
|
||||
# the stage_lua_scripts target in the main build)
|
||||
# 2. EditScene-specific lua-scripts (from
|
||||
# ${CMAKE_CURRENT_SOURCE_DIR}/lua-scripts, overlaid on top)
|
||||
# The overlay ensures editScene files take precedence over main files
|
||||
# with the same name.
|
||||
set(LUA_SCRIPTS_PACKAGE "${CMAKE_CURRENT_BINARY_DIR}/lua-scripts.package")
|
||||
set(LUA_SCRIPTS_STAGED_DIR "${CMAKE_CURRENT_BINARY_DIR}/lua-scripts")
|
||||
set(LUA_SCRIPTS_EDITSCENE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/lua-scripts")
|
||||
|
||||
# Collect all source files from the main lua-scripts source directory so
|
||||
# that the package is rebuilt when any of them change.
|
||||
# CONFIGURE_DEPENDS makes CMake re-evaluate the glob at build time,
|
||||
# so newly added files are picked up without re-running cmake.
|
||||
file(GLOB_RECURSE LUA_SCRIPTS_MAIN_FILES
|
||||
CONFIGURE_DEPENDS
|
||||
"${CMAKE_SOURCE_DIR}/lua-scripts/*.lua"
|
||||
"${CMAKE_SOURCE_DIR}/lua-scripts/*.ink"
|
||||
)
|
||||
|
||||
# Collect all editScene-specific lua-scripts source files.
|
||||
file(GLOB_RECURSE LUA_SCRIPTS_EDITSCENE_FILES
|
||||
CONFIGURE_DEPENDS
|
||||
"${LUA_SCRIPTS_EDITSCENE_SRC}/*.lua"
|
||||
"${LUA_SCRIPTS_EDITSCENE_SRC}/*.ink"
|
||||
)
|
||||
|
||||
# Custom command to overlay editScene-specific lua-scripts on top of the
|
||||
# staged main lua-scripts directory. This copies files from the editScene
|
||||
# lua-scripts source directory into the staged directory, overlaying on
|
||||
# top of any files already there from the main build.
|
||||
set(LUA_SCRIPTS_OVERLAY_DONE "${CMAKE_CURRENT_BINARY_DIR}/.lua_scripts_overlay_done")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${LUA_SCRIPTS_OVERLAY_DONE}"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
"${LUA_SCRIPTS_EDITSCENE_SRC}"
|
||||
"${LUA_SCRIPTS_STAGED_DIR}"
|
||||
COMMAND ${CMAKE_COMMAND} -E touch "${LUA_SCRIPTS_OVERLAY_DONE}"
|
||||
DEPENDS stage_lua_scripts
|
||||
${LUA_SCRIPTS_EDITSCENE_FILES}
|
||||
COMMENT "Overlaying editScene lua-scripts on ${LUA_SCRIPTS_STAGED_DIR}"
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${LUA_SCRIPTS_PACKAGE}"
|
||||
COMMAND $<TARGET_FILE:PackageTool>
|
||||
create "${LUA_SCRIPTS_PACKAGE}"
|
||||
--exclude "CMakeFiles"
|
||||
--exclude "cmake_install.cmake"
|
||||
--exclude "Makefile"
|
||||
--exclude ".lua_scripts_overlay_done"
|
||||
--exclude "*.lua-"
|
||||
"${LUA_SCRIPTS_STAGED_DIR}"
|
||||
DEPENDS PackageTool
|
||||
"${LUA_SCRIPTS_OVERLAY_DONE}"
|
||||
${LUA_SCRIPTS_MAIN_FILES}
|
||||
COMMENT "Creating lua-scripts.package from ${LUA_SCRIPTS_STAGED_DIR}"
|
||||
)
|
||||
|
||||
add_custom_target(lua_scripts_package ALL
|
||||
DEPENDS "${LUA_SCRIPTS_PACKAGE}"
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Package Archive Test
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -664,6 +737,10 @@ add_custom_command(TARGET editSceneEditor POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
"${CMAKE_BINARY_DIR}/lua-scripts"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/lua-scripts"
|
||||
# Overlay editScene-specific lua-scripts on top of main lua-scripts
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
"${LUA_SCRIPTS_EDITSCENE_SRC}"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/lua-scripts"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/resources.cfg"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/resources.cfg"
|
||||
|
||||
@@ -99,6 +99,8 @@
|
||||
#include "components/Inventory.hpp"
|
||||
|
||||
#include <OgreRTShaderSystem.h>
|
||||
#include <OgreArchiveManager.h>
|
||||
#include "package/OgrePackageArchive.h"
|
||||
#include <imgui.h>
|
||||
#include "lua/LuaEntityApi.hpp"
|
||||
#include "lua/LuaComponentApi.hpp"
|
||||
@@ -268,6 +270,13 @@ EditorApp::~EditorApp()
|
||||
void EditorApp::setup()
|
||||
{
|
||||
try {
|
||||
// Register the Package archive factory before any resource
|
||||
// locations are resolved (locateResources is called inside
|
||||
// ApplicationContext::setup()).
|
||||
static Ogre::PackageArchiveFactory s_packageFactory;
|
||||
Ogre::ArchiveManager::getSingleton().addArchiveFactory(
|
||||
&s_packageFactory);
|
||||
|
||||
// Base setup
|
||||
OgreBites::ApplicationContext::setup();
|
||||
|
||||
@@ -719,12 +728,14 @@ void EditorApp::startNewGame(const Ogre::String &scenePath)
|
||||
flecs::entity playerCharacter = playerController;
|
||||
if (playerController.is_alive() &&
|
||||
playerController.has<PlayerControllerComponent>()) {
|
||||
auto &pc = playerController.get<PlayerControllerComponent>();
|
||||
auto &pc = playerController
|
||||
.get<PlayerControllerComponent>();
|
||||
if (!pc.targetCharacterName.empty()) {
|
||||
m_world.query<EntityNameComponent>().each(
|
||||
[&](flecs::entity e,
|
||||
EntityNameComponent &en) {
|
||||
if (en.name == pc.targetCharacterName)
|
||||
if (en.name ==
|
||||
pc.targetCharacterName)
|
||||
playerCharacter = e;
|
||||
});
|
||||
}
|
||||
@@ -819,12 +830,11 @@ void EditorApp::saveGame(const std::string &slotPath,
|
||||
|
||||
nlohmann::json saveData;
|
||||
saveData["version"] = "2.0";
|
||||
saveData["saveGame"] = {
|
||||
{ "baseScene", m_currentBaseScene },
|
||||
{ "timestamp", SaveLoadSystem::getCurrentTimestamp() },
|
||||
{ "slotName", slotName },
|
||||
{ "playTime", m_playTime }
|
||||
};
|
||||
saveData["saveGame"] = { { "baseScene", m_currentBaseScene },
|
||||
{ "timestamp",
|
||||
SaveLoadSystem::getCurrentTimestamp() },
|
||||
{ "slotName", slotName },
|
||||
{ "playTime", m_playTime } };
|
||||
|
||||
/* Sync spawned character positions into the registry.
|
||||
* Read from the live SceneNode because TransformComponent
|
||||
@@ -838,7 +848,8 @@ void EditorApp::saveGame(const std::string &slotPath,
|
||||
auto &t = e.get<TransformComponent>();
|
||||
if (t.node) {
|
||||
rec->position = t.node->_getDerivedPosition();
|
||||
rec->rotation = t.node->_getDerivedOrientation();
|
||||
rec->rotation =
|
||||
t.node->_getDerivedOrientation();
|
||||
} else {
|
||||
rec->position = t.position;
|
||||
rec->rotation = t.rotation;
|
||||
@@ -856,19 +867,22 @@ void EditorApp::saveGame(const std::string &slotPath,
|
||||
if (playerController.is_alive()) {
|
||||
flecs::entity playerChar = playerController;
|
||||
if (playerController.has<PlayerControllerComponent>()) {
|
||||
auto &pc = playerController.get<PlayerControllerComponent>();
|
||||
auto &pc = playerController
|
||||
.get<PlayerControllerComponent>();
|
||||
if (!pc.targetCharacterName.empty()) {
|
||||
m_world.query<EntityNameComponent>().each(
|
||||
[&](flecs::entity e,
|
||||
EntityNameComponent &en) {
|
||||
if (en.name == pc.targetCharacterName)
|
||||
if (en.name ==
|
||||
pc.targetCharacterName)
|
||||
playerChar = e;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (playerChar.has<CharacterIdentityComponent>()) {
|
||||
playerCharId = playerChar.get<
|
||||
CharacterIdentityComponent>().registryId;
|
||||
playerCharId =
|
||||
playerChar.get<CharacterIdentityComponent>()
|
||||
.registryId;
|
||||
}
|
||||
}
|
||||
saveData["saveGame"]["playerCharacterId"] = playerCharId;
|
||||
@@ -876,8 +890,7 @@ void EditorApp::saveGame(const std::string &slotPath,
|
||||
saveData["characterRegistry"] = registry.serialize();
|
||||
saveData["containerState"] =
|
||||
ContainerStateRegistry::getInstance().serialize();
|
||||
saveData["itemState"] =
|
||||
ItemStateRegistry::getInstance().serialize();
|
||||
saveData["itemState"] = ItemStateRegistry::getInstance().serialize();
|
||||
|
||||
/* Runtime entities — skip characters and player controller */
|
||||
saveData["runtimeEntities"] = nlohmann::json::array();
|
||||
@@ -895,29 +908,31 @@ void EditorApp::saveGame(const std::string &slotPath,
|
||||
/* Character runtime component overrides (restored after registry spawn) */
|
||||
saveData["characterRuntimeData"] = nlohmann::json::object();
|
||||
static const std::vector<std::string> s_runtimeKeys = {
|
||||
"character", "goapBlackboard", "goapPlanner",
|
||||
"goapRunner", "pathFollowing", "behaviorTree",
|
||||
"inventory", "actionDebug", "animationTree",
|
||||
"animationTreeTemplate"
|
||||
"character", "goapBlackboard",
|
||||
"goapPlanner", "goapRunner",
|
||||
"pathFollowing", "behaviorTree",
|
||||
"inventory", "actionDebug",
|
||||
"animationTree", "animationTreeTemplate"
|
||||
};
|
||||
m_world.query<CharacterIdentityComponent>().each(
|
||||
[&](flecs::entity e, CharacterIdentityComponent &ci) {
|
||||
nlohmann::json entityJson = serializer.serializeEntity(e);
|
||||
nlohmann::json entityJson =
|
||||
serializer.serializeEntity(e);
|
||||
nlohmann::json filtered;
|
||||
for (const auto &key : s_runtimeKeys) {
|
||||
if (entityJson.contains(key))
|
||||
filtered[key] = entityJson[key];
|
||||
}
|
||||
saveData["characterRuntimeData"][
|
||||
std::to_string(ci.registryId)] = filtered;
|
||||
saveData["characterRuntimeData"]
|
||||
[std::to_string(ci.registryId)] = filtered;
|
||||
});
|
||||
|
||||
/* Lua callback data */
|
||||
saveData["luaData"] = editScene::collectLuaSaveData(m_lua.getState());
|
||||
|
||||
if (SaveLoadSystem::writeSaveFile(slotPath, saveData)) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Game saved to: " + slotPath);
|
||||
Ogre::LogManager::getSingleton().logMessage("Game saved to: " +
|
||||
slotPath);
|
||||
EventBus::getInstance().send("game_saved");
|
||||
} else {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
@@ -988,12 +1003,10 @@ void EditorApp::loadGame(const std::string &slotPath)
|
||||
* world at save time (listed in characterRuntimeData).
|
||||
* This avoids respawning registry records that were not
|
||||
* spawned (e.g. characters from other scenes) at origin. */
|
||||
const auto &runtimeData =
|
||||
saveData.value("characterRuntimeData",
|
||||
nlohmann::json::object());
|
||||
bool filterByRuntime =
|
||||
saveData.contains("characterRuntimeData") &&
|
||||
runtimeData.is_object();
|
||||
const auto &runtimeData = saveData.value("characterRuntimeData",
|
||||
nlohmann::json::object());
|
||||
bool filterByRuntime = saveData.contains("characterRuntimeData") &&
|
||||
runtimeData.is_object();
|
||||
for (const auto &pair : registry.getCharacters()) {
|
||||
const auto &rec = pair.second;
|
||||
if (!rec.persistent)
|
||||
@@ -1013,9 +1026,8 @@ void EditorApp::loadGame(const std::string &slotPath)
|
||||
if (!spawned.is_alive())
|
||||
continue;
|
||||
serializer.deserializeEntityComponents(
|
||||
spawned, el.value(),
|
||||
flecs::entity::null(), nullptr,
|
||||
false, false, false);
|
||||
spawned, el.value(), flecs::entity::null(),
|
||||
nullptr, false, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1029,9 +1041,11 @@ void EditorApp::loadGame(const std::string &slotPath)
|
||||
if (entityJson.contains("item") &&
|
||||
entityJson["item"].is_object() &&
|
||||
entityJson["item"].contains("instanceId")) {
|
||||
std::string iid = entityJson["item"]["instanceId"];
|
||||
m_world.query<ItemComponent>()
|
||||
.each([&](flecs::entity e, ItemComponent &item) {
|
||||
std::string iid =
|
||||
entityJson["item"]["instanceId"];
|
||||
m_world.query<ItemComponent>().each(
|
||||
[&](flecs::entity e,
|
||||
ItemComponent &item) {
|
||||
if (item.instanceId == iid)
|
||||
targetEntity = e;
|
||||
});
|
||||
@@ -1043,9 +1057,9 @@ void EditorApp::loadGame(const std::string &slotPath)
|
||||
entityJson["name"].is_object() &&
|
||||
entityJson["name"].contains("name")) {
|
||||
std::string ename = entityJson["name"]["name"];
|
||||
m_world.query<EntityNameComponent>()
|
||||
.each([&](flecs::entity e,
|
||||
EntityNameComponent &nameComp) {
|
||||
m_world.query<EntityNameComponent>().each(
|
||||
[&](flecs::entity e,
|
||||
EntityNameComponent &nameComp) {
|
||||
if (nameComp.name == ename)
|
||||
targetEntity = e;
|
||||
});
|
||||
@@ -1057,30 +1071,41 @@ void EditorApp::loadGame(const std::string &slotPath)
|
||||
if (entityJson.contains("transform") &&
|
||||
entityJson["transform"].is_object()) {
|
||||
auto &t = entityJson["transform"];
|
||||
if (targetEntity.has<TransformComponent>()) {
|
||||
if (targetEntity
|
||||
.has<TransformComponent>()) {
|
||||
auto &trans = targetEntity.get_mut<
|
||||
TransformComponent>();
|
||||
if (t.contains("position")) {
|
||||
auto &p = t["position"];
|
||||
trans.position = Ogre::Vector3(
|
||||
p.value("x", 0.0f),
|
||||
p.value("y", 0.0f),
|
||||
p.value("z", 0.0f));
|
||||
p.value("x",
|
||||
0.0f),
|
||||
p.value("y",
|
||||
0.0f),
|
||||
p.value("z",
|
||||
0.0f));
|
||||
}
|
||||
if (t.contains("rotation")) {
|
||||
auto &r = t["rotation"];
|
||||
trans.rotation = Ogre::Quaternion(
|
||||
r.value("w", 1.0f),
|
||||
r.value("x", 0.0f),
|
||||
r.value("y", 0.0f),
|
||||
r.value("z", 0.0f));
|
||||
r.value("w",
|
||||
1.0f),
|
||||
r.value("x",
|
||||
0.0f),
|
||||
r.value("y",
|
||||
0.0f),
|
||||
r.value("z",
|
||||
0.0f));
|
||||
}
|
||||
if (t.contains("scale")) {
|
||||
auto &s = t["scale"];
|
||||
trans.scale = Ogre::Vector3(
|
||||
s.value("x", 1.0f),
|
||||
s.value("y", 1.0f),
|
||||
s.value("z", 1.0f));
|
||||
s.value("x",
|
||||
1.0f),
|
||||
s.value("y",
|
||||
1.0f),
|
||||
s.value("z",
|
||||
1.0f));
|
||||
}
|
||||
trans.applyToNode();
|
||||
}
|
||||
@@ -1112,19 +1137,20 @@ void EditorApp::loadGame(const std::string &slotPath)
|
||||
});
|
||||
if (playerController.is_alive() && playerCharId != 0) {
|
||||
flecs::entity playerChar;
|
||||
m_world.query<CharacterIdentityComponent>()
|
||||
.each([&](flecs::entity e,
|
||||
CharacterIdentityComponent &ci) {
|
||||
if (ci.registryId == playerCharId)
|
||||
playerChar = e;
|
||||
});
|
||||
m_world.query<CharacterIdentityComponent>().each(
|
||||
[&](flecs::entity e, CharacterIdentityComponent &ci) {
|
||||
if (ci.registryId == playerCharId)
|
||||
playerChar = e;
|
||||
});
|
||||
if (playerChar.is_alive() &&
|
||||
playerController.has<PlayerControllerComponent>()) {
|
||||
auto &pc = playerController.get_mut<
|
||||
PlayerControllerComponent>();
|
||||
auto &pc =
|
||||
playerController
|
||||
.get_mut<PlayerControllerComponent>();
|
||||
if (playerChar.has<EntityNameComponent>()) {
|
||||
pc.targetCharacterName =
|
||||
playerChar.get<EntityNameComponent>().name;
|
||||
playerChar.get<EntityNameComponent>()
|
||||
.name;
|
||||
}
|
||||
if (!playerChar.has<InventoryComponent>()) {
|
||||
playerChar.set<InventoryComponent>(
|
||||
@@ -1143,8 +1169,8 @@ void EditorApp::loadGame(const std::string &slotPath)
|
||||
m_playTime = saveData["saveGame"].value("playTime", 0.0f);
|
||||
setGamePlayState(GamePlayState::Playing);
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Game loaded from: " + slotPath);
|
||||
Ogre::LogManager::getSingleton().logMessage("Game loaded from: " +
|
||||
slotPath);
|
||||
EventBus::getInstance().send("game_loaded");
|
||||
}
|
||||
|
||||
@@ -1549,17 +1575,21 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
|
||||
flecs::entity playerCharacter = playerController;
|
||||
if (playerController.is_alive() &&
|
||||
playerController.has<PlayerControllerComponent>()) {
|
||||
auto &pc = playerController.get<PlayerControllerComponent>();
|
||||
auto &pc = playerController.get<
|
||||
PlayerControllerComponent>();
|
||||
if (!pc.targetCharacterName.empty()) {
|
||||
m_world.query<EntityNameComponent>().each(
|
||||
[&](flecs::entity e,
|
||||
EntityNameComponent &en) {
|
||||
if (en.name == pc.targetCharacterName)
|
||||
playerCharacter = e;
|
||||
if (en.name ==
|
||||
pc.targetCharacterName)
|
||||
playerCharacter =
|
||||
e;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (playerCharacter.is_valid() && playerCharacter.is_alive()) {
|
||||
if (playerCharacter.is_valid() &&
|
||||
playerCharacter.is_alive()) {
|
||||
m_characterClassSystem->toggleCharacterSheet(
|
||||
playerCharacter);
|
||||
setGamePlayState(GamePlayState::Paused);
|
||||
@@ -1814,7 +1844,7 @@ void EditorApp::locateResources()
|
||||
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
|
||||
"LuaScripts", false);
|
||||
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
|
||||
"./lua-scripts", "FileSystem", "LuaScripts", true, true);
|
||||
"./lua-scripts.package", "Package", "LuaScripts", true, true);
|
||||
|
||||
/* Try multiple relative paths for characters to handle different
|
||||
* working directories (build root vs binary subdirectory) */
|
||||
|
||||
@@ -11,17 +11,19 @@
|
||||
* Layer 1 and 2 are selected via combo boxes.
|
||||
*/
|
||||
struct SlotSelection {
|
||||
Ogre::String layer1Mesh; // "none" or exact mesh name for layer 1
|
||||
Ogre::String layer2Mesh; // "none" or exact mesh name for layer 2
|
||||
Ogre::String layer1Mesh; // "none" or exact mesh name for layer 1
|
||||
Ogre::String layer2Mesh; // "none" or exact mesh name for layer 2
|
||||
Ogre::String explicitMesh; // backward-compat override
|
||||
};
|
||||
|
||||
/**
|
||||
* Multi-slot mesh component for character parts sharing a skeleton.
|
||||
* The "face" slot (or first available slot) serves as the master skeleton.
|
||||
*
|
||||
* Age is now stored in CharacterRegistry::CharacterRecord::age
|
||||
* and should be retrieved from there when needed.
|
||||
*/
|
||||
struct CharacterSlotsComponent {
|
||||
Ogre::String age = "adult";
|
||||
Ogre::String sex = "male";
|
||||
|
||||
/* Global outfit level: 0=nude, 1=lingerie, 2=clothed */
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
print("hello!")
|
||||
|
||||
ecs.behavior_tree.register_node("luaHello", function(entity_id, params)
|
||||
local message = params.message or "Hello!"
|
||||
print("Entity " .. entity_id .. " says: " .. message)
|
||||
return "success"
|
||||
end)
|
||||
|
||||
ecs.action_db.add_action("lua_hello_action", 1,
|
||||
{}, -- no preconditions
|
||||
{}, -- no effects
|
||||
{ -- behavior tree
|
||||
type = "sequence",
|
||||
children = {
|
||||
ecs.behavior_tree.create_node("luaHello", "message=Welcome to the game!"),
|
||||
ecs.behavior_tree.create_node("setAnimationState", "main/action"),
|
||||
ecs.behavior_tree.create_node("setAnimationState", "action/sitting-ground"),
|
||||
ecs.behavior_tree.create_node("delay", "dly", "9.0"),
|
||||
ecs.behavior_tree.create_node("setAnimationState", "main/locomotion"),
|
||||
ecs.behavior_tree.create_node("setAnimationState", "locomotion/idle"),
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
ecs.subscribe_event("game_start", function(event, params)
|
||||
print("Event: " .. event)
|
||||
-- ecs.debug_crash("game_start triggered")
|
||||
ecs.subscribe_event("new_game", function(event, params)
|
||||
-- ecs.debug_crash("new_game triggered")
|
||||
local tsub = ecs.subscribe_event("scene_ready", function(event, params)
|
||||
-- ecs.unsubscribe_event(tsub)
|
||||
ecs.debug_crash("scene_ready triggered")
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
#include "components/Primitive.hpp"
|
||||
#include "components/TriangleBuffer.hpp"
|
||||
#include "components/CharacterSlots.hpp"
|
||||
#include "components/CharacterIdentity.hpp"
|
||||
#include "systems/CharacterRegistry.hpp"
|
||||
#include "components/AnimationTree.hpp"
|
||||
#include "components/AnimationTreeTemplate.hpp"
|
||||
#include "components/Character.hpp"
|
||||
@@ -491,17 +493,26 @@ static void registerAllComponents()
|
||||
// --- CharacterSlots ---
|
||||
REGISTER_COMPONENT(
|
||||
CharacterSlotsComponent, "CharacterSlots",
|
||||
lua_pushstring(L, c.age.c_str());
|
||||
lua_setfield(L, -2, "age"); lua_pushstring(L, c.sex.c_str());
|
||||
lua_setfield(L, -2, "sex");
|
||||
{ // Push: age from registry
|
||||
Ogre::String age = "adult";
|
||||
if (e.has<CharacterIdentityComponent>()) {
|
||||
auto &id = e.get<CharacterIdentityComponent>();
|
||||
const CharacterRegistry::CharacterRecord *rec =
|
||||
CharacterRegistry::getSingleton()
|
||||
.findCharacter(id.registryId);
|
||||
if (rec && !rec->age.empty())
|
||||
age = rec->age;
|
||||
}
|
||||
lua_pushstring(L, age.c_str());
|
||||
} lua_setfield(L, -2, "age");
|
||||
lua_pushstring(L, c.sex.c_str()); lua_setfield(L, -2, "sex");
|
||||
// slots map: push as table
|
||||
lua_newtable(L); for (auto &kv : c.slots) {
|
||||
lua_pushstring(L, kv.second.c_str());
|
||||
lua_setfield(L, -2, kv.first.c_str());
|
||||
} lua_setfield(L, -2, "slots");
|
||||
// slotSelections map: push as nested table
|
||||
lua_newtable(L);
|
||||
for (auto &kv : c.slotSelections) {
|
||||
lua_newtable(L); for (auto &kv : c.slotSelections) {
|
||||
lua_newtable(L);
|
||||
lua_pushstring(L, kv.second.layer1Mesh.c_str());
|
||||
lua_setfield(L, -2, "layer1Mesh");
|
||||
@@ -510,14 +521,29 @@ static void registerAllComponents()
|
||||
lua_pushstring(L, kv.second.explicitMesh.c_str());
|
||||
lua_setfield(L, -2, "explicitMesh");
|
||||
lua_setfield(L, -2, kv.first.c_str());
|
||||
}
|
||||
lua_setfield(L, -2, "slotSelections");
|
||||
} lua_setfield(L, -2, "slotSelections");
|
||||
lua_pushinteger(L, c.outfitLevel);
|
||||
lua_setfield(L, -2, "outfitLevel");
|
||||
pushVector3(L, c.frontAxis); lua_setfield(L, -2, "frontAxis");
|
||||
, if (lua_getfield(L, idx, "age"), lua_isstring(L, -1))
|
||||
c.age = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
lua_setfield(L, -2, "outfitLevel"); pushVector3(L, c.frontAxis);
|
||||
lua_setfield(L, -2, "frontAxis");
|
||||
,
|
||||
{ // Read: age into registry
|
||||
if (lua_getfield(L, idx, "age"), lua_isstring(L, -1)) {
|
||||
Ogre::String age = lua_tostring(L, -1);
|
||||
if (e.has<CharacterIdentityComponent>()) {
|
||||
auto &id = e.get<
|
||||
CharacterIdentityComponent>();
|
||||
CharacterRegistry::CharacterRecord *rec =
|
||||
CharacterRegistry::getSingleton()
|
||||
.findCharacter(
|
||||
id.registryId);
|
||||
if (rec) {
|
||||
rec->age = age;
|
||||
CharacterRegistry::getSingleton()
|
||||
.autoSave();
|
||||
}
|
||||
}
|
||||
}
|
||||
} lua_pop(L, 1);
|
||||
if (lua_getfield(L, idx, "sex"), lua_isstring(L, -1))
|
||||
c.sex = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
@@ -534,21 +560,29 @@ static void registerAllComponents()
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} lua_pop(L, 1);
|
||||
if (lua_getfield(L, idx, "slotSelections"), lua_istable(L, -1)) {
|
||||
if (lua_getfield(L, idx, "slotSelections"),
|
||||
lua_istable(L, -1)) {
|
||||
c.slotSelections.clear();
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, -2) != 0) {
|
||||
if (lua_isstring(L, -2) && lua_istable(L, -1)) {
|
||||
SlotSelection sel;
|
||||
Ogre::String slotName = lua_tostring(L, -2);
|
||||
if (lua_getfield(L, -1, "layer1Mesh"), lua_isstring(L, -1))
|
||||
sel.layer1Mesh = lua_tostring(L, -1);
|
||||
Ogre::String slotName =
|
||||
lua_tostring(L, -2);
|
||||
if (lua_getfield(L, -1, "layer1Mesh"),
|
||||
lua_isstring(L, -1))
|
||||
sel.layer1Mesh =
|
||||
lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
if (lua_getfield(L, -1, "layer2Mesh"), lua_isstring(L, -1))
|
||||
sel.layer2Mesh = lua_tostring(L, -1);
|
||||
if (lua_getfield(L, -1, "layer2Mesh"),
|
||||
lua_isstring(L, -1))
|
||||
sel.layer2Mesh =
|
||||
lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
if (lua_getfield(L, -1, "explicitMesh"), lua_isstring(L, -1))
|
||||
sel.explicitMesh = lua_tostring(L, -1);
|
||||
if (lua_getfield(L, -1, "explicitMesh"),
|
||||
lua_isstring(L, -1))
|
||||
sel.explicitMesh =
|
||||
lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
c.slotSelections[slotName] = sel;
|
||||
}
|
||||
@@ -566,12 +600,13 @@ static void registerAllComponents()
|
||||
for (auto &kv : c.weights) {
|
||||
lua_pushnumber(L, kv.second);
|
||||
lua_setfield(L, -2, kv.first.c_str());
|
||||
}
|
||||
, if (lua_istable(L, idx)) {
|
||||
},
|
||||
if (lua_istable(L, idx)) {
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, idx) != 0) {
|
||||
if (lua_isstring(L, -2) && lua_isnumber(L, -1))
|
||||
c.weights[lua_tostring(L, -2)] = lua_tonumber(L, -1);
|
||||
c.weights[lua_tostring(L, -2)] =
|
||||
lua_tonumber(L, -1);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
});
|
||||
@@ -830,8 +865,7 @@ static void registerAllComponents()
|
||||
// --- Item ---
|
||||
REGISTER_COMPONENT(
|
||||
ItemComponent, "Item", lua_pushstring(L, c.itemId.c_str());
|
||||
lua_setfield(L, -2, "itemId");
|
||||
lua_pushinteger(L, c.stackSize);
|
||||
lua_setfield(L, -2, "itemId"); lua_pushinteger(L, c.stackSize);
|
||||
lua_setfield(L, -2, "stackSize");
|
||||
lua_pushstring(L, c.action.c_str());
|
||||
lua_setfield(L, -2, "action");
|
||||
@@ -1293,7 +1327,6 @@ static void registerAllComponents()
|
||||
c.showQuit = lua_toboolean(L, -1) != 0;
|
||||
lua_pop(L, 1););
|
||||
|
||||
|
||||
// --- PlayerController ---
|
||||
REGISTER_COMPONENT(
|
||||
PlayerControllerComponent, "PlayerController",
|
||||
|
||||
@@ -46,6 +46,70 @@ static void printUsage(const char *prog)
|
||||
<< "added from subdirectories." << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a filename matches a simple glob pattern.
|
||||
* Supports '*' (matches any sequence of characters except '/').
|
||||
*/
|
||||
static bool matchesGlob(const std::string &filename, const std::string &pattern)
|
||||
{
|
||||
size_t pi = 0, fi = 0;
|
||||
while (pi < pattern.size()) {
|
||||
if (pattern[pi] == '*') {
|
||||
// '*' matches any sequence of non-'/' characters
|
||||
pi++;
|
||||
if (pi == pattern.size())
|
||||
return true; // trailing '*' matches everything
|
||||
// Find the next character in filename that matches
|
||||
// pattern[pi]
|
||||
while (fi < filename.size() && filename[fi] != '/') {
|
||||
if (filename[fi] == pattern[pi])
|
||||
break;
|
||||
fi++;
|
||||
}
|
||||
if (fi >= filename.size() || filename[fi] == '/')
|
||||
return false;
|
||||
} else {
|
||||
if (fi >= filename.size() ||
|
||||
filename[fi] != pattern[pi])
|
||||
return false;
|
||||
fi++;
|
||||
pi++;
|
||||
}
|
||||
}
|
||||
return fi == filename.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a relative path matches any of the exclude patterns.
|
||||
* Patterns can be:
|
||||
* - Prefix match: "CMakeFiles" matches "CMakeFiles/foo" or "CMakeFiles"
|
||||
* - Glob match: "*.lua-" matches "data.lua-" or "foo/bar.lua-"
|
||||
* - Exact match: "Makefile" matches "Makefile"
|
||||
*/
|
||||
static bool isExcluded(const std::string &path,
|
||||
const std::vector<std::string> &excludes)
|
||||
{
|
||||
// Extract just the filename part for glob matching
|
||||
std::string filename = path;
|
||||
auto slashPos = path.rfind('/');
|
||||
if (slashPos != std::string::npos)
|
||||
filename = path.substr(slashPos + 1);
|
||||
|
||||
for (const auto &pattern : excludes) {
|
||||
// If pattern contains a glob character, use glob matching
|
||||
if (pattern.find('*') != std::string::npos) {
|
||||
if (matchesGlob(filename, pattern))
|
||||
return true;
|
||||
} else {
|
||||
// Otherwise use prefix matching (existing behavior)
|
||||
if (path == pattern || path.find(pattern + "/") == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add files from a source path to the archive.
|
||||
* If the source is a directory, it is added recursively.
|
||||
@@ -54,7 +118,8 @@ static void printUsage(const char *prog)
|
||||
*/
|
||||
static void addPathToArchive(Ogre::PackageArchive &archive,
|
||||
const fs::path &sourcePath,
|
||||
const std::string &destPrefix = "")
|
||||
const std::string &destPrefix = "",
|
||||
const std::vector<std::string> &excludes = {})
|
||||
{
|
||||
std::error_code ec;
|
||||
|
||||
@@ -79,6 +144,14 @@ static void addPathToArchive(Ogre::PackageArchive &archive,
|
||||
c = '/';
|
||||
}
|
||||
|
||||
// Skip excluded paths
|
||||
if (isExcluded(destName, excludes)) {
|
||||
std::cout << "Skipping (excluded): "
|
||||
<< entry.path().string()
|
||||
<< std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::cout << "Adding: " << entry.path().string()
|
||||
<< " -> " << destName << std::endl;
|
||||
archive.addFileFromDisk(destName,
|
||||
@@ -100,23 +173,46 @@ static void addPathToArchive(Ogre::PackageArchive &archive,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse --exclude options from the argument list.
|
||||
* Returns the remaining non-option arguments.
|
||||
*/
|
||||
static std::vector<std::string>
|
||||
parseExcludes(const std::vector<std::string> &args,
|
||||
std::vector<std::string> &excludes)
|
||||
{
|
||||
std::vector<std::string> remaining;
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
if (args[i] == "--exclude" && i + 1 < args.size()) {
|
||||
excludes.push_back(args[i + 1]);
|
||||
i++;
|
||||
} else {
|
||||
remaining.push_back(args[i]);
|
||||
}
|
||||
}
|
||||
return remaining;
|
||||
}
|
||||
|
||||
static int cmdCreate(const std::vector<std::string> &args)
|
||||
{
|
||||
if (args.size() < 2) {
|
||||
std::vector<std::string> excludes;
|
||||
std::vector<std::string> remaining = parseExcludes(args, excludes);
|
||||
|
||||
if (remaining.size() < 2) {
|
||||
std::cerr << "Error: create requires a package path and at "
|
||||
"least one source file"
|
||||
<< std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
const std::string &packagePath = args[0];
|
||||
const std::string &packagePath = remaining[0];
|
||||
Ogre::PackageArchive archive(packagePath, "Package");
|
||||
|
||||
// Load will create the file if it doesn't exist
|
||||
archive.load();
|
||||
|
||||
for (size_t i = 1; i < args.size(); i++) {
|
||||
addPathToArchive(archive, args[i]);
|
||||
for (size_t i = 1; i < remaining.size(); i++) {
|
||||
addPathToArchive(archive, remaining[i], "", excludes);
|
||||
}
|
||||
|
||||
std::cout << "Created package: " << packagePath << " with "
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -64,14 +64,17 @@ public:
|
||||
std::unordered_map<std::string, int> currentPools;
|
||||
bool levelUpPending = false;
|
||||
|
||||
/* Age category string (e.g. "adult", "child", "elder") */
|
||||
std::string age = "adult";
|
||||
|
||||
/* Age in years (runtime progression) */
|
||||
int ageYears = 0;
|
||||
|
||||
/* Inline appearance (used when no prefab file exists) */
|
||||
std::string inlineAge = "adult";
|
||||
std::string inlineSex = "male";
|
||||
int inlineOutfitLevel = 2;
|
||||
std::unordered_map<std::string, SlotSelection> inlineSlotSelections;
|
||||
std::unordered_map<std::string, SlotSelection>
|
||||
inlineSlotSelections;
|
||||
std::unordered_map<std::string, float> inlineShapeKeyWeights;
|
||||
|
||||
/* Runtime-only characters are NOT saved to character_registry.json */
|
||||
@@ -130,12 +133,18 @@ public:
|
||||
CharacterRegistry(const CharacterRegistry &) = delete;
|
||||
CharacterRegistry &operator=(const CharacterRegistry &) = delete;
|
||||
|
||||
void setWorld(flecs::world *world) { m_world = world; }
|
||||
void setWorld(flecs::world *world)
|
||||
{
|
||||
m_world = world;
|
||||
}
|
||||
void setSceneManager(Ogre::SceneManager *sceneMgr)
|
||||
{
|
||||
m_sceneMgr = sceneMgr;
|
||||
}
|
||||
void setEditorUISystem(EditorUISystem *ui) { m_uiSystem = ui; }
|
||||
void setEditorUISystem(EditorUISystem *ui)
|
||||
{
|
||||
m_uiSystem = ui;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load registry from auto-save file. Call after setWorld/setSceneManager.
|
||||
@@ -152,7 +161,8 @@ public:
|
||||
void deleteCharacter(uint64_t id);
|
||||
CharacterRecord *findCharacter(uint64_t id);
|
||||
const CharacterRecord *findCharacter(uint64_t id) const;
|
||||
const std::unordered_map<uint64_t, CharacterRecord> &getCharacters() const
|
||||
const std::unordered_map<uint64_t, CharacterRecord> &
|
||||
getCharacters() const
|
||||
{
|
||||
return m_characters;
|
||||
}
|
||||
@@ -227,10 +237,8 @@ public:
|
||||
int &outfitLevel);
|
||||
static bool writePrefabSlots(const std::string &filepath,
|
||||
const std::string &age,
|
||||
const std::string &sex,
|
||||
int outfitLevel);
|
||||
static bool copyPrefab(const std::string &src,
|
||||
const std::string &dst);
|
||||
const std::string &sex, int outfitLevel);
|
||||
static bool copyPrefab(const std::string &src, const std::string &dst);
|
||||
static bool deletePrefabFile(const std::string &filepath);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -332,7 +340,10 @@ public:
|
||||
|
||||
bool saveToFile(const std::string &filepath);
|
||||
bool loadFromFile(const std::string &filepath);
|
||||
const std::string &getLastError() const { return m_lastError; }
|
||||
const std::string &getLastError() const
|
||||
{
|
||||
return m_lastError;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* ImGui editor */
|
||||
@@ -387,7 +398,8 @@ private:
|
||||
void removeFromIndex(uint64_t sourceId, uint64_t targetId,
|
||||
size_t relIndex);
|
||||
|
||||
std::string generatePrefabPath(uint64_t id, const std::string &firstName,
|
||||
std::string generatePrefabPath(uint64_t id,
|
||||
const std::string &firstName,
|
||||
const std::string &lastName) const;
|
||||
|
||||
/* UI state */
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "CharacterSlotSystem.hpp"
|
||||
#include "CharacterRegistry.hpp"
|
||||
#include "../components/Transform.hpp"
|
||||
#include "../components/AnimationTree.hpp"
|
||||
#include "../components/CharacterIdentity.hpp"
|
||||
#include "../components/CharacterSlots.hpp"
|
||||
#include <OgreAnimation.h>
|
||||
#include <OgreAnimationState.h>
|
||||
#include <OgreAnimationTrack.h>
|
||||
@@ -18,7 +21,7 @@ nlohmann::json CharacterSlotSystem::s_bodyParts = nlohmann::json::object();
|
||||
std::set<Ogre::String> CharacterSlotSystem::s_meshNames;
|
||||
|
||||
CharacterSlotSystem::CharacterSlotSystem(flecs::world &world,
|
||||
Ogre::SceneManager *sceneMgr)
|
||||
Ogre::SceneManager *sceneMgr)
|
||||
: m_world(world)
|
||||
, m_sceneMgr(sceneMgr)
|
||||
{
|
||||
@@ -67,7 +70,6 @@ void CharacterSlotSystem::loadCatalog()
|
||||
names->end());
|
||||
}
|
||||
|
||||
|
||||
for (const auto &name : partNames) {
|
||||
Ogre::String group = rgm.findGroupContainingResource(name);
|
||||
Ogre::DataStreamPtr stream = rgm.openResource(name, group);
|
||||
@@ -86,7 +88,8 @@ void CharacterSlotSystem::loadCatalog()
|
||||
if (!s_bodyParts.contains(age))
|
||||
s_bodyParts[age] = nlohmann::json::object();
|
||||
if (!s_bodyParts[age].contains(sex))
|
||||
s_bodyParts[age][sex] = nlohmann::json::object();
|
||||
s_bodyParts[age][sex] =
|
||||
nlohmann::json::object();
|
||||
if (!s_bodyParts[age][sex].contains(slot))
|
||||
s_bodyParts[age][sex][slot] =
|
||||
nlohmann::json::array();
|
||||
@@ -96,8 +99,8 @@ void CharacterSlotSystem::loadCatalog()
|
||||
|
||||
/* Preload mesh into Characters group */
|
||||
try {
|
||||
Ogre::MeshManager::getSingleton().load(mesh,
|
||||
"Characters");
|
||||
Ogre::MeshManager::getSingleton().load(
|
||||
mesh, "Characters");
|
||||
} catch (...) {
|
||||
}
|
||||
} catch (...) {
|
||||
@@ -105,7 +108,6 @@ void CharacterSlotSystem::loadCatalog()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
s_catalogLoaded = true;
|
||||
}
|
||||
|
||||
@@ -124,8 +126,7 @@ std::vector<Ogre::String> CharacterSlotSystem::getAges()
|
||||
return ages;
|
||||
}
|
||||
|
||||
std::vector<Ogre::String> CharacterSlotSystem::getSexes(
|
||||
const Ogre::String &age)
|
||||
std::vector<Ogre::String> CharacterSlotSystem::getSexes(const Ogre::String &age)
|
||||
{
|
||||
std::vector<Ogre::String> sexes;
|
||||
if (!s_catalogLoaded || !s_bodyParts.contains(age))
|
||||
@@ -135,8 +136,8 @@ std::vector<Ogre::String> CharacterSlotSystem::getSexes(
|
||||
return sexes;
|
||||
}
|
||||
|
||||
std::vector<Ogre::String> CharacterSlotSystem::getSlots(
|
||||
const Ogre::String &age, const Ogre::String &sex)
|
||||
std::vector<Ogre::String> CharacterSlotSystem::getSlots(const Ogre::String &age,
|
||||
const Ogre::String &sex)
|
||||
{
|
||||
std::vector<Ogre::String> slots;
|
||||
if (!s_catalogLoaded || !s_bodyParts.contains(age) ||
|
||||
@@ -147,9 +148,10 @@ std::vector<Ogre::String> CharacterSlotSystem::getSlots(
|
||||
return slots;
|
||||
}
|
||||
|
||||
std::vector<Ogre::String> CharacterSlotSystem::getMeshesForLayer(
|
||||
const Ogre::String &age, const Ogre::String &sex,
|
||||
const Ogre::String &slot, int layer)
|
||||
std::vector<Ogre::String>
|
||||
CharacterSlotSystem::getMeshesForLayer(const Ogre::String &age,
|
||||
const Ogre::String &sex,
|
||||
const Ogre::String &slot, int layer)
|
||||
{
|
||||
std::vector<Ogre::String> meshes;
|
||||
if (!s_catalogLoaded || !s_bodyParts.contains(age) ||
|
||||
@@ -165,9 +167,10 @@ std::vector<Ogre::String> CharacterSlotSystem::getMeshesForLayer(
|
||||
return meshes;
|
||||
}
|
||||
|
||||
Ogre::String CharacterSlotSystem::getMeshLabel(
|
||||
const Ogre::String &age, const Ogre::String &sex,
|
||||
const Ogre::String &slot, const Ogre::String &mesh)
|
||||
Ogre::String CharacterSlotSystem::getMeshLabel(const Ogre::String &age,
|
||||
const Ogre::String &sex,
|
||||
const Ogre::String &slot,
|
||||
const Ogre::String &mesh)
|
||||
{
|
||||
if (!s_catalogLoaded || !s_bodyParts.contains(age) ||
|
||||
!s_bodyParts[age].contains(sex) ||
|
||||
@@ -176,8 +179,8 @@ Ogre::String CharacterSlotSystem::getMeshLabel(
|
||||
|
||||
for (const auto &entry : s_bodyParts[age][sex][slot]) {
|
||||
if (entry.value("mesh", "") == mesh) {
|
||||
const auto &garments =
|
||||
entry.value("garments", nlohmann::json::array());
|
||||
const auto &garments = entry.value(
|
||||
"garments", nlohmann::json::array());
|
||||
if (garments.empty())
|
||||
return "nude";
|
||||
Ogre::String label;
|
||||
@@ -192,9 +195,9 @@ Ogre::String CharacterSlotSystem::getMeshLabel(
|
||||
return mesh;
|
||||
}
|
||||
|
||||
std::vector<Ogre::String> CharacterSlotSystem::getMeshes(
|
||||
const Ogre::String &age, const Ogre::String &sex,
|
||||
const Ogre::String &slot)
|
||||
std::vector<Ogre::String>
|
||||
CharacterSlotSystem::getMeshes(const Ogre::String &age, const Ogre::String &sex,
|
||||
const Ogre::String &slot)
|
||||
{
|
||||
std::vector<Ogre::String> meshes;
|
||||
if (!s_catalogLoaded || !s_bodyParts.contains(age) ||
|
||||
@@ -206,8 +209,9 @@ std::vector<Ogre::String> CharacterSlotSystem::getMeshes(
|
||||
return meshes;
|
||||
}
|
||||
|
||||
std::vector<Ogre::String> CharacterSlotSystem::getShapeKeyNames(
|
||||
const Ogre::String &age, const Ogre::String &sex)
|
||||
std::vector<Ogre::String>
|
||||
CharacterSlotSystem::getShapeKeyNames(const Ogre::String &age,
|
||||
const Ogre::String &sex)
|
||||
{
|
||||
std::set<Ogre::String> keySet;
|
||||
if (!s_catalogLoaded || !s_bodyParts.contains(age) ||
|
||||
@@ -216,8 +220,8 @@ std::vector<Ogre::String> CharacterSlotSystem::getShapeKeyNames(
|
||||
|
||||
for (auto &slotEl : s_bodyParts[age][sex].items()) {
|
||||
for (const auto &entry : slotEl.value()) {
|
||||
const auto &keys =
|
||||
entry.value("shape_keys", nlohmann::json::array());
|
||||
const auto &keys = entry.value("shape_keys",
|
||||
nlohmann::json::array());
|
||||
for (const auto &k : keys)
|
||||
keySet.insert(k.get<Ogre::String>());
|
||||
}
|
||||
@@ -226,9 +230,10 @@ std::vector<Ogre::String> CharacterSlotSystem::getShapeKeyNames(
|
||||
return std::vector<Ogre::String>(keySet.begin(), keySet.end());
|
||||
}
|
||||
|
||||
static std::vector<Ogre::String> garmentsForMesh(
|
||||
const Ogre::String &age, const Ogre::String &sex,
|
||||
const Ogre::String &slot, const Ogre::String &mesh)
|
||||
static std::vector<Ogre::String> garmentsForMesh(const Ogre::String &age,
|
||||
const Ogre::String &sex,
|
||||
const Ogre::String &slot,
|
||||
const Ogre::String &mesh)
|
||||
{
|
||||
if (!CharacterSlotSystem::isCatalogLoaded())
|
||||
return {};
|
||||
@@ -248,10 +253,11 @@ static std::vector<Ogre::String> garmentsForMesh(
|
||||
return {};
|
||||
}
|
||||
|
||||
Ogre::String CharacterSlotSystem::resolveMesh(
|
||||
const Ogre::String &age, const Ogre::String &sex,
|
||||
const Ogre::String &slot, const SlotSelection &sel,
|
||||
int outfitLevel)
|
||||
Ogre::String CharacterSlotSystem::resolveMesh(const Ogre::String &age,
|
||||
const Ogre::String &sex,
|
||||
const Ogre::String &slot,
|
||||
const SlotSelection &sel,
|
||||
int outfitLevel)
|
||||
{
|
||||
if (!sel.explicitMesh.empty())
|
||||
return sel.explicitMesh;
|
||||
@@ -269,8 +275,10 @@ Ogre::String CharacterSlotSystem::resolveMesh(
|
||||
/* If layer 1 is also selected, try to find a combined mesh
|
||||
* whose garments array contains both selections */
|
||||
if (sel.layer1Mesh != "none" && !sel.layer1Mesh.empty()) {
|
||||
auto l1g = garmentsForMesh(age, sex, slot, sel.layer1Mesh);
|
||||
auto l2g = garmentsForMesh(age, sex, slot, sel.layer2Mesh);
|
||||
auto l1g =
|
||||
garmentsForMesh(age, sex, slot, sel.layer1Mesh);
|
||||
auto l2g =
|
||||
garmentsForMesh(age, sex, slot, sel.layer2Mesh);
|
||||
std::set<Ogre::String> required;
|
||||
for (const auto &g : l1g)
|
||||
required.insert(g);
|
||||
@@ -279,9 +287,9 @@ Ogre::String CharacterSlotSystem::resolveMesh(
|
||||
if (!required.empty()) {
|
||||
Ogre::String combinedMesh;
|
||||
for (const auto &entry : slotEntries) {
|
||||
auto entryGarments =
|
||||
entry.value("garments",
|
||||
nlohmann::json::array());
|
||||
auto entryGarments = entry.value(
|
||||
"garments",
|
||||
nlohmann::json::array());
|
||||
std::set<Ogre::String> eg;
|
||||
for (const auto &g : entryGarments)
|
||||
eg.insert(g.get<std::string>());
|
||||
@@ -294,7 +302,8 @@ Ogre::String CharacterSlotSystem::resolveMesh(
|
||||
}
|
||||
if (containsAll) {
|
||||
Ogre::String m =
|
||||
entry["mesh"].get<Ogre::String>();
|
||||
entry["mesh"]
|
||||
.get<Ogre::String>();
|
||||
/* Prefer exact layer2 match if it already
|
||||
* satisfies the requirement */
|
||||
if (m == sel.layer2Mesh)
|
||||
@@ -332,8 +341,7 @@ Ogre::String CharacterSlotSystem::resolveMesh(
|
||||
size_t dotMesh = mesh.rfind(".mesh");
|
||||
if (dotMesh != Ogre::String::npos && dotMesh >= 4) {
|
||||
bool isDup = true;
|
||||
for (size_t i = dotMesh - 3;
|
||||
i < dotMesh; ++i) {
|
||||
for (size_t i = dotMesh - 3; i < dotMesh; ++i) {
|
||||
if (!isdigit(static_cast<unsigned char>(
|
||||
mesh[i]))) {
|
||||
isDup = false;
|
||||
@@ -343,8 +351,7 @@ Ogre::String CharacterSlotSystem::resolveMesh(
|
||||
if (isDup && mesh[dotMesh - 4] == '.')
|
||||
effectiveLen += 1000;
|
||||
}
|
||||
if (bestLayer0.empty() ||
|
||||
effectiveLen < bestLen) {
|
||||
if (bestLayer0.empty() || effectiveLen < bestLen) {
|
||||
bestLayer0 = mesh;
|
||||
bestLen = effectiveLen;
|
||||
}
|
||||
@@ -374,9 +381,10 @@ void CharacterSlotSystem::update()
|
||||
});
|
||||
}
|
||||
|
||||
static const nlohmann::json *findCatalogEntry(
|
||||
const Ogre::String &age, const Ogre::String &sex,
|
||||
const Ogre::String &slot, const Ogre::String &mesh)
|
||||
static const nlohmann::json *findCatalogEntry(const Ogre::String &age,
|
||||
const Ogre::String &sex,
|
||||
const Ogre::String &slot,
|
||||
const Ogre::String &mesh)
|
||||
{
|
||||
if (!CharacterSlotSystem::isCatalogLoaded())
|
||||
return nullptr;
|
||||
@@ -396,7 +404,7 @@ static void ensureMeshPoseAnimation(const Ogre::String &meshName)
|
||||
Ogre::MeshPtr mesh;
|
||||
try {
|
||||
mesh = Ogre::MeshManager::getSingleton().load(meshName,
|
||||
"Characters");
|
||||
"Characters");
|
||||
} catch (...) {
|
||||
return;
|
||||
}
|
||||
@@ -405,9 +413,10 @@ static void ensureMeshPoseAnimation(const Ogre::String &meshName)
|
||||
try {
|
||||
mesh->getAnimation("ShapeKeys");
|
||||
} catch (...) {
|
||||
Ogre::Animation *anim = mesh->createAnimation("ShapeKeys", 1.0f);
|
||||
Ogre::VertexAnimationTrack *track = anim->createVertexTrack(
|
||||
0, Ogre::VAT_POSE);
|
||||
Ogre::Animation *anim =
|
||||
mesh->createAnimation("ShapeKeys", 1.0f);
|
||||
Ogre::VertexAnimationTrack *track =
|
||||
anim->createVertexTrack(0, Ogre::VAT_POSE);
|
||||
Ogre::VertexPoseKeyFrame *kf =
|
||||
track->createVertexPoseKeyFrame(0.0f);
|
||||
for (size_t i = 0; i < mesh->getPoseCount(); ++i)
|
||||
@@ -415,11 +424,30 @@ static void ensureMeshPoseAnimation(const Ogre::String &meshName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: retrieve the character's age string from the registry.
|
||||
* Falls back to "adult" if no registry entry is found.
|
||||
*/
|
||||
static Ogre::String getCharacterAge(flecs::entity e)
|
||||
{
|
||||
if (e.has<CharacterIdentityComponent>()) {
|
||||
auto &id = e.get<CharacterIdentityComponent>();
|
||||
const CharacterRegistry::CharacterRecord *rec =
|
||||
CharacterRegistry::getSingleton().findCharacter(
|
||||
id.registryId);
|
||||
if (rec && !rec->age.empty())
|
||||
return rec->age;
|
||||
}
|
||||
return "adult";
|
||||
}
|
||||
|
||||
void CharacterSlotSystem::buildCharacter(flecs::entity e,
|
||||
CharacterSlotsComponent &cs)
|
||||
CharacterSlotsComponent &cs)
|
||||
{
|
||||
destroyCharacterParts(e);
|
||||
|
||||
Ogre::String age = getCharacterAge(e);
|
||||
|
||||
/* Migrate old slots map to slotSelections if needed */
|
||||
if (cs.slotSelections.empty() && !cs.slots.empty()) {
|
||||
for (const auto &pair : cs.slots) {
|
||||
@@ -431,7 +459,7 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e,
|
||||
|
||||
/* Populate default slots from catalog if still empty */
|
||||
if (cs.slotSelections.empty()) {
|
||||
auto slots = getSlots(cs.age, cs.sex);
|
||||
auto slots = getSlots(age, cs.sex);
|
||||
for (const auto &slot : slots) {
|
||||
SlotSelection sel;
|
||||
cs.slotSelections[slot] = sel;
|
||||
@@ -448,7 +476,7 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e,
|
||||
/* Determine master slot (face preferred, else first non-empty) */
|
||||
Ogre::String masterSlot;
|
||||
if (cs.slotSelections.find("face") != cs.slotSelections.end()) {
|
||||
Ogre::String mesh = resolveMesh(cs.age, cs.sex, "face",
|
||||
Ogre::String mesh = resolveMesh(age, cs.sex, "face",
|
||||
cs.slotSelections["face"],
|
||||
cs.outfitLevel);
|
||||
if (!mesh.empty())
|
||||
@@ -456,7 +484,7 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e,
|
||||
}
|
||||
if (masterSlot.empty()) {
|
||||
for (const auto &pair : cs.slotSelections) {
|
||||
Ogre::String mesh = resolveMesh(cs.age, cs.sex, pair.first,
|
||||
Ogre::String mesh = resolveMesh(age, cs.sex, pair.first,
|
||||
pair.second,
|
||||
cs.outfitLevel);
|
||||
if (!mesh.empty()) {
|
||||
@@ -469,7 +497,7 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e,
|
||||
if (masterSlot.empty())
|
||||
return;
|
||||
|
||||
Ogre::String masterMesh = resolveMesh(cs.age, cs.sex, masterSlot,
|
||||
Ogre::String masterMesh = resolveMesh(age, cs.sex, masterSlot,
|
||||
cs.slotSelections[masterSlot],
|
||||
cs.outfitLevel);
|
||||
|
||||
@@ -489,8 +517,8 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e,
|
||||
cs.masterEntity = masterEnt;
|
||||
|
||||
/* Setup pose animation for shape keys */
|
||||
const nlohmann::json *entry = findCatalogEntry(
|
||||
cs.age, cs.sex, masterSlot, masterMesh);
|
||||
const nlohmann::json *entry =
|
||||
findCatalogEntry(age, cs.sex, masterSlot, masterMesh);
|
||||
applyShapeKeys(e, masterEnt, entry);
|
||||
|
||||
/* Notify AnimationTreeSystem that entity changed */
|
||||
@@ -510,8 +538,8 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e,
|
||||
if (slot == masterSlot)
|
||||
continue;
|
||||
|
||||
Ogre::String mesh = resolveMesh(cs.age, cs.sex, slot, sel,
|
||||
cs.outfitLevel);
|
||||
Ogre::String mesh =
|
||||
resolveMesh(age, cs.sex, slot, sel, cs.outfitLevel);
|
||||
if (mesh.empty())
|
||||
continue;
|
||||
|
||||
@@ -520,12 +548,13 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e,
|
||||
Ogre::MeshPtr partMesh =
|
||||
Ogre::MeshManager::getSingleton().load(
|
||||
mesh, "Characters");
|
||||
Ogre::Entity *partEnt = m_sceneMgr->createEntity(partMesh);
|
||||
Ogre::Entity *partEnt =
|
||||
m_sceneMgr->createEntity(partMesh);
|
||||
partEnt->shareSkeletonInstanceWith(masterEnt);
|
||||
transform.node->attachObject(partEnt);
|
||||
m_entities[e.id()].parts[slot] = partEnt;
|
||||
const nlohmann::json *entry = findCatalogEntry(
|
||||
cs.age, cs.sex, slot, mesh);
|
||||
const nlohmann::json *entry =
|
||||
findCatalogEntry(age, cs.sex, slot, mesh);
|
||||
applyShapeKeys(e, partEnt, entry);
|
||||
} catch (const Ogre::Exception &ex) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
@@ -536,9 +565,8 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e,
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterSlotSystem::applyShapeKeys(flecs::entity e,
|
||||
Ogre::Entity *ent,
|
||||
const nlohmann::json *entry)
|
||||
void CharacterSlotSystem::applyShapeKeys(flecs::entity e, Ogre::Entity *ent,
|
||||
const nlohmann::json *entry)
|
||||
{
|
||||
if (!ent || !entry)
|
||||
return;
|
||||
@@ -553,8 +581,8 @@ void CharacterSlotSystem::applyShapeKeys(flecs::entity e,
|
||||
anim = mesh->getAnimation("ShapeKeys");
|
||||
} catch (...) {
|
||||
anim = mesh->createAnimation("ShapeKeys", 1.0f);
|
||||
Ogre::VertexAnimationTrack *track = anim->createVertexTrack(
|
||||
0, Ogre::VAT_POSE);
|
||||
Ogre::VertexAnimationTrack *track =
|
||||
anim->createVertexTrack(0, Ogre::VAT_POSE);
|
||||
Ogre::VertexPoseKeyFrame *kf =
|
||||
track->createVertexPoseKeyFrame(0.0f);
|
||||
for (size_t i = 0; i < mesh->getPoseCount(); ++i)
|
||||
@@ -623,7 +651,7 @@ void CharacterSlotSystem::destroyCharacterParts(flecs::entity e)
|
||||
}
|
||||
|
||||
Ogre::Entity *CharacterSlotSystem::getSlotEntity(flecs::entity e,
|
||||
const Ogre::String &slot)
|
||||
const Ogre::String &slot)
|
||||
{
|
||||
auto it = m_entities.find(e.id());
|
||||
if (it == m_entities.end())
|
||||
@@ -635,8 +663,7 @@ Ogre::Entity *CharacterSlotSystem::getSlotEntity(flecs::entity e,
|
||||
}
|
||||
|
||||
void CharacterSlotSystem::setSlotVisible(flecs::entity e,
|
||||
const Ogre::String &slot,
|
||||
bool visible)
|
||||
const Ogre::String &slot, bool visible)
|
||||
{
|
||||
Ogre::Entity *ent = getSlotEntity(e, slot);
|
||||
if (ent) {
|
||||
|
||||
@@ -2200,7 +2200,6 @@ nlohmann::json SceneSerializer::serializeCharacterSlots(flecs::entity entity)
|
||||
auto &cs = entity.get<CharacterSlotsComponent>();
|
||||
nlohmann::json json;
|
||||
|
||||
json["age"] = cs.age;
|
||||
json["sex"] = cs.sex;
|
||||
json["outfitLevel"] = cs.outfitLevel;
|
||||
json["slots"] = nlohmann::json::object();
|
||||
@@ -2228,7 +2227,6 @@ void SceneSerializer::deserializeCharacterSlots(flecs::entity entity,
|
||||
const nlohmann::json &json)
|
||||
{
|
||||
CharacterSlotsComponent cs;
|
||||
cs.age = json.value("age", "adult");
|
||||
cs.sex = json.value("sex", "male");
|
||||
cs.outfitLevel = json.value("outfitLevel", 2);
|
||||
if (json.contains("slots") && json["slots"].is_object()) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "CharacterSlotsEditor.hpp"
|
||||
#include "../systems/CharacterSlotSystem.hpp"
|
||||
#include "../systems/CharacterRegistry.hpp"
|
||||
#include "../components/CharacterIdentity.hpp"
|
||||
#include <imgui.h>
|
||||
#include <algorithm>
|
||||
|
||||
@@ -8,8 +10,9 @@ CharacterSlotsEditor::CharacterSlotsEditor(Ogre::SceneManager *sceneMgr)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool CharacterSlotsEditor::renderComponent(flecs::entity entity,
|
||||
CharacterSlotsComponent &cs)
|
||||
CharacterSlotsComponent &cs)
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
@@ -19,31 +22,27 @@ bool CharacterSlotsEditor::renderComponent(flecs::entity entity,
|
||||
|
||||
CharacterSlotSystem::loadCatalog();
|
||||
|
||||
/* Age selector */
|
||||
std::vector<Ogre::String> ages = CharacterSlotSystem::getAges();
|
||||
Ogre::String currentAge = cs.age;
|
||||
if (ImGui::BeginCombo("Age", currentAge.c_str())) {
|
||||
for (const auto &age : ages) {
|
||||
bool isSelected = (currentAge == age);
|
||||
if (ImGui::Selectable(age.c_str(), isSelected)) {
|
||||
cs.age = age;
|
||||
modified = true;
|
||||
cs.dirty = true;
|
||||
}
|
||||
if (isSelected)
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
|
||||
/* Get current age from registry */
|
||||
Ogre::String currentAge = "adult";
|
||||
if (entity.has<CharacterIdentityComponent>()) {
|
||||
auto &id = entity.get<CharacterIdentityComponent>();
|
||||
const CharacterRegistry::CharacterRecord *rec =
|
||||
CharacterRegistry::getSingleton().findCharacter(
|
||||
id.registryId);
|
||||
if (rec && !rec->age.empty())
|
||||
currentAge = rec->age;
|
||||
}
|
||||
|
||||
/* Sex selector */
|
||||
std::vector<Ogre::String> sexes =
|
||||
CharacterSlotSystem::getSexes(cs.age);
|
||||
CharacterSlotSystem::getSexes(currentAge);
|
||||
Ogre::String currentSex = cs.sex;
|
||||
if (ImGui::BeginCombo("Sex", currentSex.c_str())) {
|
||||
for (const auto &sex : sexes) {
|
||||
bool isSelected = (currentSex == sex);
|
||||
if (ImGui::Selectable(sex.c_str(), isSelected)) {
|
||||
if (ImGui::Selectable(sex.c_str(),
|
||||
isSelected)) {
|
||||
cs.sex = sex;
|
||||
modified = true;
|
||||
cs.dirty = true;
|
||||
@@ -106,21 +105,25 @@ bool CharacterSlotsEditor::renderComponent(flecs::entity entity,
|
||||
/* Shape Keys */
|
||||
if (ImGui::CollapsingHeader("Shape Keys")) {
|
||||
auto shapeKeys = CharacterSlotSystem::getShapeKeyNames(
|
||||
cs.age, cs.sex);
|
||||
currentAge, cs.sex);
|
||||
if (shapeKeys.empty()) {
|
||||
ImGui::TextDisabled("No shape keys available.");
|
||||
} else {
|
||||
CharacterShapeKeysComponent *skc = nullptr;
|
||||
if (entity.has<CharacterShapeKeysComponent>())
|
||||
skc = &entity.get_mut<CharacterShapeKeysComponent>();
|
||||
skc = &entity.get_mut<
|
||||
CharacterShapeKeysComponent>();
|
||||
else {
|
||||
entity.set<CharacterShapeKeysComponent>({});
|
||||
skc = &entity.get_mut<CharacterShapeKeysComponent>();
|
||||
entity.set<CharacterShapeKeysComponent>(
|
||||
{});
|
||||
skc = &entity.get_mut<
|
||||
CharacterShapeKeysComponent>();
|
||||
}
|
||||
|
||||
for (const auto &name : shapeKeys) {
|
||||
float val = skc->weights[name];
|
||||
if (ImGui::SliderFloat(name.c_str(), &val, 0.0f,
|
||||
if (ImGui::SliderFloat(name.c_str(),
|
||||
&val, 0.0f,
|
||||
1.0f)) {
|
||||
skc->weights[name] = val;
|
||||
skc->dirty = true;
|
||||
@@ -135,9 +138,10 @@ bool CharacterSlotsEditor::renderComponent(flecs::entity entity,
|
||||
|
||||
/* Slot selections */
|
||||
std::vector<Ogre::String> availableSlots =
|
||||
CharacterSlotSystem::getSlots(cs.age, cs.sex);
|
||||
CharacterSlotSystem::getSlots(currentAge, cs.sex);
|
||||
for (const auto &pair : cs.slotSelections) {
|
||||
if (std::find(availableSlots.begin(), availableSlots.end(),
|
||||
if (std::find(availableSlots.begin(),
|
||||
availableSlots.end(),
|
||||
pair.first) == availableSlots.end())
|
||||
availableSlots.push_back(pair.first);
|
||||
}
|
||||
@@ -145,7 +149,8 @@ bool CharacterSlotsEditor::renderComponent(flecs::entity entity,
|
||||
|
||||
for (const auto &slot : availableSlots) {
|
||||
/* Ensure selection exists */
|
||||
if (cs.slotSelections.find(slot) == cs.slotSelections.end()) {
|
||||
if (cs.slotSelections.find(slot) ==
|
||||
cs.slotSelections.end()) {
|
||||
SlotSelection sel;
|
||||
/* Migrate old explicit mesh if present */
|
||||
auto oldIt = cs.slots.find(slot);
|
||||
@@ -159,22 +164,28 @@ bool CharacterSlotsEditor::renderComponent(flecs::entity entity,
|
||||
if (ImGui::TreeNode(slot.c_str())) {
|
||||
/* Resolved mesh preview */
|
||||
Ogre::String resolved =
|
||||
CharacterSlotSystem::resolveMesh(cs.age, cs.sex,
|
||||
slot, sel,
|
||||
cs.outfitLevel);
|
||||
CharacterSlotSystem::resolveMesh(
|
||||
currentAge, cs.sex, slot, sel,
|
||||
cs.outfitLevel);
|
||||
ImGui::TextDisabled("Resolved: %s",
|
||||
resolved.empty() ? "(none)" :
|
||||
resolved.c_str());
|
||||
resolved.empty() ?
|
||||
"(none)" :
|
||||
resolved.c_str());
|
||||
|
||||
/* Explicit mesh override */
|
||||
bool useExplicit = !sel.explicitMesh.empty();
|
||||
if (ImGui::Checkbox("Lock explicit mesh", &useExplicit)) {
|
||||
if (ImGui::Checkbox("Lock explicit mesh",
|
||||
&useExplicit)) {
|
||||
if (!useExplicit) {
|
||||
sel.explicitMesh.clear();
|
||||
} else {
|
||||
/* Lock to currently resolved mesh */
|
||||
sel.explicitMesh = CharacterSlotSystem::resolveMesh(
|
||||
cs.age, cs.sex, slot, sel, cs.outfitLevel);
|
||||
sel.explicitMesh =
|
||||
CharacterSlotSystem::resolveMesh(
|
||||
currentAge,
|
||||
cs.sex, slot,
|
||||
sel,
|
||||
cs.outfitLevel);
|
||||
}
|
||||
modified = true;
|
||||
cs.dirty = true;
|
||||
@@ -183,22 +194,30 @@ bool CharacterSlotsEditor::renderComponent(flecs::entity entity,
|
||||
if (useExplicit) {
|
||||
std::vector<Ogre::String> meshes =
|
||||
CharacterSlotSystem::getMeshes(
|
||||
cs.age, cs.sex, slot);
|
||||
currentAge, cs.sex, slot);
|
||||
Ogre::String preview =
|
||||
sel.explicitMesh.empty() ?
|
||||
"(none)" :
|
||||
sel.explicitMesh;
|
||||
if (ImGui::BeginCombo("Mesh", preview.c_str())) {
|
||||
if (ImGui::Selectable("(none)",
|
||||
sel.explicitMesh.empty())) {
|
||||
if (ImGui::BeginCombo(
|
||||
"Mesh", preview.c_str())) {
|
||||
if (ImGui::Selectable(
|
||||
"(none)",
|
||||
sel.explicitMesh
|
||||
.empty())) {
|
||||
sel.explicitMesh.clear();
|
||||
modified = true;
|
||||
cs.dirty = true;
|
||||
}
|
||||
for (const auto &m : meshes) {
|
||||
bool isSelected = (sel.explicitMesh == m);
|
||||
if (ImGui::Selectable(m.c_str(), isSelected)) {
|
||||
sel.explicitMesh = m;
|
||||
bool isSelected =
|
||||
(sel.explicitMesh ==
|
||||
m);
|
||||
if (ImGui::Selectable(
|
||||
m.c_str(),
|
||||
isSelected)) {
|
||||
sel.explicitMesh =
|
||||
m;
|
||||
modified = true;
|
||||
cs.dirty = true;
|
||||
}
|
||||
@@ -208,29 +227,45 @@ bool CharacterSlotsEditor::renderComponent(flecs::entity entity,
|
||||
} else {
|
||||
/* Layer 1 combo */
|
||||
std::vector<Ogre::String> layer1Meshes =
|
||||
CharacterSlotSystem::getMeshesForLayer(
|
||||
cs.age, cs.sex, slot, 1);
|
||||
CharacterSlotSystem::
|
||||
getMeshesForLayer(
|
||||
currentAge, cs.sex,
|
||||
slot, 1);
|
||||
Ogre::String l1Preview = "none";
|
||||
if (!sel.layer1Mesh.empty())
|
||||
l1Preview = CharacterSlotSystem::getMeshLabel(
|
||||
cs.age, cs.sex, slot, sel.layer1Mesh);
|
||||
if (ImGui::BeginCombo("Lingerie (Layer 1)",
|
||||
l1Preview.c_str())) {
|
||||
if (ImGui::Selectable("none",
|
||||
sel.layer1Mesh.empty() ||
|
||||
sel.layer1Mesh == "none")) {
|
||||
l1Preview = CharacterSlotSystem::
|
||||
getMeshLabel(
|
||||
currentAge, cs.sex,
|
||||
slot,
|
||||
sel.layer1Mesh);
|
||||
if (ImGui::BeginCombo(
|
||||
"Lingerie (Layer 1)",
|
||||
l1Preview.c_str())) {
|
||||
if (ImGui::Selectable(
|
||||
"none",
|
||||
sel.layer1Mesh.empty() ||
|
||||
sel.layer1Mesh ==
|
||||
"none")) {
|
||||
sel.layer1Mesh = "none";
|
||||
modified = true;
|
||||
cs.dirty = true;
|
||||
}
|
||||
for (const auto &m : layer1Meshes) {
|
||||
Ogre::String label =
|
||||
CharacterSlotSystem::getMeshLabel(
|
||||
cs.age, cs.sex, slot, m);
|
||||
bool isSelected = (sel.layer1Mesh == m);
|
||||
if (ImGui::Selectable(label.c_str(),
|
||||
isSelected)) {
|
||||
sel.layer1Mesh = m;
|
||||
for (const auto &m :
|
||||
layer1Meshes) {
|
||||
Ogre::String label = CharacterSlotSystem::
|
||||
getMeshLabel(
|
||||
currentAge,
|
||||
cs.sex,
|
||||
slot,
|
||||
m);
|
||||
bool isSelected =
|
||||
(sel.layer1Mesh ==
|
||||
m);
|
||||
if (ImGui::Selectable(
|
||||
label.c_str(),
|
||||
isSelected)) {
|
||||
sel.layer1Mesh =
|
||||
m;
|
||||
modified = true;
|
||||
cs.dirty = true;
|
||||
}
|
||||
@@ -240,29 +275,45 @@ bool CharacterSlotsEditor::renderComponent(flecs::entity entity,
|
||||
|
||||
/* Layer 2 combo */
|
||||
std::vector<Ogre::String> layer2Meshes =
|
||||
CharacterSlotSystem::getMeshesForLayer(
|
||||
cs.age, cs.sex, slot, 2);
|
||||
CharacterSlotSystem::
|
||||
getMeshesForLayer(
|
||||
currentAge, cs.sex,
|
||||
slot, 2);
|
||||
Ogre::String l2Preview = "none";
|
||||
if (!sel.layer2Mesh.empty())
|
||||
l2Preview = CharacterSlotSystem::getMeshLabel(
|
||||
cs.age, cs.sex, slot, sel.layer2Mesh);
|
||||
if (ImGui::BeginCombo("Clothing (Layer 2)",
|
||||
l2Preview.c_str())) {
|
||||
if (ImGui::Selectable("none",
|
||||
sel.layer2Mesh.empty() ||
|
||||
sel.layer2Mesh == "none")) {
|
||||
l2Preview = CharacterSlotSystem::
|
||||
getMeshLabel(
|
||||
currentAge, cs.sex,
|
||||
slot,
|
||||
sel.layer2Mesh);
|
||||
if (ImGui::BeginCombo(
|
||||
"Clothing (Layer 2)",
|
||||
l2Preview.c_str())) {
|
||||
if (ImGui::Selectable(
|
||||
"none",
|
||||
sel.layer2Mesh.empty() ||
|
||||
sel.layer2Mesh ==
|
||||
"none")) {
|
||||
sel.layer2Mesh = "none";
|
||||
modified = true;
|
||||
cs.dirty = true;
|
||||
}
|
||||
for (const auto &m : layer2Meshes) {
|
||||
Ogre::String label =
|
||||
CharacterSlotSystem::getMeshLabel(
|
||||
cs.age, cs.sex, slot, m);
|
||||
bool isSelected = (sel.layer2Mesh == m);
|
||||
if (ImGui::Selectable(label.c_str(),
|
||||
isSelected)) {
|
||||
sel.layer2Mesh = m;
|
||||
for (const auto &m :
|
||||
layer2Meshes) {
|
||||
Ogre::String label = CharacterSlotSystem::
|
||||
getMeshLabel(
|
||||
currentAge,
|
||||
cs.sex,
|
||||
slot,
|
||||
m);
|
||||
bool isSelected =
|
||||
(sel.layer2Mesh ==
|
||||
m);
|
||||
if (ImGui::Selectable(
|
||||
label.c_str(),
|
||||
isSelected)) {
|
||||
sel.layer2Mesh =
|
||||
m;
|
||||
modified = true;
|
||||
cs.dirty = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user