diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index 02f91a44..f2013bc1 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -591,6 +591,10 @@ static void printUsage(const char* argv0) { std::printf(" List every objective on a quest (for --remove-quest-objective)\n"); std::printf(" --list-quest-rewards

[--json]\n"); std::printf(" List XP/coin/item rewards on a quest\n"); + std::printf(" --info-creature

[--json]\n"); + std::printf(" Print every field for one creature spawn (stats, behavior, AI, flags)\n"); + std::printf(" --info-quest

[--json]\n"); + std::printf(" Print every field for one quest (objectives + reward + chain in one shot)\n"); std::printf(" --info-wcp [--json]\n"); std::printf(" Print WCP archive metadata (name, files) and exit\n"); std::printf(" --list-wcp Print every file inside a WCP archive (sorted by path) and exit\n"); @@ -631,6 +635,7 @@ int main(int argc, char* argv[]) { "--info-zone", "--info-wcp", "--list-wcp", "--list-creatures", "--list-objects", "--list-quests", "--list-quest-objectives", "--list-quest-rewards", + "--info-creature", "--info-quest", "--unpack-wcp", "--pack-wcp", "--validate", "--validate-wom", "--validate-wob", "--validate-woc", "--validate-whm", "--validate-all", "--validate-glb", "--info-glb", @@ -2496,6 +2501,194 @@ int main(int argc, char* argv[]) { std::printf(" [%zu] %s\n", k, r.itemRewards[k].c_str()); } return 0; + } else if (std::strcmp(argv[i], "--info-creature") == 0 && i + 2 < argc) { + // Single-creature deep dive — every CreatureSpawn field for + // one entry. Companion to --list-creatures (which is a + // table view); useful for digging into 'why is this NPC + // not behaving like I expect?'. + std::string path = argv[++i]; + std::string idxStr = argv[++i]; + bool jsonOut = (i + 1 < argc && + std::strcmp(argv[i + 1], "--json") == 0); + if (jsonOut) i++; + int idx; + try { idx = std::stoi(idxStr); } + catch (...) { + std::fprintf(stderr, "info-creature: bad idx '%s'\n", idxStr.c_str()); + return 1; + } + wowee::editor::NpcSpawner sp; + if (!sp.loadFromFile(path)) { + std::fprintf(stderr, "info-creature: failed to load %s\n", path.c_str()); + return 1; + } + if (idx < 0 || idx >= static_cast(sp.spawnCount())) { + std::fprintf(stderr, + "info-creature: idx %d out of range [0, %zu)\n", + idx, sp.spawnCount()); + return 1; + } + const auto& s = sp.getSpawns()[idx]; + using B = wowee::editor::CreatureBehavior; + const char* behavior = + s.behavior == B::Patrol ? "patrol" : + s.behavior == B::Wander ? "wander" : "stationary"; + if (jsonOut) { + nlohmann::json j; + j["index"] = idx; + j["id"] = s.id; + j["name"] = s.name; + j["modelPath"] = s.modelPath; + j["displayId"] = s.displayId; + j["position"] = {s.position.x, s.position.y, s.position.z}; + j["orientation"] = s.orientation; + j["level"] = s.level; + j["health"] = s.health; + j["mana"] = s.mana; + j["minDamage"] = s.minDamage; + j["maxDamage"] = s.maxDamage; + j["armor"] = s.armor; + j["faction"] = s.faction; + j["scale"] = s.scale; + j["behavior"] = behavior; + j["wanderRadius"] = s.wanderRadius; + j["aggroRadius"] = s.aggroRadius; + j["leashRadius"] = s.leashRadius; + j["respawnTimeMs"] = s.respawnTimeMs; + j["patrolPoints"] = s.patrolPath.size(); + j["hostile"] = s.hostile; + j["questgiver"] = s.questgiver; + j["vendor"] = s.vendor; + j["trainer"] = s.trainer; + std::printf("%s\n", j.dump(2).c_str()); + return 0; + } + std::printf("Creature [%d] '%s'\n", idx, s.name.c_str()); + std::printf(" id : %u\n", s.id); + std::printf(" displayId : %u\n", s.displayId); + std::printf(" modelPath : %s\n", + s.modelPath.empty() ? "(uses displayId)" : s.modelPath.c_str()); + std::printf(" position : (%.2f, %.2f, %.2f)\n", + s.position.x, s.position.y, s.position.z); + std::printf(" orientation : %.2f deg\n", s.orientation); + std::printf(" scale : %.2f\n", s.scale); + std::printf(" level : %u\n", s.level); + std::printf(" health/mana : %u / %u\n", s.health, s.mana); + std::printf(" damage : %u-%u\n", s.minDamage, s.maxDamage); + std::printf(" armor : %u\n", s.armor); + std::printf(" faction : %u\n", s.faction); + std::printf(" behavior : %s\n", behavior); + std::printf(" wander rad : %.1f\n", s.wanderRadius); + std::printf(" aggro rad : %.1f\n", s.aggroRadius); + std::printf(" leash rad : %.1f\n", s.leashRadius); + std::printf(" respawn ms : %u\n", s.respawnTimeMs); + std::printf(" patrol points : %zu\n", s.patrolPath.size()); + std::printf(" flags : %s%s%s%s\n", + s.hostile ? "hostile " : "", + s.questgiver ? "questgiver " : "", + s.vendor ? "vendor " : "", + s.trainer ? "trainer " : ""); + return 0; + } else if (std::strcmp(argv[i], "--info-quest") == 0 && i + 2 < argc) { + // Single-quest deep dive — combines what --list-quest-objectives + // and --list-quest-rewards show into one view, plus the chain + // pointer + descriptions that neither covers. + std::string path = argv[++i]; + std::string idxStr = argv[++i]; + bool jsonOut = (i + 1 < argc && + std::strcmp(argv[i + 1], "--json") == 0); + if (jsonOut) i++; + int idx; + try { idx = std::stoi(idxStr); } + catch (...) { + std::fprintf(stderr, "info-quest: bad idx '%s'\n", idxStr.c_str()); + return 1; + } + wowee::editor::QuestEditor qe; + if (!qe.loadFromFile(path)) { + std::fprintf(stderr, "info-quest: failed to load %s\n", path.c_str()); + return 1; + } + if (idx < 0 || idx >= static_cast(qe.questCount())) { + std::fprintf(stderr, + "info-quest: idx %d out of range [0, %zu)\n", + idx, qe.questCount()); + return 1; + } + const auto& q = qe.getQuests()[idx]; + using OT = wowee::editor::QuestObjectiveType; + auto typeName = [](OT t) { + switch (t) { + case OT::KillCreature: return "kill"; + case OT::CollectItem: return "collect"; + case OT::TalkToNPC: return "talk"; + case OT::ExploreArea: return "explore"; + case OT::EscortNPC: return "escort"; + case OT::UseObject: return "use"; + } + return "?"; + }; + if (jsonOut) { + nlohmann::json j; + j["index"] = idx; + j["id"] = q.id; + j["title"] = q.title; + j["description"] = q.description; + j["completionText"] = q.completionText; + j["requiredLevel"] = q.requiredLevel; + j["questGiverNpcId"] = q.questGiverNpcId; + j["turnInNpcId"] = q.turnInNpcId; + j["nextQuestId"] = q.nextQuestId; + j["reward"] = { + {"xp", q.reward.xp}, + {"gold", q.reward.gold}, + {"silver", q.reward.silver}, + {"copper", q.reward.copper}, + {"items", q.reward.itemRewards} + }; + nlohmann::json objs = nlohmann::json::array(); + for (const auto& obj : q.objectives) { + objs.push_back({ + {"type", typeName(obj.type)}, + {"target", obj.targetName}, + {"count", obj.targetCount}, + {"description", obj.description} + }); + } + j["objectives"] = objs; + std::printf("%s\n", j.dump(2).c_str()); + return 0; + } + std::printf("Quest [%d] '%s'\n", idx, q.title.c_str()); + std::printf(" id : %u\n", q.id); + std::printf(" required level : %u\n", q.requiredLevel); + std::printf(" giver NPC id : %u\n", q.questGiverNpcId); + std::printf(" turn-in NPC id : %u\n", q.turnInNpcId); + std::printf(" next quest id : %u%s\n", q.nextQuestId, + q.nextQuestId == 0 ? " (terminal)" : ""); + if (!q.description.empty()) { + std::printf(" description : %s\n", q.description.c_str()); + } + if (!q.completionText.empty()) { + std::printf(" completion text : %s\n", q.completionText.c_str()); + } + std::printf(" reward : %u XP, %ug %us %uc, %zu item(s)\n", + q.reward.xp, q.reward.gold, q.reward.silver, + q.reward.copper, q.reward.itemRewards.size()); + for (size_t k = 0; k < q.reward.itemRewards.size(); ++k) { + std::printf(" item[%zu] : %s\n", k, + q.reward.itemRewards[k].c_str()); + } + std::printf(" objectives : %zu\n", q.objectives.size()); + for (size_t k = 0; k < q.objectives.size(); ++k) { + const auto& o = q.objectives[k]; + std::printf(" [%zu] %-7s ×%u %s%s%s\n", + k, typeName(o.type), o.targetCount, + o.targetName.c_str(), + o.description.empty() ? "" : " — ", + o.description.c_str()); + } + return 0; } else if (std::strcmp(argv[i], "--diff-wcp") == 0 && i + 2 < argc) { // Print which files differ between two WCP archives. Useful // when verifying that an authoring tweak only changed what