feat: Add metro transit, gadgets, shop, social apps, and traffic models checkpoint
This commit is contained in:
1 parent
caa42b84ed
commit
74e2963a12
79 files changed
+527962
-496
No files matched your search
@@ -175,6 +175,8 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub
|
||||
await WriteJsonAsync(ctx, ItemCatalogList(ctx.Request.QueryString), token);
|
||||
else if (method == "POST" && (path == "/api/item/give" || path == "/api/player/give-item"))
|
||||
await WriteJsonAsync(ctx, await GiveItemAsync(await ReadBodyAsync(ctx.Request, token), token), token);
|
||||
else if (method == "POST" && path == "/api/gacha/grant-tickets")
|
||||
await WriteJsonAsync(ctx, await GachaGrantTicketsAsync(token), token);
|
||||
else if (method == "POST" && (path == "/api/armory/equip" || path == "/api/weapon/equip"))
|
||||
await WriteJsonAsync(ctx, await WeaponEquipAsync(await ReadBodyAsync(ctx.Request, token), token), token);
|
||||
else
|
||||
@@ -706,26 +708,14 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub
|
||||
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 (กดตู้ขายน้ำ)" }
|
||||
},
|
||||
poiActions = PoiActionCatalog4229938.All()
|
||||
.OrderBy(e => e.Id)
|
||||
.Select(e => new { id = e.Id, name = string.IsNullOrWhiteSpace(e.Name) ? $"POI {e.Id}" : NameTranslator.PoiAction(e.Name) })
|
||||
.ToList(),
|
||||
items = entries.Select(e => new
|
||||
{
|
||||
id = e.Id,
|
||||
name = string.IsNullOrWhiteSpace(e.Name) ? $"Agent_{e.Id}" : e.Name,
|
||||
name = string.IsNullOrWhiteSpace(e.Name) ? $"Agent_{e.Id}" : NameTranslator.Agent(e.Name),
|
||||
category = e.Category,
|
||||
model = e.GeneralModelId,
|
||||
camp = e.Camp,
|
||||
@@ -1549,6 +1539,31 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub
|
||||
CityTrafficEngine.Instance.TargetDensity = Math.Clamp(den.GetInt32(), 0, 100);
|
||||
if (root.TryGetProperty("speedScale", out var spd))
|
||||
CityTrafficEngine.Instance.SpeedScale = Math.Clamp((float)spd.GetDouble(), 0.1f, 5.0f);
|
||||
if (root.TryGetProperty("moveReset", out var mr) && (mr.ValueKind == JsonValueKind.True || mr.ValueKind == JsonValueKind.False))
|
||||
{
|
||||
TrafficAndCrowdDriver.VehicleMoveReset = mr.GetBoolean();
|
||||
Console.WriteLine($"[TRAFFIC] vehicle move channel: SyncTeleportVehicle reset={TrafficAndCrowdDriver.VehicleMoveReset}");
|
||||
}
|
||||
if (root.TryGetProperty("useAetherProtocol", out var ua) && (ua.ValueKind == JsonValueKind.True || ua.ValueKind == JsonValueKind.False))
|
||||
{
|
||||
CityTrafficEngine.Instance.UseAetherProtocol = ua.GetBoolean();
|
||||
Console.WriteLine($"[TRAFFIC] protocol: {(ua.GetBoolean() ? "Aether client-driven (lane sync)" : "legacy scene-entity streaming")}");
|
||||
}
|
||||
if (root.TryGetProperty("serverVehicles", out var sv) && (sv.ValueKind == JsonValueKind.True || sv.ValueKind == JsonValueKind.False))
|
||||
{
|
||||
CityTrafficEngine.Instance.ServerSpawnedVehicles = sv.GetBoolean();
|
||||
Console.WriteLine($"[TRAFFIC] server-spawned Aether vehicles: {sv.GetBoolean()}");
|
||||
}
|
||||
if (root.TryGetProperty("ambientIntersections", out var ai) && (ai.ValueKind == JsonValueKind.True || ai.ValueKind == JsonValueKind.False))
|
||||
{
|
||||
CityTrafficEngine.Instance.AmbientIntersectionInit = ai.GetBoolean();
|
||||
Console.WriteLine($"[TRAFFIC] ambient intersection init: {ai.GetBoolean()} (takes effect on next world entry)");
|
||||
}
|
||||
if (root.TryGetProperty("timestampMode", out var tm) && tm.TryGetInt32(out var mode))
|
||||
{
|
||||
CityTrafficEngine.Instance.AetherTimestampMode = Math.Clamp(mode, 0, 2);
|
||||
Console.WriteLine($"[TRAFFIC] aether timestamp mode: {CityTrafficEngine.Instance.AetherTimestampMode} (0=unix, 1=unix×15, 2=session seconds)");
|
||||
}
|
||||
return Task.FromResult<object>(new { ok = true, status = CityTrafficEngine.Instance.GetStatus() });
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -1639,9 +1654,28 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<object> GiveItemAsync(string json, CancellationToken token)
|
||||
/// <summary>Debug panel: grants every gacha ticket item so any pool can be opened.</summary>
|
||||
private async Task<object> GachaGrantTicketsAsync(CancellationToken token)
|
||||
{
|
||||
var session = hub.Current;
|
||||
if (session is null)
|
||||
return new { ok = false, error = "no active game session" };
|
||||
|
||||
var results = new List<object>();
|
||||
var granted = 0;
|
||||
foreach (var ticketId in EconomyConfigRepository.GachaTicketItemIds())
|
||||
{
|
||||
var result = await GameRouter.PanelGiveItemAsync(session, ticketId, 99, false);
|
||||
if (result.Ok)
|
||||
granted++;
|
||||
results.Add(new { ticketId, ok = result.Ok, message = result.Message });
|
||||
}
|
||||
|
||||
return new { ok = granted > 0, granted, results };
|
||||
}
|
||||
|
||||
private async Task<object> GiveItemAsync(string json, CancellationToken token)
|
||||
{ var session = hub.Current;
|
||||
if (session is null)
|
||||
return new { ok = false, error = "no live game session (is the client in the world?)" };
|
||||
|
||||
|
||||
@@ -1,43 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Debug Panel — vehicles</title>
|
||||
<style>
|
||||
body { font-family: Consolas, monospace; background: #14161a; color: #d7dce2; max-width: 860px; margin: 24px auto; padding: 0 16px; }
|
||||
h1 { font-size: 20px; }
|
||||
h2 { font-size: 15px; color: #8fb8ff; border-bottom: 1px solid #333; padding-bottom: 4px; }
|
||||
.card { background: #1d2026; border: 1px solid #333; border-radius: 8px; padding: 12px 16px; margin: 12px 0; }
|
||||
.dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: #666; margin-right: 6px; }
|
||||
.dot.on { background: #4caf50; }
|
||||
button { background: #2b6cb0; color: #fff; border: 0; border-radius: 6px; padding: 8px 14px; margin: 4px 4px 4px 0; cursor: pointer; font-family: inherit; }
|
||||
button:hover { background: #3884d6; }
|
||||
button.ghost { background: #3a3f47; }
|
||||
input, select { background: #101215; color: #d7dce2; border: 1px solid #444; border-radius: 6px; padding: 7px 9px; margin: 2px; font-family: inherit; }
|
||||
/* ===== design tokens ported from PrivateServer1 (Drmk) WebUI ===== */
|
||||
:root {
|
||||
--bg-page: #dbe0e7; --bg-canvas: #dde2e9; --bg-header: rgba(238,242,247,0.92);
|
||||
--bg-card: rgba(248,250,253,0.82); --bg-card-elevated: #ffffff; --bg-card-sub: rgba(230,236,243,0.6);
|
||||
--bg-input: rgba(255,255,255,0.9);
|
||||
--pill-active-bg: #23272e; --pill-active-text: #fff;
|
||||
--pill-inactive-bg: rgba(168,179,195,0.28); --pill-inactive-text: #3e4756; --pill-hover-bg: rgba(168,179,195,0.42);
|
||||
--btn-primary-bg: #22262e; --btn-primary-hover: #111419; --btn-primary-text: #fff;
|
||||
--btn-secondary-bg: rgba(160,173,190,0.28); --btn-secondary-hover: rgba(160,173,190,0.42); --btn-secondary-text: #22262e;
|
||||
--text-main: #181c23; --text-2: #3a4352; --text-3: #5f6b7c; --text-4: #8a96a6;
|
||||
--border-subtle: rgba(160,175,195,0.35); --border-card: rgba(255,255,255,0.8); --border-input: rgba(160,175,195,0.5);
|
||||
--accent-blue: #2b66ff; --accent-glow: rgba(43,102,255,0.35);
|
||||
--red: #ef4444; --green: #10b981;
|
||||
--font-game: 'Bahnschrift','PingFang SC','Microsoft YaHei','Segoe UI',-apple-system,BlinkMacSystemFont,Roboto,sans-serif;
|
||||
}
|
||||
html[data-theme="dark"] {
|
||||
--bg-page: #101319; --bg-canvas: #12161f; --bg-header: rgba(20,25,34,0.92);
|
||||
--bg-card: rgba(26,32,43,0.85); --bg-card-elevated: #1b212c; --bg-card-sub: rgba(38,46,60,0.5);
|
||||
--bg-input: rgba(16,20,28,0.9);
|
||||
--pill-active-bg: #e8ecf2; --pill-active-text: #161a21;
|
||||
--pill-inactive-bg: rgba(120,134,156,0.22); --pill-inactive-text: #aab6c6; --pill-hover-bg: rgba(120,134,156,0.34);
|
||||
--btn-primary-bg: #e8ecf2; --btn-primary-hover: #ffffff; --btn-primary-text: #161a21;
|
||||
--btn-secondary-bg: rgba(120,134,156,0.22); --btn-secondary-hover: rgba(120,134,156,0.34); --btn-secondary-text: #d5dde8;
|
||||
--text-main: #e8ecf2; --text-2: #c3ccd9; --text-3: #94a1b3; --text-4: #6b7889;
|
||||
--border-subtle: rgba(120,134,156,0.30); --border-card: rgba(255,255,255,0.06); --border-input: rgba(120,134,156,0.45);
|
||||
}
|
||||
body { font-family: var(--font-game); background: var(--bg-canvas); color: var(--text-main);
|
||||
max-width: 1180px; margin: 0 auto; padding: 78px 18px 40px; }
|
||||
.game-header { position: fixed; top: 0; left: 0; right: 0; z-index: 100; height: 50px;
|
||||
display: flex; align-items: center; justify-content: space-between; padding: 0 16px;
|
||||
background: var(--bg-header); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px);
|
||||
border-bottom: 1px solid var(--border-subtle); }
|
||||
.ananta-svg-wrap svg, .ananta-svg-wrap img { height: 26px; display: block; color: var(--text-main); }
|
||||
.theme-toggle { height: 30px; padding: 0 10px; border-radius: 6px; border: 1px solid var(--border-subtle);
|
||||
background: var(--btn-secondary-bg); color: var(--btn-secondary-text); cursor: pointer; font-size: 14px; margin-left: auto; }
|
||||
h1 { font-size: 16px; font-weight: 700; color: var(--text-main); margin: 6px 0 12px; letter-spacing: .3px; }
|
||||
h2 { font-size: 13.5px; font-weight: 700; color: var(--text-2); text-transform: uppercase; letter-spacing: .8px;
|
||||
border-bottom: 1px solid var(--border-subtle); padding-bottom: 6px; margin: 4px 0 12px; }
|
||||
.card { background: var(--bg-card); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--border-card); border-radius: 10px; padding: 16px 18px; margin: 14px 0;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.04); }
|
||||
button { height: 34px; padding: 0 15px; border-radius: 6px; border: 1px solid var(--border-subtle);
|
||||
background: var(--btn-secondary-bg); color: var(--btn-secondary-text); font-size: 13px; font-weight: 600;
|
||||
font-family: var(--font-game); cursor: pointer; margin: 3px 4px 3px 0; transition: background .15s ease; }
|
||||
button:hover { background: var(--btn-secondary-hover); }
|
||||
button.ghost { background: var(--btn-primary-bg); color: var(--btn-primary-text); border-color: rgba(0,0,0,0.12); }
|
||||
button.ghost:hover { background: var(--btn-primary-hover); }
|
||||
.btn-add-npc { height: 24px; padding: 0 10px; font-size: 11px; border-radius: 5px;
|
||||
background: var(--btn-primary-bg); color: var(--btn-primary-text); }
|
||||
.btn-add-npc:hover { background: var(--btn-primary-hover); }
|
||||
input, select, textarea { background: var(--bg-input); color: var(--text-main); border: 1px solid var(--border-input);
|
||||
border-radius: 6px; padding: 7px 10px; margin: 2px; font-family: var(--font-game); font-size: 13px; }
|
||||
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent-blue);
|
||||
box-shadow: 0 0 0 3px var(--accent-glow); }
|
||||
input[type=number] { width: 110px; }
|
||||
input[type=range] { width: 100%; margin: 10px 0 2px 0; accent-color: #ffb74d; cursor: pointer; }
|
||||
#clock { font-size: 30px; letter-spacing: 2px; color: #ffd88a; }
|
||||
input[type=range] { width: 100%; margin: 10px 0 2px; accent-color: var(--accent-blue); cursor: pointer; height: auto; border: none; padding: 0; background: transparent; }
|
||||
input[type=checkbox] { accent-color: var(--accent-blue); }
|
||||
#clock { font-size: 30px; letter-spacing: 2px; color: var(--text-main); font-weight: 700; }
|
||||
.dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: #888; margin-right: 6px; }
|
||||
.dot.on { background: var(--green); box-shadow: 0 0 8px rgba(16,185,129,0.6); }
|
||||
table { border-collapse: collapse; width: 100%; margin-top: 8px; }
|
||||
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; }
|
||||
.picklist { max-height: 180px; overflow-y: auto; border: 1px solid #333; border-radius: 6px; padding: 6px; margin-top: 6px; background: #101215; }
|
||||
td, th { border: none; border-bottom: 1px solid var(--border-subtle); padding: 6px 8px; text-align: left; font-size: 12.5px; }
|
||||
tr:hover td { background: var(--bg-card-sub); }
|
||||
.muted { color: var(--text-3); font-size: 12px; }
|
||||
#log { white-space: pre-wrap; font-size: 12px; max-height: 160px; overflow-y: auto; font-family: 'Cascadia Mono', Consolas, monospace; }
|
||||
.npc-tab { height: 28px; padding: 0 12px; border-radius: 999px; border: 1px solid transparent;
|
||||
background: var(--pill-inactive-bg); color: var(--pill-inactive-text); font-size: 12px; font-weight: 600;
|
||||
font-family: var(--font-game); cursor: pointer; transition: .15s; }
|
||||
.npc-tab:hover { background: var(--pill-hover-bg); }
|
||||
.npc-tab.active { background: var(--pill-active-bg); color: var(--pill-active-text); font-weight: bold; }
|
||||
.badge-cat { font-size: 10px; padding: 2px 7px; border-radius: 999px; font-weight: bold; display: inline-block; }
|
||||
.badge-monster { background: rgba(239,68,68,0.14); color: #ff8a80; border: 1px solid rgba(239,68,68,0.4); }
|
||||
.badge-citizen { background: rgba(43,102,255,0.12); color: #90cdf4; border: 1px solid rgba(43,102,255,0.4); }
|
||||
.badge-police { background: rgba(183,149,11,0.14); color: #ffcc80; border: 1px solid rgba(183,149,11,0.4); }
|
||||
.badge-animal { background: rgba(16,185,129,0.12); color: #95d5b2; border: 1px solid rgba(16,185,129,0.4); }
|
||||
.badge-ally { background: rgba(142,36,170,0.14); color: #e1bee7; border: 1px solid rgba(142,36,170,0.4); }
|
||||
.picklist { max-height: 180px; overflow-y: auto; border: 1px solid var(--border-subtle); border-radius: 8px;
|
||||
padding: 6px; margin-top: 6px; background: var(--bg-input); }
|
||||
.picklist label { display: block; padding: 3px 0; font-size: 12px; cursor: pointer; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1><span id="dot" class="dot"></span>Vehicle debug</h1>
|
||||
<header class="game-header">
|
||||
<div style="display:flex;align-items:center"><div class="ananta-svg-wrap" title="ANANTA">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 118" fill="currentColor" fill-rule="evenodd" class="ananta-svg-logo">
|
||||
<path d="M 386 2 L 388 11 L 390 13 L 390 16 L 393 25 L 395 27 L 398 39 L 400 41 L 401 47 L 405 54 L 406 60 L 411 71 L 411 74 L 413 76 L 416 88 L 418 90 L 422 104 L 426 114 L 428 116 L 455 115 L 445 94 L 445 92 L 447 90 L 457 90 L 461 94 L 475 116 L 510 116 L 510 114 L 502 104 L 501 101 L 473 64 L 472 61 L 439 17 L 433 7 L 428 2 Z M 416 26 L 418 25 L 423 26 L 432 42 L 437 48 L 437 50 L 440 53 L 440 55 L 443 58 L 444 61 L 442 63 L 434 63 L 432 61 Z M 333 2 L 335 7 L 336 17 L 338 20 L 338 24 L 340 29 L 349 29 L 353 33 L 354 40 L 356 42 L 357 50 L 361 59 L 362 68 L 364 70 L 367 85 L 369 87 L 370 95 L 372 98 L 372 102 L 374 104 L 375 112 L 377 116 L 406 116 L 405 108 L 403 106 L 403 102 L 400 93 L 398 91 L 395 79 L 393 76 L 387 58 L 387 55 L 385 52 L 385 49 L 379 34 L 379 32 L 382 29 L 388 29 L 387 23 L 385 21 L 382 8 L 380 6 L 379 2 Z M 260 2 L 260 16 L 262 24 L 263 46 L 265 51 L 266 77 L 268 80 L 269 105 L 271 109 L 272 116 L 299 116 L 298 100 L 296 96 L 296 88 L 294 84 L 293 69 L 291 65 L 292 62 L 294 61 L 297 64 L 297 66 L 299 68 L 299 70 L 307 84 L 307 86 L 310 90 L 310 92 L 312 94 L 312 96 L 315 100 L 321 114 L 323 116 L 356 116 L 353 101 L 351 98 L 350 89 L 346 79 L 345 70 L 341 60 L 340 51 L 338 49 L 338 44 L 336 41 L 335 32 L 333 30 L 333 25 L 330 18 L 330 13 L 328 11 L 328 7 L 326 2 L 304 2 L 305 10 L 307 13 L 307 18 L 309 21 L 310 31 L 312 34 L 312 39 L 310 41 L 308 40 L 291 10 L 291 8 L 286 2 Z M 210 2 L 208 7 L 208 12 L 206 15 L 205 24 L 203 27 L 203 32 L 201 34 L 200 44 L 198 47 L 198 52 L 196 55 L 195 65 L 193 67 L 193 72 L 190 80 L 190 85 L 188 87 L 187 97 L 185 100 L 185 105 L 183 107 L 182 116 L 211 116 L 213 111 L 213 105 L 215 101 L 215 95 L 217 90 L 232 90 L 234 92 L 234 116 L 264 115 L 262 98 L 262 84 L 260 80 L 260 67 L 258 61 L 257 34 L 255 31 L 255 18 L 253 12 L 252 2 Z M 228 25 L 232 25 L 233 27 L 233 61 L 231 63 L 221 62 L 222 51 L 224 47 L 224 40 L 226 37 L 226 30 Z M 202 2 L 179 3 L 177 8 L 177 12 L 175 14 L 172 30 L 170 32 L 169 40 L 166 42 L 164 40 L 164 22 L 162 2 L 135 2 L 133 4 L 130 15 L 128 17 L 125 28 L 123 30 L 120 39 L 120 42 L 112 61 L 112 64 L 110 66 L 109 72 L 105 79 L 104 85 L 99 96 L 99 99 L 97 101 L 94 113 L 92 115 L 125 115 L 127 111 L 128 104 L 130 102 L 130 98 L 132 95 L 133 88 L 135 86 L 135 82 L 138 76 L 138 72 L 141 64 L 143 62 L 146 63 L 146 78 L 148 86 L 148 109 L 150 116 L 175 116 L 177 110 L 177 105 L 179 103 L 180 93 L 182 90 L 182 85 L 184 82 L 185 72 L 187 69 L 190 52 L 192 49 L 193 39 L 195 36 L 198 19 L 200 16 Z M 126 2 L 84 2 L 2 115 L 38 116 L 42 112 L 42 110 L 47 104 L 47 102 L 56 90 L 66 90 L 68 93 L 60 109 L 59 116 L 86 115 L 94 94 L 94 91 L 96 89 L 97 83 L 101 75 L 102 69 L 104 67 L 105 61 L 109 53 L 110 47 L 112 45 L 115 33 L 117 31 L 118 25 L 120 23 L 120 20 L 125 9 Z M 94 24 L 96 26 L 96 28 L 94 30 L 91 39 L 89 41 L 81 60 L 78 63 L 70 63 L 68 60 L 76 49 L 76 47 L 81 41 L 89 27 L 92 24 Z" />
|
||||
</svg>
|
||||
</div><div class="brand-title" style="font-size:13px;font-weight:700;color:var(--text-2);margin-left:10px;white-space:nowrap">Private Server Console</div></div>
|
||||
<button id="themeToggle" class="theme-toggle" title="Toggle theme">🌙</button>
|
||||
</header>
|
||||
<h1 style="display:none">Vehicle debug</h1>
|
||||
|
||||
<div class="card">
|
||||
<h2>Status</h2>
|
||||
@@ -134,7 +191,7 @@
|
||||
<!-- Search & Pose Toolbar -->
|
||||
<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:8px">
|
||||
<div style="flex:1;min-width:220px">
|
||||
<input id="npcSearchInput" type="text" placeholder="🔍 ค้นหา (พิมพ์ชื่อ เช่น 谜面, Nanami, เป็ด, สุนัข หรือเลข ID)..." style="width:100%;box-sizing:border-box" oninput="onNpcSearchInput()">
|
||||
<input id="npcSearchInput" type="text" placeholder="🔍 ค้นหา (พิมพ์ EN/จีน เช่น Security, Placeholder, 占位, Duck หรือเลข ID)..." style="width:100%;box-sizing:border-box" oninput="onNpcSearchInput()">
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:6px">
|
||||
<label style="font-size:12px;color:#aaa">ท่าทาง (Pose):</label>
|
||||
@@ -237,13 +294,6 @@
|
||||
|
||||
<div class="card" style="border-color:#3b82f6">
|
||||
<h2 style="color:#60a5fa">🚇 ระบบขนส่งมวลชน & รถไฟใต้ดิน (Metro Transit & Live Train Tracker)</h2>
|
||||
|
||||
<div style="margin-bottom:12px;padding:8px 12px;background:#1e293b;border-radius:6px;display:flex;align-items:center;gap:10px;border:1px solid #3b82f6">
|
||||
<input id="autoSubwayCutscene" type="checkbox" checked style="width:18px;height:18px;accent-color:#38bdf8;cursor:pointer">
|
||||
<label for="autoSubwayCutscene" style="font-size:13px;color:#f0f9ff;cursor:pointer;font-weight:600">
|
||||
🎬 เล่นคัทซีนรถไฟลอดอุโมงค์อัตโนมัติ (เล่นฉากนั่งรถไฟ -> ลอดอุโมงค์ -> โผล่ออกที่อุโมงค์สถานีปลายทาง)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Sub-Tabs Navigation -->
|
||||
<div style="display:flex;gap:6px;margin-bottom:12px;flex-wrap:wrap">
|
||||
@@ -436,6 +486,7 @@
|
||||
จำนวน (Count): <input id="giveCount" type="number" value="1" min="1" max="99999" style="width:90px">
|
||||
<label class="muted" style="margin-left:6px"><input id="giveBind" type="checkbox"> ผูกมัด (Bind)</label>
|
||||
<button style="background:#f57f17;color:#fff;font-weight:bold" onclick="giveItem()">🎁 เสกไอเทม / Grant</button>
|
||||
<button style="background:#00838f;color:#fff;font-weight:bold" onclick="grantGachaTickets()">🎟️ เสกตั๋วกาชาทั้งหมด</button>
|
||||
</div>
|
||||
<div style="margin-top:6px">
|
||||
ค้นหาไอเทม: <input id="itemFilter" placeholder="ชื่อไอเทม หรือ ID" style="width:250px" onkeydown="if(event.key==='Enter')renderItems()">
|
||||
@@ -649,7 +700,7 @@ async function loadNpcCatalog(category = 'all', query = '') {
|
||||
if (r && r.items) {
|
||||
npcCatalogData = r.items;
|
||||
renderNpcTable(npcCatalogData);
|
||||
if (r.poiActions && document.getElementById('npcPoiSelect').children.length <= 1) {
|
||||
if (r.poiActions) {
|
||||
renderPoiSelect(r.poiActions);
|
||||
}
|
||||
}
|
||||
@@ -895,31 +946,6 @@ async function launchArcadeMinigame() {
|
||||
}
|
||||
|
||||
async function warpSubway(x, y, z, facing, stationName, endTimeline) {
|
||||
const autoCutscene = document.getElementById('autoSubwayCutscene')?.checked ?? true;
|
||||
if (autoCutscene) {
|
||||
const sName = stationName || 'สถานีปลายทาง';
|
||||
log(`🚇 กำลังเดินทางด้วยรถไฟไปยัง ${sName}... (ระบบกำลังเล่นคัทซีนรถไฟอัตโนมัติ)`);
|
||||
await api('/api/timeline/play', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'loading_metro_normal01_m' })
|
||||
});
|
||||
setTimeout(async () => {
|
||||
await api('/api/player/teleport', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ x: parseFloat(x), y: parseFloat(y), z: parseFloat(z), facing: parseFloat(facing || 0) })
|
||||
});
|
||||
await api('/api/timeline/play', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: endTimeline || 'loading_metro_end_1' })
|
||||
});
|
||||
log(`🚇 รถไฟเทียบชานชาลาสถานี ${sName} และเดินออกจากอุโมงค์เรียบร้อยแล้ว`);
|
||||
refresh();
|
||||
}, 1800);
|
||||
return;
|
||||
}
|
||||
const r = await api('/api/player/teleport', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -1216,6 +1242,14 @@ async function applyCrowdConfig() {
|
||||
await refreshTrafficStatus();
|
||||
}
|
||||
|
||||
async function trafficLab(key, value) {
|
||||
const body = {};
|
||||
body[key] = value;
|
||||
const r = await api('/api/traffic/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
||||
log('🧪 traffic ' + key + ' = ' + value + ': ' + JSON.stringify(r));
|
||||
await refreshTrafficStatus();
|
||||
}
|
||||
|
||||
async function spawnTrafficWave() {
|
||||
const r = await api('/api/traffic/spawn-wave', { method: 'POST' });
|
||||
log('spawn wave: ' + JSON.stringify(r));
|
||||
@@ -1330,6 +1364,11 @@ async function giveItem(id) {
|
||||
}
|
||||
}
|
||||
|
||||
async function grantGachaTickets() {
|
||||
const r = await api('/api/gacha/grant-tickets', { method: 'POST' });
|
||||
log('🎟️ ตั๋วกาชา: ' + JSON.stringify(r));
|
||||
}
|
||||
|
||||
async function equipSelected() {
|
||||
const pick = document.querySelector('input[name=wpn]:checked');
|
||||
if (!pick) { log('กรุณาเลือกอาวุธก่อนกดสวมใส่'); return; }
|
||||
@@ -1370,5 +1409,22 @@ document.getElementById('timeslider').addEventListener('change', async () => {
|
||||
refresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var btn = document.getElementById('themeToggle');
|
||||
if (!btn) return;
|
||||
var saved = localStorage.getItem('panel-theme');
|
||||
if (saved) document.documentElement.setAttribute('data-theme', saved);
|
||||
btn.textContent = document.documentElement.getAttribute('data-theme') === 'dark' ? '☀' : '☽';
|
||||
btn.addEventListener('click', function () {
|
||||
var el = document.documentElement;
|
||||
var next = el.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
|
||||
el.setAttribute('data-theme', next);
|
||||
localStorage.setItem('panel-theme', next);
|
||||
btn.textContent = next === 'dark' ? '☀' : '☽';
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -7,6 +7,7 @@ using Ananta.Server.Handlers.LoginGate;
|
||||
using Ananta.Server.Protocol.Client4229938;
|
||||
using Ananta.Server.Gameplay.Traffic;
|
||||
using Ananta.Server.Gameplay.Crowd;
|
||||
using Ananta.Server.Gameplay.World;
|
||||
using Ananta.Server.Network;
|
||||
|
||||
namespace Ananta.Server.App;
|
||||
@@ -48,6 +49,14 @@ internal sealed class PrivateServerApplication(PrivateServerConfig config)
|
||||
TrafficAndCrowdDriver.Instance,
|
||||
() => gameSessions.Current is not null ? [gameSessions.Current] : Array.Empty<TcpSession>());
|
||||
|
||||
// Interactive gadget + scene-item grid streaming (vending machines, arcades, doors,
|
||||
// lampposts, props from the client's baked cell data) — server-side equivalent of the
|
||||
// AnantaGadgetLoader / AnantaWorldMod injects.
|
||||
GadgetStreamingEngine.Instance.Initialize(
|
||||
clientDataFolder,
|
||||
TrafficAndCrowdDriver.Instance,
|
||||
() => gameSessions.Current is not null ? [gameSessions.Current] : Array.Empty<TcpSession>());
|
||||
|
||||
// Localhost debug API for debug-panel.py (optional, never blocks the game stack).
|
||||
using var debugApi = config.Debug.Enabled ? new DebugApiServer(config, gameSessions) : null;
|
||||
var webInfo = "web=off";
|
||||
|
||||
Reference in new issue
Block a user