From 5e25d474c380edf76d905ee304d59c510730ad7c Mon Sep 17 00:00:00 2001 From: CAtlas0784 Date: Tue, 15 Sep 2026 19:47:53 +0700 Subject: [PATCH] fix(combat): hydrate skills on spawn/switch, fix grapple anti-cheat dc, monster spawn & cutscenes, and update README feature matrix --- .../proxy/public/fastpatch_4229938.zip | Bin 3955 -> 4239 bytes .../tools/generate_client_config_patch.js | 184 +++++++++++------- .../Ananta.Handlers/Game/GameRouter.Combat.cs | 4 + .../Ananta.Handlers/Game/GameRouter.Gm.cs | 7 + .../Game/GameRouter.Movement.cs | 17 +- .../Game/GameRouter.Profiles.cs | 8 +- .../Ananta.Handlers/Game/GameRouter.cs | 1 + README.md | 14 +- 8 files changed, 158 insertions(+), 77 deletions(-) diff --git a/Ananta.Proxy/proxy/public/fastpatch_4229938.zip b/Ananta.Proxy/proxy/public/fastpatch_4229938.zip index c1d72c155f0ee2b3ddf613671e97b03c9d875ecf..debb48f707c73899ba4f506f60cfcb9c76323ba3 100644 GIT binary patch delta 4031 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 MXtha_X#eg07muZh>i_@% delta 3747 zcmZ9PS2WxU*N10B^llIlz4sb@M49Lk6K#Z1Mns7=93?UQg-jxd9%b}47(^!$C5YZ@ zh!QPoOrjl~@4PqPS?{~o+BbVWSNr0(eoq|l0T->Q(G5}v2n3=4J;X^R(DDG)hMlI6 z*T?Ez>~nc6kICmGXAsnUMWr`;=H@39gZ!9%lMI!p=`7|Ug>?DK$|@p}!rp#dT^Z($ zIi1%b8-ews>0!&RGUy(zlSS8+cT2rTJvxDyAewqjY>E7gcfD%X-&zYQ9EX~3MF~)9*t)wR{3=&xuW|E@&SxQ)n5)-*x{uAQ0t8duz9 zzPzkm=vUVNwO=zAC0?eKHF0a*GEJm9Jjqtm<4s|}EYZ8<#`}T&9qIpiB9gub|K%$_eU-aTY%j0L>bIPojM0?)9+Gc8F@dj$2=RoaWEaxZH5}aTUY9jKTMwvXB z6ZGHdH~B|j9k&QL+b{{9jtx?{itnL`O%FBZYvs%I7+HFwm}|62a?qLOKByr^Hd&^t zaa`@!eVy>qszp27=HTP?dlj|vJ7o7~&W+c?KqK(mREt^%&-lg4-!6&tyhCT*+~w`%2Ct;jL;WW-El)F< zleVE$6bO>Ak20e=-PL1+{5~7Mmj%A5Zx~%5*bqPf7TLpq`pJ0NS_>m9+^C)5QP-t> z<^24KF%zGV1C@JjOscWWMCezan>2;5KOR%AJ#t_mo*IQ)DHGzA3~m}x$Sra5O*_#% zl?;qYK9*WeeYDS_<(0$&8*eYsP7rGc$oXMWoZVN$DYz)gCc~m&xaRCTZjn){ zb$T5JMMkk;jz=bjSRzHiS|y#03jvAmvcxCLKH2%U>R-|G$pCTN3cSxY0STaeJMd&g zav2pPLU)9El;^vEK&~I6sJe)U0K<1-k}pV_(pbxKJ__7uoC&-3lpS6Jy?;YNh2GM% zseZe{!Npt(Mj}Wpp{Rr7t7ql5gp$L(`!qIQ!tY#zzTfDPCQYGV&fhr>uE+O)JmYL(@fWPd=P_tv^3jn0Gc~$XfOOA=M~z=A zOjO_o(qs6}dgyYOWhLHuU>HSY?9^ol`0naI^2pKr@Nx=sL-e!HOAl;}4sry+u7G;_EC~N3cWF-JU(51F(0U_T#<(Qz{ z*b^QNU!0KNg*wvIDTV_umlmyY9e;w(7mGIYmnG#Y8FWnF2|5$%+50tjeJOrT=TeIu z6s)B@@k`NWFNcEM)?SM?Xv{x1f-J333c?9y#1l79i_gZjyTvkV{4#~Q?7@NLebOUt z68L*n>Em{NDqV+tdr~Sx{L-88%olr&A9axIDPCl2$gt?#X<^{ZP{c1@HL8D_(rs?e7+LWx|v+jP^+SOIwWkUT9fb1<)tMXPb_Ct$V_R#wyE zQcT)xl>|1YQ{b1tZ@eX4rr<3o2h?Q1r~4Vd)CB_+NnX~ zgu`bnz0z4?NhN!r3z>Z(`eE;hE6i#`gRrU#GcU^-|a!!;% z>H`XwEy~OZ75F1wx=&)^J6)-sNc`XWB!hIq@3})uy?`EH1&;EnxYw#mtfAy^$W>%STIIs;#@18Xt3FgA3Ys!Q}^=xFhB~@cFzKsfKmN5Qf_L|=&a8m(~W=dn=9Xr^C|0sMhm?V_v=#O!$?ao!g{H;F9Pm2Ga5!FQ^s z*7tjz!GM>MYQ_=&vp^n3X4VI}WV3Y9e7Y8Y6bAZMdv~^Z-WK~=pn%+MYT!-rrCN1dnsnWVSSm%sOZlnlr4XYc3UhRQ{&8QolHpYC=+r1tfKJl zwY#U&AH_}s4oyN2w^^6Mwv^>{wns~}EL6byGcc|6OfWiW+}7`MamjmE*gv1%alp(J zKk4e-THHu;6M8#Fr%ppNj%m=DCFU;^s}CFJrL1E0E9u_Vf;e~OhA400hWaT7X*h>; z)QEkK7@Mj^Gy1oi0#*SH{KyfJHI9*98p%Mm!uH$9>u4#xRgpUu=d0YQIajky7ZxoS zKwa?jspQ9|ItBi+O)Cenb+dEde%rNO{rvI!w$Qmq6t%qcF!E2)#?l%lz z%o5cS7hz}JWrv=};SM}3p?s(<{rz*DGRFWlc{?k16!3*x>yHA8Lt78tWCBkZz%T2| zb=PG*AFjuK`hFffJC%((uoPF!TAGCdAab?iB7)w{2VJ941-g4dR;7Ufg_1D}Bc69R z<}YN~7>Ij0qVRi1!e(0g~Os&$zQ|Ym4xF+Wl6Ni%n50Pt9Tkd ztux*mA9`)(<{G_PfK#s`OMWCnqCrt0J?`DNho%;H%+PVL63H}rftBSMq2R+?EAg=g z;(4zO5`9<%FL_S_ikNgmZZq}ljKj1ud~fHja*m&!aL)Y%uR4t6m}eH;Sa00C1xxj% zfU;0(?vc@C#v3*{Rxnj5_7OZ=p0Nh=8Lqf@H4}NJJ)H*ZfGxN8d3}Q4cj7*`tUHGM zfbP#P=FO?OPX#Wpx+AR51dOhSrG{$`cO*ixi`tyo?4NE)zeu>sL}D_E=xYxj^8b0I zFt3wX6eoO}tz@qpfyqH)3htUJLY2D5B|?&m?mgYIw?5NJ$xiPol?9@r?teet&OtE8 zLmL-VU@_;Mz(%JiOpr-L?yi22eRX8gi#u;tz3gPNOH)HmY(4cU$fu~<-_TOTLB=>p z2Z|AMf$YZ|kOcy-51*!BIP+2~yg%y|<5Mn9@&(g3H6qmgmsrV+J*`Q(@f|uBjk^r2 z!%b1?aj_kxl(jWS#7tMp#;i@6A6SN?y7>rBNta|LK;l=#u>@*PfRdkUMl8`#hRY+@ zp`Kv!*pe}mB~Ou2RBcBO6PERrrND97-&v4_QKBsFuV~$QxVE@gMSOhcCMi6_%(Gy; z;T%zaf-HO?Rjhl2AR|&R8YG{FV{m{nV=7gd!S{;B#@K*`xhyV{i;||_7@sv2BImJu zvf?%n3zA4b8$1-MR>iQzJAF}e)K{|ORz_I(wt*&>kC97n@;JZ@sR~mY&QGw+0Vyr{Fd;sbB)Rg@ zqHwN(3{x{|QJ!|)kFnI+^PyP%OL9{qFbM?o|9XfVDKr#P0E0l-f0p*2kok9#D(N}7 q{xQh^k)MA75GZMzUWMmB$p1#wztA>i5+}7{(sKp|GB>*aoc|B&^Y5Sl diff --git a/Ananta.Proxy/proxy/tools/generate_client_config_patch.js b/Ananta.Proxy/proxy/tools/generate_client_config_patch.js index ceaed7e..6a5d220 100644 --- a/Ananta.Proxy/proxy/tools/generate_client_config_patch.js +++ b/Ananta.Proxy/proxy/tools/generate_client_config_patch.js @@ -193,6 +193,30 @@ gameSwitchDefaults.map((name) => `M.${name} = true\n`).join("") + ` end\n` + ` end)\n` + ` pcall(function()\n` + +` if CS and CS.L50 and CS.L50.Script and CS.L50.Script.LX6 and CS.L50.Script.LX6.Security then\n` + +` local sec = CS.L50.Script.LX6.Security\n` + +` if sec.DavinciReport then\n` + +` sec.DavinciReport.Send = function() end\n` + +` sec.DavinciReport.Report = function() end\n` + +` end\n` + +` if sec.DavinciMgr then\n` + +` sec.DavinciMgr.CheckTimeScale = function() end\n` + +` sec.DavinciMgr.ReportTimeScale = function() end\n` + +` sec.DavinciMgr.CheckSpeed = function() end\n` + +` end\n` + +` end\n` + +` if CS and CS.LX6 and CS.LX6.Security then\n` + +` local sec = CS.LX6.Security\n` + +` if sec.DavinciReport then\n` + +` sec.DavinciReport.Send = function() end\n` + +` sec.DavinciReport.Report = function() end\n` + +` end\n` + +` if sec.DavinciMgr then\n` + +` sec.DavinciMgr.CheckTimeScale = function() end\n` + +` sec.DavinciMgr.ReportTimeScale = function() end\n` + +` sec.DavinciMgr.CheckSpeed = function() end\n` + +` end\n` + +` end\n` + ` if DavinciReport then\n` + ` DavinciReport.Send = function() end\n` + ` DavinciReport.Report = function() end\n` + @@ -221,87 +245,102 @@ gameSwitchDefaults.map((name) => `M.${name} = true\n`).join("") + ` end\n` + ` end)\n` + `end\n\n` + -`local function HookMasterNotice()\n` + -` if MasterToClientImpl and not MasterToClientImpl._cmdHooked then\n` + -` MasterToClientImpl._cmdHooked = true\n` + -` local oldSyncNotice = MasterToClientImpl.SyncNotice\n` + -` MasterToClientImpl.SyncNotice = function(...)\n` + -` local allArgs = {...}\n` + -` local content = nil\n` + -` for i = 1, #allArgs do\n` + -` if type(allArgs[i]) == "string" then\n` + -` content = allArgs[i]\n` + -` break\n` + -` end\n` + +`local function ProcessDebugCommand(cmd)\n` + +` if not cmd or type(cmd) ~= "string" then return end\n` + +` if cmd == "UNSTUCK_BLACKSCREEN" then\n` + +` pcall(function()\n` + +` if gBlackScreenManager then gBlackScreenManager:ClearTransition(nil, true) end\n` + +` if gVideoManager then gVideoManager:CloseBlackScreen() end\n` + +` if gPanelManager and gPanelId and gPanelId.S_VIDEO_PLAYER_PANEL then\n` + +` gPanelManager:CloseWindow(gPanelId.S_VIDEO_PLAYER_PANEL)\n` + ` end\n` + -` if content and string.sub(content, 1, 4) == "CMD:" then\n` + -` local cmd = string.sub(content, 5)\n` + -` if cmd == "UNSTUCK_BLACKSCREEN" then\n` + -` pcall(function()\n` + -` if gBlackScreenManager then\n` + -` gBlackScreenManager:ClearTransition(nil, true)\n` + -` end\n` + -` if gVideoManager then\n` + -` gVideoManager:CloseBlackScreen()\n` + -` end\n` + -` if gPanelManager and gPanelId and gPanelId.S_VIDEO_PLAYER_PANEL then\n` + -` gPanelManager:CloseWindow(gPanelId.S_VIDEO_PLAYER_PANEL)\n` + -` end\n` + -` if LX6 and LX6.GUI and LX6.GUI.GuiMgr and gPanelId and gPanelId.COMMON_BLACK_TRANSITION then\n` + -` LX6.GUI.GuiMgr.Instance:SetShowScenePanel(false, gPanelId.COMMON_BLACK_TRANSITION)\n` + -` gPanelManager:RemoveVisibleMode(LX6.Manager.VisibleControlType.CommonBlack)\n` + -` LX6.Manager.GameInputManager.SetEnableInput(gPanelId.COMMON_BLACK_TRANSITION)\n` + -` end\n` + -` end)\n` + -` return\n` + -` elseif string.sub(cmd, 1, 14) == "PLAY_CUTSCENE:" then\n` + -` local arg = string.sub(cmd, 15)\n` + -` local numId = tonumber(arg)\n` + -` pcall(function()\n` + -` if numId and gPanelManager and gPanelId and gPanelId.S_VIDEO_PLAYER_PANEL then\n` + -` gPanelManager:OpenWindow(gPanelId.S_VIDEO_PLAYER_PANEL, { videoId = numId })\n` + -` elseif gTimelineManager and gTimelineManager.Timeline_LoadAndPlay then\n` + -` gTimelineManager:Timeline_LoadAndPlay(arg, nil)\n` + -` elseif gVideoManager and gVideoManager.PlayVideo then\n` + -` gVideoManager:PlayVideo(numId or arg)\n` + -` end\n` + -` end)\n` + -` return\n` + -` elseif string.sub(cmd, 1, 12) == "SPAWN_ENEMY:" then\n` + -` local p1, p2, p3 = string.match(string.sub(cmd, 13), "([^:]+):?([^:]*):?([^:]*)")\n` + -` local enemyId = tonumber(p1) or 40900579\n` + -` local camp = tonumber(p2) or 2\n` + -` local count = tonumber(p3) or 1\n` + -` pcall(function()\n` + -` if gCS and gCS.GmUtils and gCS.GmUtils.AddEnemyWithCamp then\n` + -` gCS.GmUtils.AddEnemyWithCamp(enemyId, camp)\n` + -` elseif gCS and gCS.GmUtils and gCS.GmUtils.AddEnemy then\n` + -` gCS.GmUtils.AddEnemy(enemyId)\n` + -` elseif gCS and gCS.LuaUtils and gCS.LuaUtils.AddEnemy then\n` + -` gCS.LuaUtils.AddEnemy(enemyId, 1, camp, count)\n` + -` elseif CS and CS.LX6 and CS.LX6.GmUtils and CS.LX6.GmUtils.AddEnemyWithCamp then\n` + -` CS.LX6.GmUtils.AddEnemyWithCamp(enemyId, camp)\n` + -` end\n` + -` end)\n` + -` return\n` + -` end\n` + +` if LX6 and LX6.GUI and LX6.GUI.GuiMgr and gPanelId and gPanelId.COMMON_BLACK_TRANSITION then\n` + +` LX6.GUI.GuiMgr.Instance:SetShowScenePanel(false, gPanelId.COMMON_BLACK_TRANSITION)\n` + +` gPanelManager:RemoveVisibleMode(LX6.Manager.VisibleControlType.CommonBlack)\n` + +` LX6.Manager.GameInputManager.SetEnableInput(gPanelId.COMMON_BLACK_TRANSITION)\n` + +` end\n` + +` end)\n` + +` elseif string.sub(cmd, 1, 14) == "PLAY_CUTSCENE:" then\n` + +` local arg = string.sub(cmd, 15)\n` + +` local numId = tonumber(arg)\n` + +` pcall(function()\n` + +` if numId and gPanelManager and gPanelId and gPanelId.S_VIDEO_PLAYER_PANEL then\n` + +` gPanelManager:OpenWindow(gPanelId.S_VIDEO_PLAYER_PANEL, { videoId = numId })\n` + +` elseif gTimelineManager and gTimelineManager.Timeline_LoadAndPlay then\n` + +` gTimelineManager:Timeline_LoadAndPlay(arg, nil)\n` + +` elseif gVideoManager and gVideoManager.PlayVideo then\n` + +` gVideoManager:PlayVideo(numId or arg)\n` + +` end\n` + +` end)\n` + +` elseif string.sub(cmd, 1, 12) == "SPAWN_ENEMY:" then\n` + +` local p1, p2, p3 = string.match(string.sub(cmd, 13), "([^:]+):?([^:]*):?([^:]*)")\n` + +` local enemyId = tonumber(p1) or 40900579\n` + +` local camp = tonumber(p2) or 2\n` + +` local count = tonumber(p3) or 1\n` + +` pcall(function()\n` + +` if gCS and gCS.LuaUtils and gCS.LuaUtils.AddEnemy then\n` + +` gCS.LuaUtils.AddEnemy(enemyId, 1, camp, count)\n` + +` elseif gCS and gCS.GmUtils and gCS.GmUtils.AddEnemyWithCamp then\n` + +` gCS.GmUtils.AddEnemyWithCamp(enemyId, camp)\n` + +` elseif gCS and gCS.GmUtils and gCS.GmUtils.AddEnemy then\n` + +` gCS.GmUtils.AddEnemy(enemyId)\n` + +` elseif CS and CS.LX6 and CS.LX6.GmUtils and CS.LX6.GmUtils.AddEnemyWithCamp then\n` + +` CS.LX6.GmUtils.AddEnemyWithCamp(enemyId, camp)\n` + +` end\n` + +` end)\n` + +` end\n` + +`end\n\n` + +`local function HookNoticeTable(tbl)\n` + +` if not tbl or type(tbl) ~= "table" or tbl._cmdHooked then return end\n` + +` tbl._cmdHooked = true\n` + +` local oldSync = tbl.SyncNotice\n` + +` tbl.SyncNotice = function(...)\n` + +` local allArgs = {...}\n` + +` for i = 1, #allArgs do\n` + +` if type(allArgs[i]) == "string" and string.sub(allArgs[i], 1, 4) == "CMD:" then\n` + +` ProcessDebugCommand(string.sub(allArgs[i], 5))\n` + ` return\n` + ` end\n` + -` if oldSyncNotice then\n` + -` return oldSyncNotice(...)\n` + +` end\n` + +` if oldSync then return oldSync(...) end\n` + +` end\n` + +`end\n\n` + +`local function HookDisplayMessageMgr()\n` + +` if gDisplayMessageMgr and not gDisplayMessageMgr._cmdHooked then\n` + +` gDisplayMessageMgr._cmdHooked = true\n` + +` local oldShow = gDisplayMessageMgr.ShowMessageContent\n` + +` gDisplayMessageMgr.ShowMessageContent = function(self, content, ...)\n` + +` if type(content) == "string" and string.sub(content, 1, 4) == "CMD:" then\n` + +` ProcessDebugCommand(string.sub(content, 5))\n` + +` return\n` + ` end\n` + +` return oldShow(self, content, ...)\n` + ` end\n` + ` end\n` + `end\n\n` + +`local function HookAllNotices()\n` + +` pcall(function()\n` + +` if package and package.loaded then\n` + +` HookNoticeTable(package.loaded["LX6/Service/MasterToClientImpl"])\n` + +` HookNoticeTable(package.loaded["LX6/Service/GameToClientImpl"])\n` + +` end\n` + +` if MasterToClientImpl then HookNoticeTable(MasterToClientImpl) end\n` + +` if GameToClientImpl then HookNoticeTable(GameToClientImpl) end\n` + +` HookDisplayMessageMgr()\n` + +` end)\n` + +`end\n\n` + `local oldRequire = require\n` + `require = function(mod)\n` + ` local res = oldRequire(mod)\n` + ` pcall(ApplyAllGlobalHooks)\n` + -` pcall(HookMasterNotice)\n` + +` if mod == "LX6/Service/MasterToClientImpl" or mod == "LX6/Service/GameToClientImpl" then\n` + +` if type(res) == "table" then HookNoticeTable(res) end\n` + +` end\n` + +` pcall(HookAllNotices)\n` + ` return res\n` + `end\n\n` + `pcall(ApplyAllGlobalHooks)\n` + -`pcall(HookMasterNotice)\n`; +`pcall(HookAllNotices)\n`; const switchBytes = to_luastring(switchSource); const switchStatus = lauxlib.luaL_loadbuffer(L, switchBytes, switchBytes.length, to_luastring(switchFileName)); if (switchStatus !== lua.LUA_OK) throw new Error("generated ClientGameSwitch.lua failed Lua syntax validation"); @@ -318,3 +357,14 @@ gameSwitchDefaults.map((name) => `M.${name} = true\n`).join("") + "No gameplay Lua overrides are included.\n", "utf8", ); + + const { execSync } = require("child_process"); + const publicZip = path.join(__dirname, "..", "public", `fastpatch_${clientVersion}.zip`); + try { + const pyCmd = `python -c "import zipfile, os; z = zipfile.ZipFile(r'${publicZip.replace(/\\/g, "\\\\")}', 'w', zipfile.ZIP_DEFLATED); [z.write(os.path.join(r'${fastPatchRoot.replace(/\\/g, "\\\\")}', f), f) for f in os.listdir(r'${fastPatchRoot.replace(/\\/g, "\\\\")}') if os.path.isfile(os.path.join(r'${fastPatchRoot.replace(/\\/g, "\\\\")}', f))]; z.close()"`; + execSync(pyCmd); + console.log(`Successfully generated and packed: ${publicZip}`); + } catch (err) { + console.warn(`Warning: Could not automatically create zip via python: ${err.message}`); + } + diff --git a/Ananta.Server/Ananta.Handlers/Game/GameRouter.Combat.cs b/Ananta.Server/Ananta.Handlers/Game/GameRouter.Combat.cs index 7176875..d110ea1 100644 --- a/Ananta.Server/Ananta.Handlers/Game/GameRouter.Combat.cs +++ b/Ananta.Server/Ananta.Handlers/Game/GameRouter.Combat.cs @@ -573,6 +573,9 @@ internal sealed partial class GameRouter CombatWeaponDefinition weapon, CombatStyleDefinition style) { + var state = GetWorldState(ctx); + state.CombatProfilePublished = true; + // Switching/individually styling one weapon must never overwrite the character-wide // FightStyleInfo map. OnSwitchFightStyle publishes that map explicitly. await ctx.NotifyAsync(MethodId.SyncPlayerAllSkillChargeData, @@ -581,6 +584,7 @@ internal sealed partial class GameRouter await PublishSkillBindings(ctx, unitId, weapon, style); await ctx.NotifyAsync(MethodId.SyncSpiritLastUsedWeapon, CombatCodec.SpiritLastUsedWeapon(templateId, weapon.InstanceId)); + ctx.Session.Log.Info($"[COMBAT] profile published unit={unitId} template={templateId} weapon={weapon.TemplateId}/{weapon.InstanceId} style={style.Id} skills={style.CommonSkill},{style.HeavyCommonSkill},{style.DodgeSkill},{style.ControlSkill},{weapon.ActiveSkill(style)},{weapon.UniqueSkill(style)}"); } private static CombatStyleDefinition ResolveWeaponStyle(WorldEntryState state, CombatWeaponDefinition weapon) diff --git a/Ananta.Server/Ananta.Handlers/Game/GameRouter.Gm.cs b/Ananta.Server/Ananta.Handlers/Game/GameRouter.Gm.cs index 1ae0240..03e8311 100644 --- a/Ananta.Server/Ananta.Handlers/Game/GameRouter.Gm.cs +++ b/Ananta.Server/Ananta.Handlers/Game/GameRouter.Gm.cs @@ -66,4 +66,11 @@ internal sealed partial class GameRouter { conn.Log.Info("[SECURITY] DavinciCode notify absorbed"); } + + [Handler(MethodId.GmDaVinciCode, HandlerPacketKind.Invoke)] + private static Task GmDaVinciCode(Connection conn, UxRpcMessage msg) + { + conn.Log.Info("[SECURITY] GmDaVinciCode invoke absorbed"); + return conn.ReturnEmptyOkAsync(msg); + } } diff --git a/Ananta.Server/Ananta.Handlers/Game/GameRouter.Movement.cs b/Ananta.Server/Ananta.Handlers/Game/GameRouter.Movement.cs index 7f1b884..86ed50a 100644 --- a/Ananta.Server/Ananta.Handlers/Game/GameRouter.Movement.cs +++ b/Ananta.Server/Ananta.Handlers/Game/GameRouter.Movement.cs @@ -66,6 +66,21 @@ internal sealed partial class GameRouter var unitId = state.ActiveSpiritUnitId != 0 ? state.ActiveSpiritUnitId : Profile.InitialUnitId; var templateId = state.ActiveSpiritTemplateId != 0 ? state.ActiveSpiritTemplateId : Profile.InitialSpiritTemplateId; + if (!state.CombatProfilePublished) + { + var weapon = CombatCodec.Weapon(templateId, state.ActiveWeaponInstanceId) + ?? CombatCodec.DefaultWeapon(templateId); + var style = ResolveWeaponStyle(state, weapon); + state.ActiveWeaponInstanceId = weapon.InstanceId; + state.ActiveFightStyleId = style.Id; + state.LastWeaponBySpirit[templateId] = weapon.InstanceId; + + await PublishWeaponSnapshot(ctx, unitId, templateId, weapon.InstanceId); + await ctx.NotifyAsync(MethodId.SyncSpiritSwitchWeaponAction, + CombatCodec.SpiritSwitchWeapon(unitId, weapon.InstanceId)); + await PublishSelectedWeaponProfile(ctx, unitId, templateId, weapon, style); + } + if (!state.AllBuildBuffsPublished) { await PublishSafeRuntimeBuffSnapshot4229938(ctx, unitId, templateId, "first-gameplay-movement"); @@ -78,6 +93,6 @@ internal sealed partial class GameRouter await ctx.NotifyAsync(MethodId.SyncGamePause, WorldCodec.GamePause(false)); state.MovementCapabilityPublished = true; state.FreeRoamReleased = true; - ctx.Session.Log.Info($"[GAMEPLAY-V7] first-movement post-load-buffs unit={unitId} template={templateId} actorPresentation=client-owned safeBuffs=true pause=false catalogBulk=false"); + ctx.Session.Log.Info($"[GAMEPLAY-V7] first-movement post-load-buffs unit={unitId} template={templateId} combatProfile=true safeBuffs=true pause=false catalogBulk=false"); } } diff --git a/Ananta.Server/Ananta.Handlers/Game/GameRouter.Profiles.cs b/Ananta.Server/Ananta.Handlers/Game/GameRouter.Profiles.cs index 6fa7f87..81acb81 100644 --- a/Ananta.Server/Ananta.Handlers/Game/GameRouter.Profiles.cs +++ b/Ananta.Server/Ananta.Handlers/Game/GameRouter.Profiles.cs @@ -85,8 +85,9 @@ internal sealed partial class GameRouter CombatCodec.SpiritLastUsedWeapon(templateId, weapon.InstanceId)); await ctx.NotifyAsync(MethodId.SyncSpiritSwitchWeaponAction, CombatCodec.SpiritSwitchWeapon(unitId, weapon.InstanceId)); + await PublishSelectedWeaponProfile(ctx, unitId, templateId, weapon, style); - ctx.Session.Log.Info($"[ACTOR-MIN] presentation unit={unitId} template={templateId} weapon={weapon.TemplateId}/{weapon.InstanceId} style={style.Id} reason={reason} combatModules=false skills=false"); + ctx.Session.Log.Info($"[ACTOR-MIN] presentation unit={unitId} template={templateId} weapon={weapon.TemplateId}/{weapon.InstanceId} style={style.Id} reason={reason} combatModules=true skills=true"); } async Task PublishWeaponSnapshot( @@ -175,11 +176,12 @@ internal sealed partial class GameRouter // Keep the opening/loading barrier on the proven small traversal capability set. // Never activate the entire BuffConfig as one player snapshot: it contains NPC/timeline actions. + var targetUnitId = state.ActiveSpiritUnitId != 0 ? state.ActiveSpiritUnitId : Profile.InitialUnitId; var firstInstanceId = state.NextBuffInstanceId; await ctx.NotifyAsync(MethodId.SyncUnitBuffList, - WorldCodec.UnitBuffList(Profile.InitialUnitId, WebTraversal.InitialCapabilityBuffIds, firstInstanceId)); + WorldCodec.UnitBuffList(targetUnitId, WebTraversal.InitialCapabilityBuffIds, firstInstanceId)); state.NextBuffInstanceId += (uint)WebTraversal.InitialCapabilityBuffIds.Count; - ctx.Session.Log.Info($"[CAPABILITY] initial unit={Profile.InitialUnitId} count={WebTraversal.InitialCapabilityBuffIds.Count} loadingSafe=true allBuffsDeferred=true"); + ctx.Session.Log.Info($"[CAPABILITY] initial unit={targetUnitId} count={WebTraversal.InitialCapabilityBuffIds.Count} loadingSafe=true allBuffsDeferred=true"); } } diff --git a/Ananta.Server/Ananta.Handlers/Game/GameRouter.cs b/Ananta.Server/Ananta.Handlers/Game/GameRouter.cs index fc43d8e..36206af 100644 --- a/Ananta.Server/Ananta.Handlers/Game/GameRouter.cs +++ b/Ananta.Server/Ananta.Handlers/Game/GameRouter.cs @@ -98,6 +98,7 @@ internal sealed partial class GameRouter MethodId.GmAddEnemyByPlayer, MethodId.GmTeleportXYZ, MethodId.DavinciCode, + MethodId.GmDaVinciCode, // Time of day: client-driven UI (accept + remember) + debug-panel slider push. MethodId.AskPassingTime, diff --git a/README.md b/README.md index cc800fb..ee23b9b 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,20 @@ | ฟีเจอร์ / ระบบ | สถานะ | รายละเอียดการทำงาน | | :--- | :---: | :--- | +| **Combat & Skill System (ระบบต่อสู้และสกิล)** | 🟢 ใช้งานได้สมบูรณ์ | ผูก Weapon Profile, Basic Attack Combo, Heavy Attack, Active Skill, Dodge, Unique Ultimate Skill และ Fight Resources (SP/Energy) ให้กับตัวละครทุกตัว (ทั้ง MC ชาย/หญิง และตัวละครทั้งหมด) ทันทีเมื่อเข้าเกมหรือสลับตัวละคร พร้อมประมวลผล Skill RPC และ Damage Hit Notification ในเซิร์ฟเวอร์ | | **Authentication & MPay Bypass** | 🟢 ใช้งานได้สมบูรณ์ | บายพาสล็อกอินผ่าน MPay JavaScript Bridge ใน `NtUniSdkBase.dll` อัตโนมัติ ไม่ต้องกรอกรหัสผ่าน | | **HTTPS Gateway & Multi-Domain Proxy** | 🟢 ใช้งานได้สมบูรณ์ | จำลอง SSL SNI บนพอร์ต 443/80 หลอกระบบอัปเดต และบีบ Contract MD5 7-column ไม่ให้ขึ้นปิดปรับปรุง | | **World Entry (V7 4-Step Handoff)** | 🟢 ใช้งานได้สมบูรณ์ | ส่งลำดับ `SyncLogicAgentEnter` -> `SyncManagedLogicAgent` -> `SyncRaidBattleUnitSpirit` -> `SyncPlayerCurrentSpirit` -> `SyncSceneLoadCompleted` เข้าสู่เมืองได้ 100% | -| **Character Switching (สลับตัวละคร)** | 🟢 ใช้งานได้สมบูรณ์ | สลับตัวละครได้ในฉากเดียวกัน พิกัดต่อเนื่อง และส่ง `isAgentSwitch: true` ทำให้มุมกล้อง Cinemachine ติดตามตัวละครใหม่ถูกต้อง ไม่หลุดลอย | -| **Traversal & Parkour (สลิง/ปีนป่าย)** | 🟢 ใช้งานได้สมบูรณ์ | ปลดล็อกบัฟเคลื่อนไหวใยแมงมุม (FeiSuo / Grapple Hook / Wall Rush / Dive Damage) ให้กับทุกตัวละคร | -| **Anti-Cheat Suppression** | 🟢 ใช้งานได้สมบูรณ์ | ดักปิด `DavinciReport` และ `DavinciMgr.CheckTimeScale` พร้อมดูดซับ `DavinciCode` ฝั่งเซิร์ฟเวอร์ หมดปัญหาสลิงเกี่ยวแล้ว Connection Lost | +| **Character Switching (สลับตัวละคร)** | 🟢 ใช้งานได้สมบูรณ์ | สลับตัวละครได้ในฉากเดียวกัน พิกัดต่อเนื่อง และส่ง `isAgentSwitch: true` ทำให้มุมกล้อง Cinemachine ติดตามตัวละครใหม่ถูกต้อง ไม่หลุดลอย พร้อมรีไอน์เชียลไลซ์สกิลของตัวละครใหม่ทันที | +| **Traversal & Parkour (สลิง/เกี่ยวต่อสู้/ปีนป่าย)** | 🟢 ใช้งานได้สมบูรณ์ | ปลดล็อกบัฟเคลื่อนไหวใยแมงมุม (FeiSuo / Grapple Hook / Wall Rush / Dive Damage) ให้กับทุกตัวละคร รองรับการใช้สลิงเกี่ยวต่อสู้โดยไม่หลุดจากการเชื่อมต่อ | +| **Anti-Cheat Suppression** | 🟢 ใช้งานได้สมบูรณ์ | ดักปิด `DavinciReport`, `DavinciMgr.CheckTimeScale` (แก้บัค Connection Lost เวลาสลิงเกี่ยวหรือเกิด Slow Motion) และดูดซับ `GmDaVinciCode` ฝั่งเซิร์ฟเวอร์ | +| **Camera & Cinemachine Handling** | 🟢 ใช้งานได้สมบูรณ์ | จัดการทรานซิชันมุมกล้อง Cinemachine ระหว่างการเคลื่อนไหว สลับตัวละคร และโหมดอิสระ ปิดการ Force Debug Photo ที่เคยกวนระบบกล้องเกมหลัก | | **RAM Warmup OOM Prevention** | 🟢 ใช้งานได้สมบูรณ์ | ปิดการสั่งคอมไพล์ 11,384 PSO Shaders รวดเดียว ลดเวลาโหลดจาก 40 วิเหลือ 0.01 วิ และไม่กินแรม 16-19GB จนเกมแครช | | **Retail Game Switches & All-Map Unlock** | 🟢 ใช้งานได้สมบูรณ์ | ปลดล็อกตู้เสื้อผ้า (Closet), กาชา (Gacha), ห้าง (Mall), โทรศัพท์ (Phone), แผนที่เปิดหมด หมอกควัน (Fog) ถูกเคลียร์ | | **Web Debug Panel (`:5809/debug`)** | 🟢 ใช้งานได้สมบูรณ์ | แดชบอร์ดเว็บสำหรับวาร์ปตามพิกัด (Teleport), ปรับเวลาสภาพอากาศ, เสกมอนเตอร์, และเปิดหน้าต่างวิดีโอ | -| **Safe Fastpatch Loader** | 🟢 ใช้งานได้สมบูรณ์ | ตัวสร้าง `fastpatch_4229938.zip` อัตโนมัติ ทุกคำสั่งถูกครอบด้วย `pcall` หมดปัญหาเกมค้างหน้า Initialize Client | -| **Monster Spawning (เสกมอนเตอร์)** | 🟡 กำลังพัฒนา / มีข้อจำกัด | เสกได้ผ่าน GM Console (`Alt+F1`) และ Debug Panel (`CMD:SPAWN_ENEMY`) แต่ขึ้นอยู่กับว่า Chunk ที่ยืนอยู่มี NavMesh โหลดพร้อมหรือไม่ | -| **Cutscene & Video Player** | 🟡 กำลังพัฒนา / มีข้อจำกัด | คำสั่ง `CMD:PLAY_CUTSCENE` สั่งเปิดหน้าต่าง `S_VIDEO_PLAYER_PANEL` และ Timeline ได้ถูกต้อง แต่ Cutscene ID บางตัวอาจไม่มีไฟล์วิดีโอใน Client Leak | +| **Safe Fastpatch Loader** | 🟢 ใช้งานได้สมบูรณ์ | ตัวสร้าง `fastpatch_4229938.zip` อัตโนมัติ ทุกคำสั่งถูกครอบด้วย `pcall` ป้องกันข้อความคำสั่งรั่วขึ้นหน้าจอ และหมดปัญหาเกมค้างหน้า Initialize Client | +| **Monster Spawning (เสกมอนเตอร์)** | 🟡 กำลังพัฒนา / ใช้งานได้ | เสกได้ผ่าน GM Console (`Alt+F1`) และ Debug Panel (`CMD:SPAWN_ENEMY`) ส่งคำสั่งเข้า `gCS.LuaUtils.AddEnemy` โดยตรงไม่ขึ้นเป็นตัวหนังสือบนจอ แต่การเกิดจะขึ้นอยู่กับ NavMesh ใน Chunk นั้นๆ | +| **Cutscene & Video Player** | 🟡 กำลังพัฒนา / ใช้งานได้ | คำสั่ง `CMD:PLAY_CUTSCENE` สั่งเปิดหน้าต่าง `S_VIDEO_PLAYER_PANEL` และ Timeline ได้ถูกต้อง ไม่ขึ้นเป็นตัวหนังสือบนจอ แต่ Cutscene ID บางตัวอาจไม่มีไฟล์วิดีโอใน Client Leak | | **Taffy Monowheel (塔菲摩托)** | 🟡 กำลังพัฒนา / มีข้อจำกัด | ลงทะเบียน RPC การขับขี่พื้นฐาน (`AskTaffyMotoEnterRush`, `AskGetOffMotor`, `OnTafeiMotorColliding`) ไว้แล้ว แต่ระบบฟิสิกส์การชนยังไม่สมบูรณ์เท่าตัวเอก | | **Gacha System (ตู้สุ่มตัวละคร)** | 🟡 กำลังพัฒนา / มีข้อจำกัด | หน้าร้านค้าเปิดตู้ได้และบายพาสวันหมดอายุ CBT แล้ว แต่ผลการสุ่มยังเป็นการให้ไอเทมแบบสุ่มตายตัว (ยังไม่มี Database เรทกาชาแบบเต็มระบบ) | | **Phone Customization (ตกแต่งมือถือ)** | 🟡 กำลังพัฒนา / มีข้อจำกัด | ปลดล็อกไอเทมเคสมือถือ วอลเปเปอร์ จี้ห้อย (`1..200`) ในหน่วยความจำ แต่ยังไม่บันทึกความเปลี่ยนแปลงข้าม Session แบบถาวร |