From 29b2f480d551fa8da29d5f00f2d4fc5c7daf92f0 Mon Sep 17 00:00:00 2001 From: CAtlas0784 Date: Thu, 17 Sep 2026 22:58:11 +0700 Subject: [PATCH] feat(webui): unify npc/monster spawner with categorized catalog table, realtime search, and instant Add button --- .../proxy/public/fastpatch_4229938.zip | Bin 9896 -> 9902 bytes Ananta.Server/Ananta.App/DebugApiServer.cs | 51 ++++ .../Ananta.App/DebugPanel/index.html | 270 ++++++++++++++---- .../Client4229938/NpcCatalog4229938.cs | 61 +++- 4 files changed, 318 insertions(+), 64 deletions(-) diff --git a/Ananta.Proxy/proxy/public/fastpatch_4229938.zip b/Ananta.Proxy/proxy/public/fastpatch_4229938.zip index 743f06a691c05eae6eb60ca1a6d191baa4583be6..002fa8e39a3e95dd693a10efb28565945e8a1e5e 100644 GIT binary patch delta 9685 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 V@4r?5>F84p8Dx=4>Hll^e*nqSq&5Hm delta 9694 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 diff --git a/Ananta.Server/Ananta.App/DebugApiServer.cs b/Ananta.Server/Ananta.App/DebugApiServer.cs index a5480db..8dde16f 100644 --- a/Ananta.Server/Ananta.App/DebugApiServer.cs +++ b/Ananta.Server/Ananta.App/DebugApiServer.cs @@ -141,6 +141,8 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub await WriteJsonAsync(ctx, await NpcSpawnAsync(await ReadBodyAsync(ctx.Request, token), token), token); else if (method == "GET" && path == "/api/scenes") await WriteJsonAsync(ctx, Scenes(), token); + else if (method == "GET" && path == "/api/npc/catalog") + await WriteJsonAsync(ctx, NpcCatalog(ctx.Request), token); else await WriteJsonAsync(ctx, new { ok = false, error = "unknown route" }, token, 404); } @@ -575,6 +577,55 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub }; } + private static object NpcCatalog(HttpListenerRequest req) + { + var q = req.QueryString["q"]; + var cat = req.QueryString["cat"]; + int limit = 150; + if (int.TryParse(req.QueryString["limit"], out var l) && l > 0) + limit = Math.Clamp(l, 1, 300); + + var entries = NpcCatalog4229938.Search(q, cat, limit); + + return new + { + categories = new[] + { + new { id = "all", label = "🌟 All (ทั้งหมด)" }, + new { id = "monster", label = "⚔️ Monsters & Bosses (มอนสเตอร์/บอส)" }, + new { id = "citizen", label = "🚶 Citizens (ชาวเมือง/ประชาชน)" }, + new { id = "police", label = "👮 Police & Security (ตำรวจ/ยาม)" }, + new { id = "animal", label = "🐱 Animals & Pets (สัตว์/เป็ด/แมว)" }, + new { id = "ally", label = "🤝 Allies & Story (พันธมิตร/ตัวละคร)" }, + }, + poiActions = new[] + { + new { id = 0, name = "🧍 Auto / None (ไม่ระบุท่า)" }, + new { id = 2, name = "🧍 Idle (ยืนนิ่ง)" }, + new { id = 1, name = "🚶 Walk (เดิน)" }, + new { id = 4, name = "🏃 Run (วิ่ง)" }, + new { id = 11, name = "📱 Phone / Camera (คุยโทรศัพท์/ถ่ายรูป)" }, + new { id = 10, name = "🧱 Lean on Wall (พิงกำแพง)" }, + new { id = 12, name = "🪑 Formal Sit (นั่งเก้าอี้เรียบร้อย)" }, + new { id = 13, name = "🪑 Relaxed Sit (นั่งเก้าอี้ผ่อนคลาย)" }, + new { id = 15, name = "💬 Sit & Talk (นั่งคุย)" }, + new { id = 6, name = "👏 Clapping (ปรบมือ)" }, + new { id = 5, name = "👀 Spectating (ยืนมุงดู)" }, + new { id = 3, name = "😱 Scared / Panicking (ตกใจกลัว)" }, + new { id = 8, name = "🥤 Vending Machine (กดตู้ขายน้ำ)" } + }, + items = entries.Select(e => new + { + id = e.Id, + name = string.IsNullOrWhiteSpace(e.Name) ? $"Agent_{e.Id}" : e.Name, + category = e.Category, + model = e.GeneralModelId, + camp = e.Camp, + defaultPoi = e.Category == "monster" ? 0 : 2 + }).ToList() + }; + } + private sealed class NpcSpawnRequest { public List? Items { get; set; } diff --git a/Ananta.Server/Ananta.App/DebugPanel/index.html b/Ananta.Server/Ananta.App/DebugPanel/index.html index f5e9596..b6a4de3 100644 --- a/Ananta.Server/Ananta.App/DebugPanel/index.html +++ b/Ananta.Server/Ananta.App/DebugPanel/index.html @@ -21,6 +21,17 @@ td, th { border: 1px solid #333; padding: 4px 8px; text-align: left; font-size: 13px; } .muted { color: #888; font-size: 12px; } #log { white-space: pre-wrap; font-size: 12px; max-height: 160px; overflow-y: auto; } + .npc-tab { background: #1e293b; color: #94a3b8; border: 1px solid #334155; border-radius: 4px; padding: 5px 12px; font-size: 12px; cursor: pointer; transition: 0.15s; } + .npc-tab:hover { background: #334155; color: #fff; } + .npc-tab.active { background: #00838f; color: #fff; border-color: #00e5ff; font-weight: bold; } + .btn-add-npc { background: #2e7d32; color: #fff; border: 0; border-radius: 4px; padding: 3px 8px; font-size: 11px; cursor: pointer; font-weight: bold; } + .btn-add-npc:hover { background: #388e3c; } + .badge-cat { font-size: 10px; padding: 2px 6px; border-radius: 3px; font-weight: bold; display: inline-block; } + .badge-monster { background: #5c1d1d; color: #ff8a80; border: 1px solid #b71c1c; } + .badge-citizen { background: #1a365d; color: #90cdf4; border: 1px solid #2b6cb0; } + .badge-police { background: #3e2723; color: #ffcc80; border: 1px solid #795548; } + .badge-animal { background: #1b4332; color: #95d5b2; border: 1px solid #2d6a4f; } + .badge-ally { background: #4a148c; color: #e1bee7; border: 1px solid #8e24aa; } @@ -105,17 +116,82 @@
-

👥 NPC Spawner (เสก NPC / ตัวละครเดินดิน)

-
One NPC per line: AgentConfig ID, optionally followed by a POI action ID (เช่น 40130709, 2).
- -
- - - - +

👥 NPC & Monster Spawner (เสก NPC / มอนสเตอร์ / สัตว์ / ตัวละคร)

+
มอนสเตอร์ สัตว์ และชาวเมืองในเกมใช้โครงสร้าง Agent เดียวกัน ค้นหาหรือเลือกหมวดหมู่ แล้วกด ➕ Add เพื่อเพิ่ม ID ลงในคิวเสกได้ทันที
+ + +
+ + + + + +
-
NPCs are placed in a player-relative grid in front of you.
-

+
+  
+  
+
+ +
+
+ + +
+
+ + +
+ + + + + + + + + + + + + +
หมวดIDชื่อ (Name)Model IDAction
กำลังโหลดรายชื่อ Agent...
+
+ + +
+ 📋 รายการที่จะเสก (คั่นด้วยบรรทัดใหม่: ID, POI_ID): +
+ + + + +
+
+ + +
+ + + + +
+
NPC / มอนสเตอร์จะถูกวางเป็นแถวกริดด้านหน้าตัวละครของคุณโดยอิงจากตำแหน่งและมุมหันในปัจจุบัน
+
@@ -174,36 +250,6 @@
Immediately forces client transition reset, restores user input, and rolls back coordinates to before the glitch.
-
-

Combat & Monster Spawner

-
- Monster: - - ID: -
-
- Camp: - - Count: - -
-
Spawns enemies near the player for live combat testing. Use with active weapon & combat sandbox.
-
-

🎬 Realtime 3D Cutscenes & Timelines (In-Engine 3D)

@@ -418,14 +464,129 @@ const scenePresets = { airport: { raid: 23300888, instance: 20001222, universe: 76000888, x: -4468.3, y: -6.15, z: -2597.5, facing: 85.5 } }; let npcBusy = false; +let currentNpcCategory = 'all'; +let npcCatalogData = []; +let npcSearchTimeout = null; + +async function loadNpcCatalog(category = 'all', query = '') { + try { + const q = encodeURIComponent(query || ''); + const cat = encodeURIComponent(category || 'all'); + const r = await api(`/api/npc/catalog?cat=${cat}&q=${q}&limit=150`); + if (r && r.items) { + npcCatalogData = r.items; + renderNpcTable(npcCatalogData); + if (r.poiActions && document.getElementById('npcPoiSelect').children.length <= 1) { + renderPoiSelect(r.poiActions); + } + } + } catch (err) { + console.error('Failed to load NPC catalog:', err); + } +} + +function renderPoiSelect(poiList) { + const sel = document.getElementById('npcPoiSelect'); + if (!sel) return; + sel.innerHTML = ''; + poiList.forEach(p => { + const opt = document.createElement('option'); + opt.value = p.id; + opt.textContent = `${p.name} [${p.id}]`; + if (p.id === 2) opt.selected = true; + sel.appendChild(opt); + }); +} + +function renderNpcTable(items) { + const tbody = document.getElementById('npcTableBody'); + if (!tbody) return; + if (!items || items.length === 0) { + tbody.innerHTML = 'ไม่พบ Agent ในเงื่อนไขนี้'; + return; + } + + const catBadges = { + monster: '⚔️ Monster', + citizen: '🚶 Citizen', + police: '👮 Guard', + animal: '🐱 Animal', + ally: '🤝 Ally' + }; + + tbody.innerHTML = items.map(item => { + const badge = catBadges[item.category] || 'Agent'; + const escapedName = (item.name || '').replace(/'/g, "\\'"); + return ` + ${badge} + ${item.id} + ${item.name} + ${item.model || '-'} + + + + `; + }).join(''); +} + +function setNpcCategory(cat) { + currentNpcCategory = cat; + const tabs = document.querySelectorAll('.npc-tab'); + tabs.forEach(t => t.classList.remove('active')); + const activeTab = document.querySelector(`.npc-tab[data-cat="${cat}"]`); + if (activeTab) activeTab.classList.add('active'); + const q = document.getElementById('npcSearchInput').value.trim(); + loadNpcCatalog(currentNpcCategory, q); +} + +function onNpcSearchInput() { + if (npcSearchTimeout) clearTimeout(npcSearchTimeout); + npcSearchTimeout = setTimeout(() => { + const q = document.getElementById('npcSearchInput').value.trim(); + loadNpcCatalog(currentNpcCategory, q); + }, 250); +} + +function addNpcToInput(id, name) { + const textarea = document.getElementById('npcItems'); + const poi = document.getElementById('npcPoiSelect').value || '0'; + const newLine = `${id}, ${poi}`; + const cur = textarea.value.trim(); + if (!cur) { + textarea.value = newLine; + } else { + textarea.value = cur + '\n' + newLine; + } + textarea.scrollTop = textarea.scrollHeight; + log(`Added to spawn queue: ${name} [${id}] (POI: ${poi})`); +} + +function clearNpcList() { + document.getElementById('npcItems').value = ''; +} + +function setNpcPreset(preset) { + const textarea = document.getElementById('npcItems'); + if (preset === 'gang') { + textarea.value = '40900579, 0\n40100004, 0\n40100011, 0\n40100034, 0'; + } else if (preset === 'citizens') { + textarea.value = '40130709, 2\n40130001, 1\n4065053, 11'; + } else if (preset === 'animals') { + textarea.value = '4065051, 0\n40120100, 2\n40120325, 0'; + } +} + async function spawnNpcs() { if (npcBusy) return; - const lines = document.getElementById('npcItems').value.split(/\r?\n/).map(line => line.trim()).filter(Boolean); - const items = lines.map(line => { - const parts = line.split(/[\s,;]+/); + const rawLines = document.getElementById('npcItems').value.split(/\r?\n/).map(line => line.trim()).filter(Boolean); + const items = rawLines.map(line => { + const clean = line.split('//')[0].split('#')[0].trim(); + const parts = clean.split(/[\s,;]+/); return { npcFormworkId: Number(parts[0]), poiActionId: Number(parts[1] || 0) }; - }); + }).filter(item => Number.isInteger(item.npcFormworkId) && item.npcFormworkId > 0); + const output = document.getElementById('npcResult'); + output.style.display = 'block'; if (!items.length || items.some(item => !Number.isInteger(item.npcFormworkId) || item.npcFormworkId < 1 || item.npcFormworkId > 4294967295 || !Number.isInteger(item.poiActionId) || item.poiActionId < 0 || item.poiActionId > 4294967295)) { output.textContent = 'Enter valid numeric IDs, one NPC per line.'; return; @@ -435,7 +596,7 @@ async function spawnNpcs() { const maxPerRow = Number(document.getElementById('npcMaxPerRow').value); npcBusy = true; document.getElementById('npcSpawn').disabled = true; - output.textContent = 'Spawning NPCs...'; + output.textContent = 'Spawning NPCs/Monsters into world...'; try { const r = await api('/api/npc/spawn', { method: 'POST', @@ -443,6 +604,7 @@ async function spawnNpcs() { body: JSON.stringify({ items, xSpacing: xSp, zSpacing: zSp, maxPerRow }) }); output.textContent = JSON.stringify(r, null, 2); + log('Spawned ' + items.length + ' entities: ' + (r.ok ? 'SUCCESS' : r.message)); } catch (err) { output.textContent = 'Spawn request failed: ' + err.message; } finally { @@ -560,23 +722,6 @@ async function setCustomWeather() { await setWeather(id); } -function onEnemyPresetChange() { - const sel = document.getElementById('enemypreset').value; - if (sel !== 'custom') { - document.getElementById('enemyId').value = sel; - } -} - -async function spawnEnemy() { - const body = { - enemyId: parseInt(document.getElementById('enemyId').value || '40900579', 10), - camp: parseInt(document.getElementById('enemyCamp').value || '0', 10), - count: parseInt(document.getElementById('enemyCount').value || '1', 10) - }; - const r = await api('/api/enemy/spawn', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); - log('spawn enemy: ' + JSON.stringify(r)); -} - function onTimelinePresetChange() { const sel = document.getElementById('timelinepreset').value; if (sel !== 'custom') { @@ -595,6 +740,7 @@ async function playTimeline() { loadFleet(); loadScenes(); +loadNpcCatalog(); refresh(); setInterval(refresh, 2000); document.getElementById('timeslider').addEventListener('input', e => { diff --git a/Ananta.Server/Ananta.Core/ClientData/Client4229938/NpcCatalog4229938.cs b/Ananta.Server/Ananta.Core/ClientData/Client4229938/NpcCatalog4229938.cs index 3a7f648..32ef2ea 100644 --- a/Ananta.Server/Ananta.Core/ClientData/Client4229938/NpcCatalog4229938.cs +++ b/Ananta.Server/Ananta.Core/ClientData/Client4229938/NpcCatalog4229938.cs @@ -12,7 +12,9 @@ internal sealed record NpcCatalogEntry4229938( uint BehaviorActionConfigId, uint ActionGroupId, uint AiSettingId, - uint InteractSettingId); + uint InteractSettingId, + uint Camp = 0, + string Category = "citizen"); internal static class NpcCatalog4229938 { @@ -21,6 +23,43 @@ internal static class NpcCatalog4229938 internal static bool TryGet(uint id, out NpcCatalogEntry4229938 entry) => Cache.Value.TryGetValue(id, out entry!); + internal static IReadOnlyCollection All => Cache.Value.Values; + + internal static List Search(string? query, string? category, int limit = 150) + { + var q = query?.Trim(); + var cat = category?.Trim().ToLowerInvariant(); + var hasQuery = !string.IsNullOrWhiteSpace(q); + var hasCat = !string.IsNullOrWhiteSpace(cat) && cat != "all"; + + uint queryId = 0; + if (hasQuery) uint.TryParse(q, out queryId); + + var list = new List(); + foreach (var entry in Cache.Value.Values) + { + if (hasCat && !string.Equals(entry.Category, cat, StringComparison.OrdinalIgnoreCase)) + continue; + + if (hasQuery) + { + if (queryId != 0 && entry.Id == queryId) + { + list.Add(entry); + if (list.Count >= limit) break; + continue; + } + if (!entry.Name.Contains(q!, StringComparison.OrdinalIgnoreCase) && + !entry.Id.ToString().Contains(q!)) + continue; + } + + list.Add(entry); + if (list.Count >= limit) break; + } + return list; + } + private static Dictionary Load() { var root = PrivateServerConfigStore.ResolveProjectPath(PrivateServerConfigStore.Current.Paths.ClientConfigs); @@ -59,9 +98,27 @@ internal static class NpcCatalog4229938 var animSetTag = UInt32(row, "AnimStereotype"); if (animSetTag == 0) animSetTag = UInt32(row, "BattleAnimType"); + + var camp = UInt32(row, "Camp"); + var enemyClass = UInt32(row, "EnemyClassType"); + var isAnimal = (row.TryGetProperty("AnimalGamePlay", out var ag) && ag.ValueKind == JsonValueKind.True) || + name.Contains("鸭") || name.Contains("电台") || name.Contains("猫") || name.Contains("鸽") || name.Contains("鸟") || name.Contains("犬"); + string category; + if (isAnimal || camp == 11) + category = "animal"; + else if (camp is 0 or 26 || enemyClass > 0 || name.Contains("帮") || name.Contains("怪") || name.Contains("Boss") || name.Contains("哨兵") || name.Contains("斗士") || name.Contains("无赖") || name.Contains("狂飙") || name.Contains("狂战士") || name.Contains("史莱姆")) + category = "monster"; + else if (camp is 16 or 17 or 18 or 19 or 24 || name.Contains("警") || name.Contains("守卫") || name.Contains("保镖") || name.Contains("巡卫") || name.Contains("安保")) + category = "police"; + else if (camp == 1 || name.Contains("Seymour") || name.Contains("Taffy") || name.Contains("Bansy") || name.Contains("Garm") || name.Contains("Rin") || name.Contains("HotDog")) + category = "ally"; + else + category = "citizen"; + result[id] = new(id, name, model, persona, animSetTag, UInt32(row, "Action"), UInt32(row, "ActionGroupId"), - UInt32(row, "AI"), UInt32(row, "InteractSetting")); + UInt32(row, "AI"), UInt32(row, "InteractSetting"), + camp, category); } return result; }