feat(minigame): add multi-arcade minigame catalog (KOF97, Metal Slug, Piano, Drumkit, Bowling, Basketball) and fix in-engine panel launcher

This commit is contained in:
CAtlas0784 committed 2026-09-18 13:45:45 +07:00
1 parent b7746b4954
commit f2a36fe188
4 files changed
+207 -25

No files matched your search

Binary file not shown.
@@ -825,25 +825,115 @@ gameSwitchDefaults.map((name) => `M.${name} = true\n`).join("") +
` end\n` +
` end)\n` +
` end)\n` +
` elseif cmd == "LAUNCH_MINIGAME:FIGHTER" then\n` +
` elseif string.sub(cmd, 1, 16) == "LAUNCH_MINIGAME:" or string.sub(cmd, 1, 20) == "CMD:LAUNCH_MINIGAME:" or string.sub(cmd, 1, 9) == "MINIGAME:" or string.sub(cmd, 1, 13) == "CMD:MINIGAME:" then\n` +
` local rawGame = string.match(cmd, "MINIGAME:([^:]+)") or "KOF97"\n` +
` local gameType = string.upper(rawGame)\n` +
` pcall(function()\n` +
` if gPanelManager and gPanelId and gPanelId.S_FIGHTER_MINIGAME_PANEL then\n` +
` gPanelManager:OpenWindow(gPanelId.S_FIGHTER_MINIGAME_PANEL)\n` +
` elseif CS and CS.L18 and CS.L18.MiniGame and CS.L18.MiniGame.Fighter and CS.L18.MiniGame.Fighter.FighterMinigame then\n` +
` local mg = CS.L18.MiniGame.Fighter.FighterMinigame.Instance\n` +
` if mg then\n` +
` mg.GameStarted = true\n` +
` pcall(function() mg:CreatePlayer() end)\n` +
` pcall(function() mg:CreateEnemy() end)\n` +
` local function forceShow(panelId, data)\n` +
` if not panelId or not gPanelManager then return false end\n` +
` local ok = false\n` +
` pcall(function()\n` +
` if gPanelManager.panelData then\n` +
` gPanelManager.panelData[panelId] = data\n` +
` end\n` +
` if LX6 and LX6.Manager and LX6.Manager.PanelManager and LX6.Manager.PanelManager.Instance then\n` +
` LX6.Manager.PanelManager.Instance:CheckShowFromLua(panelId, nil, nil, -1, -1)\n` +
` ok = true\n` +
` elseif gPanelManager.CheckShow then\n` +
` ok = gPanelManager:CheckShow(panelId, data)\n` +
` end\n` +
` end)\n` +
` return ok\n` +
` end\n` +
` local myUnit = gCS and gCS.MyPlayerManager and gCS.MyPlayerManager.PlayerUnit\n` +
` local pObj = myUnit and myUnit.PlayerObj\n` +
` local pPos = (pObj and pObj.transform and pObj.transform.position) or (UnityEngine and UnityEngine.Vector3 and UnityEngine.Vector3.zero) or Vector3.zero\n` +
` local pRot = (pObj and pObj.transform and pObj.transform.rotation) or (UnityEngine and UnityEngine.Quaternion and UnityEngine.Quaternion.identity) or Quaternion.identity\n` +
` local pScale = (UnityEngine and UnityEngine.Vector3 and UnityEngine.Vector3.one) or Vector3.one\n` +
` local fArgs = {\n` +
` position = pPos,\n` +
` rotation = pRot,\n` +
` localScale = pScale,\n` +
` forbidClickExit = false,\n` +
` autoCloseAfterWin = -1,\n` +
` ignoreCountdown = false\n` +
` }\n` +
` local tip = "Launched Minigame: " .. tostring(gameType)\n` +
` if gameType == "KOF97" then\n` +
` if gPanelId and gPanelId.LIBRETRO_PANEL then\n` +
` forceShow(gPanelId.LIBRETRO_PANEL, { gameType = 1 })\n` +
` tip = "Opened Arcade: The King of Fighters 97 (ตู้เกม KOF '97)"\n` +
` end\n` +
` elseif gameType == "METALSLUG" then\n` +
` if gPanelId and gPanelId.LIBRETRO_PANEL then\n` +
` forceShow(gPanelId.LIBRETRO_PANEL, { gameType = 0 })\n` +
` tip = "Opened Arcade: Metal Slug (ตู้เกม Metal Slug)"\n` +
` end\n` +
` elseif gameType == "FIGHTER" then\n` +
` local shown = false\n` +
` if gPanelId and gPanelId.FIGHTER_MAIN_PANEL then\n` +
` shown = forceShow(gPanelId.FIGHTER_MAIN_PANEL, fArgs)\n` +
` end\n` +
` if gPanelId and gPanelId.FIGHTER_HUD_PANEL then\n` +
` forceShow(gPanelId.FIGHTER_HUD_PANEL, fArgs)\n` +
` end\n` +
` if not shown and gPanelId and gPanelId.LIBRETRO_PANEL then\n` +
` forceShow(gPanelId.LIBRETRO_PANEL, { gameType = 1 })\n` +
` tip = "Opened Arcade: The King of Fighters 97 (KOF)"\n` +
` else\n` +
` tip = "Opened 3D Arcade Fighter Minigame"\n` +
` end\n` +
` elseif gameType == "PIANO" then\n` +
` if gPanelId and gPanelId.UI_PANEL__INSTRUMENT__PIANO then\n` +
` forceShow(gPanelId.UI_PANEL__INSTRUMENT__PIANO)\n` +
` tip = "Opened Grand Piano Minigame"\n` +
` end\n` +
` elseif gameType == "DRUMKIT" then\n` +
` if gPanelId and gPanelId.UI_PANEL__INSTRUMENT__DRUMKIT then\n` +
` forceShow(gPanelId.UI_PANEL__INSTRUMENT__DRUMKIT)\n` +
` tip = "Opened Drumkit Minigame"\n` +
` end\n` +
` elseif gameType == "BOWLING" then\n` +
` if gPanelId and gPanelId.MINI_GAMES_BOWLING_MAIN_PANEL then\n` +
` forceShow(gPanelId.MINI_GAMES_BOWLING_MAIN_PANEL)\n` +
` tip = "Opened Bowling Minigame"\n` +
` end\n` +
` elseif gameType == "BASKETBALL" then\n` +
` if gPanelId and gPanelId.BASKETBALL_SHOOT_PANEL then\n` +
` forceShow(gPanelId.BASKETBALL_SHOOT_PANEL)\n` +
` tip = "Opened Basketball Shoot Minigame"\n` +
` end\n` +
` end\n` +
` local tip = "Launched Arcade Fighter Minigame"\n` +
` if gDisplayMessageMgr and gDisplayMessageMgr.ShowMessageContent then\n` +
` gDisplayMessageMgr:ShowMessageContent(tip)\n` +
` end\n` +
` if gCS and gCS.MessageTipsMgr and gCS.MessageTipsMgr.ShowMessageTips then\n` +
` gCS.MessageTipsMgr:ShowMessageTips(tip)\n` +
` pcall(function()\n` +
` if gDisplayMessageMgr and gDisplayMessageMgr.ShowMessageContent then\n` +
` gDisplayMessageMgr:ShowMessageContent(tip)\n` +
` end\n` +
` if gCS and gCS.MessageTipsMgr and gCS.MessageTipsMgr.ShowMessageTips then\n` +
` gCS.MessageTipsMgr:ShowMessageTips(tip)\n` +
` end\n` +
` end)\n` +
` end)\n` +
` elseif cmd == "CLOSE_MINIGAME" or cmd == "CMD:CLOSE_MINIGAME" then\n` +
` pcall(function()\n` +
` local toClose = {\n` +
` gPanelId and gPanelId.LIBRETRO_PANEL,\n` +
` gPanelId and gPanelId.FIGHTER_MAIN_PANEL,\n` +
` gPanelId and gPanelId.FIGHTER_HUD_PANEL,\n` +
` gPanelId and gPanelId.UI_PANEL__INSTRUMENT__PIANO,\n` +
` gPanelId and gPanelId.UI_PANEL__INSTRUMENT__DRUMKIT,\n` +
` gPanelId and gPanelId.MINI_GAMES_BOWLING_MAIN_PANEL,\n` +
` gPanelId and gPanelId.BASKETBALL_SHOOT_PANEL\n` +
` }\n` +
` for _, pid in ipairs(toClose) do\n` +
` if pid and gPanelManager then\n` +
` pcall(function() gPanelManager:Close(pid) end)\n` +
` end\n` +
` end\n` +
` pcall(function()\n` +
` if gDisplayMessageMgr and gDisplayMessageMgr.ShowMessageContent then\n` +
` gDisplayMessageMgr:ShowMessageContent("Closed Minigame")\n` +
` end\n` +
` end)\n` +
` end)\n` +
` elseif cmd == "TOGGLE_CLOTHES" then\n` +
` pcall(function()\n` +
+48 -3
View File
@@ -139,6 +139,10 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub
await WriteJsonAsync(ctx, await PlayTimelineAsync(await ReadBodyAsync(ctx.Request, token), token), token);
else if (method == "POST" && path == "/api/minigame/launch")
await WriteJsonAsync(ctx, await LaunchMinigameAsync(await ReadBodyAsync(ctx.Request, token), token), token);
else if (method == "POST" && path == "/api/minigame/close")
await WriteJsonAsync(ctx, await CloseMinigameAsync(token), token);
else if (method == "GET" && path == "/api/minigame/catalog")
await WriteJsonAsync(ctx, MinigameCatalog(), token);
else if (method == "GET" && path == "/api/cutscene/catalog")
await WriteJsonAsync(ctx, CutsceneCatalog(), token);
else if (method == "POST" && path == "/api/npc/spawn")
@@ -1349,11 +1353,52 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub
if (session is null)
return new { ok = false, error = "no live game session (is the client in the world?)" };
var cmd = "CMD:LAUNCH_MINIGAME:FIGHTER";
string game = "KOF97";
try
{
using var doc = JsonDocument.Parse(string.IsNullOrWhiteSpace(json) ? "{}" : json);
var root = doc.RootElement;
if (root.TryGetProperty("game", out var pg)) game = pg.GetString() ?? "KOF97";
else if (root.TryGetProperty("id", out var pi)) game = pi.GetString() ?? "KOF97";
}
catch { }
var cmd = $"CMD:LAUNCH_MINIGAME:{game.ToUpperInvariant()}";
await session.NotifyAsync(MethodId.SyncNotice, UxSerializer.Serialize(cmd), token);
session.Log.Info("[DEBUG-API] launch arcade minigame fighter dispatched");
return new { ok = true, game = "FIGHTER" };
session.Log.Info($"[DEBUG-API] launch minigame game={game}");
return new { ok = true, game };
}
private async Task<object> CloseMinigameAsync(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?)" };
var cmd = "CMD:CLOSE_MINIGAME";
await session.NotifyAsync(MethodId.SyncNotice, UxSerializer.Serialize(cmd), token);
session.Log.Info("[DEBUG-API] close minigame dispatched");
return new { ok = true };
}
private static object MinigameCatalog()
{
return new
{
ok = true,
items = new[]
{
new { id = "KOF97", name = "🕹️ Arcade: The King of Fighters '97 (ตู้เกม KOF '97)", category = "arcade" },
new { id = "METALSLUG", name = "🕹️ Arcade: Metal Slug (ตู้เกม Metal Slug ลุยด่าน)", category = "arcade" },
new { id = "FIGHTER", name = "🥊 3D Arcade Fighter (มินิเกมต่อสู้ 3D)", category = "arcade" },
new { id = "PIANO", name = "🎹 Grand Piano Interactive (เล่นเปียโนสด)", category = "music" },
new { id = "DRUMKIT", name = "🥁 Drumkit Interactive (ตีกลองชุดสด)", category = "music" },
new { id = "BOWLING", name = "🎳 Bowling Alley (โยนโบว์ลิ่ง)", category = "sports" },
new { id = "BASKETBALL", name = "🏀 Street Basketball (ชูตบาสเกตบอล)", category = "sports" },
}
};
}
private static async Task<string> ReadBodyAsync(HttpListenerRequest request, CancellationToken token)
+54 -7
View File
@@ -264,12 +264,35 @@
</div>
<div class="card" style="border-color:#ff9800">
<h2 style="color:#ffb74d">🕹️ Arcade Minigame (ตู้เกมต่อสู้อาร์เคด)</h2>
<div style="display:flex;gap:8px;align-items:center">
<button style="background:#e65100;color:white" onclick="launchArcadeMinigame()">Launch Arcade Fighter / เปิดตู้เกมต่อสู้</button>
<span class="badge" style="background:#bf360c;color:#ffccbc">L18 Street Fighter</span>
<h2 style="color:#ffb74d">🕹️ Arcade & Interactive Minigames (ตู้เกมอาร์เคด & มินิเกมในโลก)</h2>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
<select id="minigameSelect" style="flex:1;min-width:240px;background:#2d3748;color:#e2e8f0;border:1px solid #4a5568;padding:8px 12px;border-radius:6px;font-size:14px">
<optgroup label="🕹️ ตู้เกมอาร์เคด (Arcade Cabinets)">
<option value="KOF97" selected>🕹️ Arcade: The King of Fighters '97 (ตู้เกม KOF '97)</option>
<option value="METALSLUG">🕹️ Arcade: Metal Slug (ตู้เกม Metal Slug ลุยด่าน)</option>
<option value="FIGHTER">🥊 3D Arcade Fighter (มินิเกมต่อสู้ 3D)</option>
</optgroup>
<optgroup label="🎵 ดนตรีสด (Live Musical Instruments)">
<option value="PIANO">🎹 Grand Piano (เล่นเปียโนสด)</option>
<option value="DRUMKIT">🥁 Drumkit Set (ตีกลองชุดสด)</option>
</optgroup>
<optgroup label="🏆 กิจกรรมกีฬา (Sports & Leisure)">
<option value="BOWLING">🎳 Bowling Alley (โยนโบว์ลิ่ง)</option>
<option value="BASKETBALL">🏀 Street Basketball (ชูตบาสเกตบอล)</option>
</optgroup>
</select>
<button style="background:#e65100;color:white;font-weight:600;padding:8px 16px" onclick="launchSelectedMinigame()">Launch / เปิดมินิเกม</button>
<button style="background:#4a5568;color:#cbd5e0;padding:8px 14px" onclick="closeActiveMinigame()">Close / ปิดมินิเกม</button>
</div>
<div class="muted" style="margin-top:6px">เปิดระบบมินิเกมตู้เกม Arcade Fighter ในฉาก</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:8px">
<button style="background:#d97706;color:white;font-size:12px;padding:4px 8px" onclick="launchMinigameQuick('KOF97')">🕹️ KOF '97</button>
<button style="background:#d97706;color:white;font-size:12px;padding:4px 8px" onclick="launchMinigameQuick('METALSLUG')">🕹️ Metal Slug</button>
<button style="background:#2563eb;color:white;font-size:12px;padding:4px 8px" onclick="launchMinigameQuick('PIANO')">🎹 Piano</button>
<button style="background:#2563eb;color:white;font-size:12px;padding:4px 8px" onclick="launchMinigameQuick('DRUMKIT')">🥁 Drumkit</button>
<button style="background:#059669;color:white;font-size:12px;padding:4px 8px" onclick="launchMinigameQuick('BOWLING')">🎳 Bowling</button>
<button style="background:#059669;color:white;font-size:12px;padding:4px 8px" onclick="launchMinigameQuick('BASKETBALL')">🏀 Basketball</button>
</div>
<div class="muted" style="margin-top:6px">เปิดตู้เกมอาร์เคด (KOF '97, Metal Slug) หรือมินิเกมดนตรี/กีฬาในโลกได้ทันที พร้อมปุ่มควบคุมแบบเต็มหน้าจอ</div>
</div>
<div class="card" style="border-color:#e53935">
@@ -739,9 +762,33 @@ async function playSelectedCutscene() {
log('play cutscene: ' + JSON.stringify(r));
}
async function launchSelectedMinigame() {
const sel = document.getElementById('minigameSelect');
const game = sel ? sel.value : 'KOF97';
const r = await api('/api/minigame/launch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ game })
});
log('launch minigame (' + game + '): ' + JSON.stringify(r));
}
async function launchMinigameQuick(game) {
const r = await api('/api/minigame/launch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ game })
});
log('quick launch minigame (' + game + '): ' + JSON.stringify(r));
}
async function closeActiveMinigame() {
const r = await api('/api/minigame/close', { method: 'POST' });
log('close minigame: ' + JSON.stringify(r));
}
async function launchArcadeMinigame() {
const r = await api('/api/minigame/launch', { method: 'POST' });
log('launch arcade minigame: ' + JSON.stringify(r));
await launchSelectedMinigame();
}
async function setTimePreset(hour, minute) {