mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-06 00:53:52 +00:00
feat(editor): NPC scatter tool, adjacent tile creation, multi-tile prep
- Scatter tool: place N creatures in a radius around cursor position with random rotation and uniform disk distribution - File > Add Adjacent Tile: creates and exports a blank tile N/S/E/W of current (foundation for multi-tile zone editing) - Scatter UI: count slider (1-30), radius slider (10-200) - Scatter places all copies with same stats/behavior as template
This commit is contained in:
parent
6e24e08818
commit
48026421c9
5 changed files with 67 additions and 0 deletions
|
|
@ -603,6 +603,22 @@ void EditorApp::setGizmoAxis(TransformAxis axis) {
|
|||
viewport_.getGizmo().setTarget(sel->position, sel->scale);
|
||||
}
|
||||
|
||||
void EditorApp::addAdjacentTile(int offsetX, int offsetY) {
|
||||
if (!terrain_.isLoaded()) return;
|
||||
int newX = loadedTileX_ + offsetX;
|
||||
int newY = loadedTileY_ + offsetY;
|
||||
if (newX < 0 || newX > 63 || newY < 0 || newY > 63) return;
|
||||
|
||||
// Create a blank tile adjacent to current
|
||||
auto adj = TerrainEditor::createBlankTerrain(newX, newY, terrain_.chunks[0].position[2],
|
||||
Biome::Grassland);
|
||||
// Stitch edges: copy border heights from current terrain to adjacent
|
||||
// (This is a simplified version — full multi-tile needs a different architecture)
|
||||
LOG_INFO("Adjacent tile created at [", newX, ",", newY, "] (not yet rendered in viewport)");
|
||||
ADTWriter::write(adj, "output/" + loadedMap_ + "/" + loadedMap_ + "_" +
|
||||
std::to_string(newX) + "_" + std::to_string(newY) + ".adt");
|
||||
}
|
||||
|
||||
void EditorApp::snapSelectedToGround() {
|
||||
auto* sel = objectPlacer_.getSelected();
|
||||
if (!sel || !terrain_.isLoaded()) return;
|
||||
|
|
|
|||
|
|
@ -64,6 +64,9 @@ public:
|
|||
void startGizmoMode(TransformMode mode);
|
||||
void setGizmoAxis(TransformAxis axis);
|
||||
void snapSelectedToGround();
|
||||
|
||||
// Multi-tile support
|
||||
void addAdjacentTile(int offsetX, int offsetY);
|
||||
TransformGizmo& getGizmo() { return viewport_.getGizmo(); }
|
||||
bool shouldOpenContextMenu() const { return openContextMenu_; }
|
||||
void clearContextMenuFlag() { openContextMenu_ = false; }
|
||||
|
|
|
|||
|
|
@ -73,6 +73,13 @@ void EditorUI::renderMenuBar(EditorApp& app) {
|
|||
app.quickSave();
|
||||
if (ImGui::MenuItem("Export Zone...", nullptr, false, app.hasTerrainLoaded()))
|
||||
showSaveDialog_ = true;
|
||||
if (ImGui::BeginMenu("Add Adjacent Tile", app.hasTerrainLoaded())) {
|
||||
if (ImGui::MenuItem("North (+X)")) app.addAdjacentTile(1, 0);
|
||||
if (ImGui::MenuItem("South (-X)")) app.addAdjacentTile(-1, 0);
|
||||
if (ImGui::MenuItem("East (+Y)")) app.addAdjacentTile(0, 1);
|
||||
if (ImGui::MenuItem("West (-Y)")) app.addAdjacentTile(0, -1);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Quit", "Alt+F4")) app.requestQuit();
|
||||
ImGui::EndMenu();
|
||||
|
|
@ -537,6 +544,25 @@ void EditorUI::renderNpcPanel(EditorApp& app) {
|
|||
if (ImGui::Button("Deselect##npc")) spawner.clearSelection();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Scatter tool
|
||||
if (ImGui::CollapsingHeader("Scatter Tool")) {
|
||||
static int scatterCount = 5;
|
||||
static float scatterRadius = 50.0f;
|
||||
ImGui::SliderInt("Count", &scatterCount, 1, 30);
|
||||
ImGui::SliderFloat("Radius##scatter", &scatterRadius, 10.0f, 200.0f);
|
||||
auto& brush = app.getTerrainEditor().brush();
|
||||
if (ImGui::Button("Scatter at Cursor", ImVec2(-1, 0))) {
|
||||
if (brush.isActive() && !tmpl.modelPath.empty()) {
|
||||
spawner.scatter(tmpl, brush.getPosition(), scatterRadius, scatterCount);
|
||||
app.markObjectsDirty();
|
||||
}
|
||||
}
|
||||
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1),
|
||||
"Places %d copies in %.0f radius", scatterCount, scatterRadius);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
static char npcPath[256] = "output/creatures.json";
|
||||
ImGui::InputText("File##npc", npcPath, sizeof(npcPath));
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
|
|
@ -102,6 +103,23 @@ bool NpcSpawner::saveToFile(const std::string& path) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
void NpcSpawner::scatter(const CreatureSpawn& base, const glm::vec3& center,
|
||||
float radius, int count) {
|
||||
std::mt19937 rng(static_cast<uint32_t>(center.x * 100 + center.y * 37));
|
||||
std::uniform_real_distribution<float> distAngle(0.0f, 6.2831853f);
|
||||
std::uniform_real_distribution<float> distDist(0.0f, radius);
|
||||
std::uniform_real_distribution<float> distRot(0.0f, 360.0f);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
float angle = distAngle(rng);
|
||||
float dist = std::sqrt(distDist(rng) / radius) * radius;
|
||||
CreatureSpawn s = base;
|
||||
s.position = center + glm::vec3(std::cos(angle) * dist, std::sin(angle) * dist, 0.0f);
|
||||
s.orientation = distRot(rng);
|
||||
placeCreature(s);
|
||||
}
|
||||
}
|
||||
|
||||
bool NpcSpawner::loadFromFile(const std::string& path) {
|
||||
// Simple JSON-ish parser for our format — full JSON parsing would need a library
|
||||
LOG_INFO("NPC spawn loading not yet implemented for: ", path);
|
||||
|
|
|
|||
|
|
@ -81,6 +81,10 @@ public:
|
|||
// Template creature for placement
|
||||
CreatureSpawn& getTemplate() { return template_; }
|
||||
|
||||
// Scatter: place multiple copies in a radius around a point
|
||||
void scatter(const CreatureSpawn& base, const glm::vec3& center,
|
||||
float radius, int count);
|
||||
|
||||
private:
|
||||
uint32_t nextId();
|
||||
std::vector<CreatureSpawn> spawns_;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue