From 070a27b2cb1b056706140404689211edd2b57b40 Mon Sep 17 00:00:00 2001 From: shenlei Date: Thu, 24 Sep 2026 01:10:41 +0900 Subject: [PATCH] port: P3-d PythonTextTail + textTail module (1:1 40250) Copy PythonTextTail.cpp/PythonTextTailModule.cpp verbatim, drop the pending stand-in and the textTail module stub, register initTextTail() after initChat() and call CPythonTextTail::Initialize() in Create as 40250 does. Implement CScreen::ProjectPosition/UnprojectPosition (were stubs) with D3DXVec3Project/Unproject, and ApplicationStringTable_GetString(DWORD) over UserInterface.rc's English string table. port.login_flow projects the ground item's name tail in an ALT frame and picks it back by screen position. Co-Authored-By: Claude Opus 5.5 --- audit/history.jsonl | 1 + audit/port-map/EterLib/GrpScreen.cpp.json | 14 +- .../UserInterface/PythonTextTail.cpp.json | 248 ++++ .../PythonTextTailModule.cpp.json | 122 ++ .../UserInterface/UserInterface.cpp.json | 160 +-- docs/PORT-PLAN.md | 2 + extension/src/platform/EterLib/GrpScreen.cpp | 30 +- .../ScriptLib/GameplayModuleConstants.cpp | 6 - .../platform/ScriptLib/GameplayModules.cpp | 46 - .../src/platform/ScriptLib/PythonBoot.cpp | 1 + .../UserInterface/PythonApplication.cpp | 5 +- .../platform/UserInterface/UserInterface.cpp | 35 + .../pending/UserInterface/PythonTextTail.cpp | 75 -- .../src/port/UserInterface/PythonTextTail.cpp | 1029 +++++++++++++++++ .../UserInterface/PythonTextTailModule.cpp | 235 ++++ extension/src/port/UserInterface/StdAfx.h | 7 + extension/src/port/common/D3DXMath.cpp | 39 + extension/src/port/common/D3DXMath.h | 5 + extension/tests/port_login_flow_test.cpp | 25 + 19 files changed, 1874 insertions(+), 211 deletions(-) create mode 100644 audit/port-map/UserInterface/PythonTextTail.cpp.json create mode 100644 audit/port-map/UserInterface/PythonTextTailModule.cpp.json delete mode 100644 extension/src/platform/pending/UserInterface/PythonTextTail.cpp create mode 100644 extension/src/port/UserInterface/PythonTextTail.cpp create mode 100644 extension/src/port/UserInterface/PythonTextTailModule.cpp diff --git a/audit/history.jsonl b/audit/history.jsonl index e05fcf04..d49c7379 100644 --- a/audit/history.jsonl +++ b/audit/history.jsonl @@ -533,3 +533,4 @@ {"time": "2026-09-24T06:00:00Z", "event": "port_round", "unit": "PORT-PLAN batch 2 P3-a: ItemData, ItemManager, PythonNonPlayer + LoadLocaleData", "ported": ["GameLib/ItemData.cpp", "GameLib/ItemManager.cpp", "UserInterface/PythonNonPlayer.cpp", "UserInterface/PythonNonPlayerModule.cpp"], "adapted": ["UserInterface/PythonApplication.cpp:LoadLocaleData"], "divergent": [], "needs_live": [], "tests": ["port.login_flow 3c: item 19 Sword+9 type 1 refine 9, mob 101 Wild Dog", "port.login_live 192.168.21.203: same", "ctest all 19/19, python_game_render_test offline PASS", "port_map.py check: 0 errors"], "todo": ["P3-b PythonItem + PythonItemModule", "CPythonSkill (RegisterSkillDesc/Table) still pending"]} {"time": "2026-09-24T07:00:00Z", "event": "port_round", "unit": "PORT-PLAN batch 2 P3-b: PythonItem + PythonItemModule", "ported": ["UserInterface/PythonItem.cpp", "UserInterface/PythonItemModule.cpp"], "adapted": ["UserInterface/PythonItemModule.cpp:itemGetIconImage", "GameLib/MapManager.cpp:CMapManager::GetNormal"], "divergent": [], "needs_live": [], "tests": ["port.login_flow: fake GC_ITEM_GROUND_ADD vnum 19 + ownership, player.PickCloseItem -> CG_ITEM_PICKUP -> GC_ITEM_GROUND_DEL removes it", "port.login_flow: item.SelectItem(19) name/type/subtype/value", "port.login_live 192.168.21.203: passes (icon pointer truncation segfault fixed)", "ctest all 19/19, python_game_render_test offline PASS", "port_map.py check: 0 errors"], "todo": ["P3-c PythonExchange", "PythonTextTail item tails (Register/Delete/SetOwner/Pick still stubs)", "CEffectManager (dropitem.mse)"]} {"time": "2026-09-24T08:00:00Z", "event": "port_round", "unit": "PORT-PLAN batch 2 P3-c: PythonExchange + PythonExchangeModule", "ported": ["UserInterface/PythonExchange.cpp", "UserInterface/PythonExchangeModule.cpp"], "adapted": [], "divergent": [], "needs_live": ["exchange with a real second player"], "tests": ["port.login_flow: net.SendExchangeStartPacket -> fake GC_EXCHANGE START/ITEM_ADD/GOLD_ADD fills target side, uiexchange opens; SendExchangeExitPacket -> END closes", "port.login_live 192.168.21.203: passes", "ctest all 19/19, python_game_render_test offline PASS", "port_map.py check: 0 errors"], "todo": ["PythonTextTail item tails", "PythonMiniMap", "CEffectManager", "CPythonSkill"]} +{"time": "2026-09-24T09:00:00Z", "event": "port_round", "unit": "PORT-PLAN batch 2 P3-d: PythonTextTail + textTail module", "ported": ["UserInterface/PythonTextTail.cpp", "UserInterface/PythonTextTailModule.cpp"], "adapted": ["EterLib/GrpScreen.cpp:CScreen::ProjectPosition", "EterLib/GrpScreen.cpp:CScreen::UnprojectPosition", "UserInterface/UserInterface.cpp:ApplicationStringTable_GetString", "UserInterface/UserInterface.cpp:ApplicationStringTable_GetStringz"], "divergent": [], "needs_live": [], "tests": ["port.login_flow: ground item name tail registered, ALT frame projects it, CPythonTextTail::Pick returns the item VID, deleted on pickup", "port.login_live 192.168.21.203: passes", "ctest all 19/19, python_game_render_test offline PASS", "port_map.py check: 0 errors"], "todo": ["PythonMiniMap", "CEffectManager", "CPythonSkill"]} diff --git a/audit/port-map/EterLib/GrpScreen.cpp.json b/audit/port-map/EterLib/GrpScreen.cpp.json index 3a3aa51c..18f430ae 100644 --- a/audit/port-map/EterLib/GrpScreen.cpp.json +++ b/audit/port-map/EterLib/GrpScreen.cpp.json @@ -115,10 +115,20 @@ "status": "TODO" }, "CScreen::ProjectPosition": { - "status": "TODO" + "status": "ADAPTED", + "impl": [ + "extension/src/platform/EterLib/GrpScreen.cpp:CScreen::ProjectPosition" + ], + "note": "Body verbatim; D3DXVec3Project/Unproject come from port/common/D3DXMath.cpp (D3DX semantics, null matrix = identity).", + "test": "extension/tests/port_login_flow_test.cpp ground-item step (port.login_flow: ALT frame projects the item name tail, textTail Pick returns the ground item VID)" }, "CScreen::UnprojectPosition": { - "status": "TODO" + "status": "ADAPTED", + "impl": [ + "extension/src/platform/EterLib/GrpScreen.cpp:CScreen::UnprojectPosition" + ], + "note": "Body verbatim; D3DXVec3Project/Unproject come from port/common/D3DXMath.cpp (D3DX semantics, null matrix = identity).", + "test": "extension/tests/port_login_flow_test.cpp ground-item step (port.login_flow: ALT frame projects the item name tail, textTail Pick returns the ground item VID)" }, "CScreen::SetColorOperation": { "status": "TODO" diff --git a/audit/port-map/UserInterface/PythonTextTail.cpp.json b/audit/port-map/UserInterface/PythonTextTail.cpp.json new file mode 100644 index 00000000..90a7113a --- /dev/null +++ b/audit/port-map/UserInterface/PythonTextTail.cpp.json @@ -0,0 +1,248 @@ +{ + "reference": "UserInterface/PythonTextTail.cpp", + "reference_sha256": "30392fd529a043f638bc7ada3e5177f78a9424857911b2903f3f9d7cec7aba7a", + "priority": "P3", + "contracts": [], + "functions": { + "TextTail_GetLivingTime": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:TextTail_GetLivingTime" + ] + }, + "TextTail_SetLivingTime": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:TextTail_SetLivingTime" + ] + }, + "CPythonTextTail::GetInfo": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::GetInfo" + ] + }, + "CPythonTextTail::UpdateAllTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::UpdateAllTextTail" + ] + }, + "CPythonTextTail::UpdateShowingTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::UpdateShowingTextTail" + ] + }, + "CPythonTextTail::UpdateTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::UpdateTextTail" + ] + }, + "CPythonTextTail::ArrangeTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::ArrangeTextTail" + ] + }, + "CPythonTextTail::Render": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::Render" + ] + }, + "CPythonTextTail::RenderTextTailBox": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::RenderTextTailBox" + ] + }, + "CPythonTextTail::RenderTextTailName": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::RenderTextTailName" + ] + }, + "CPythonTextTail::HideAllTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::HideAllTextTail" + ] + }, + "CPythonTextTail::UpdateDistance": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::UpdateDistance" + ] + }, + "CPythonTextTail::ShowAllTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::ShowAllTextTail" + ] + }, + "CPythonTextTail::ShowCharacterTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::ShowCharacterTextTail" + ] + }, + "CPythonTextTail::ShowItemTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::ShowItemTextTail" + ] + }, + "CPythonTextTail::isIn": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::isIn" + ] + }, + "CPythonTextTail::RegisterCharacterTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::RegisterCharacterTextTail" + ] + }, + "CPythonTextTail::RegisterItemTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::RegisterItemTextTail" + ] + }, + "CPythonTextTail::RegisterChatTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::RegisterChatTail" + ] + }, + "CPythonTextTail::RegisterInfoTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::RegisterInfoTail" + ] + }, + "CPythonTextTail::GetTextTailPosition": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::GetTextTailPosition" + ] + }, + "CPythonTextTail::IsChatTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::IsChatTextTail" + ] + }, + "CPythonTextTail::SetCharacterTextTailColor": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::SetCharacterTextTailColor" + ] + }, + "CPythonTextTail::SetItemTextTailOwner": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::SetItemTextTailOwner" + ] + }, + "CPythonTextTail::DeleteCharacterTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::DeleteCharacterTextTail" + ] + }, + "CPythonTextTail::DeleteItemTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::DeleteItemTextTail" + ] + }, + "CPythonTextTail::RegisterTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::RegisterTextTail" + ] + }, + "CPythonTextTail::DeleteTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::DeleteTextTail" + ] + }, + "CPythonTextTail::Pick": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::Pick" + ] + }, + "CPythonTextTail::SelectItemName": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::SelectItemName" + ] + }, + "CPythonTextTail::AttachTitle": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::AttachTitle" + ] + }, + "CPythonTextTail::DetachTitle": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::DetachTitle" + ] + }, + "CPythonTextTail::EnablePKTitle": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::EnablePKTitle" + ] + }, + "CPythonTextTail::AttachLevel": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::AttachLevel" + ] + }, + "CPythonTextTail::DetachLevel": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::DetachLevel" + ] + }, + "CPythonTextTail::Initialize": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::Initialize" + ] + }, + "CPythonTextTail::Destroy": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::Destroy" + ] + }, + "CPythonTextTail::Clear": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::Clear" + ] + }, + "CPythonTextTail::CPythonTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::CPythonTextTail" + ] + }, + "CPythonTextTail::~CPythonTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTail.cpp:CPythonTextTail::~CPythonTextTail" + ] + } + } +} diff --git a/audit/port-map/UserInterface/PythonTextTailModule.cpp.json b/audit/port-map/UserInterface/PythonTextTailModule.cpp.json new file mode 100644 index 00000000..6459b297 --- /dev/null +++ b/audit/port-map/UserInterface/PythonTextTailModule.cpp.json @@ -0,0 +1,122 @@ +{ + "reference": "UserInterface/PythonTextTailModule.cpp", + "reference_sha256": "bd7149ddc99091064e9e011894562e6adae137efec7ec10c42400c2b23bba796", + "priority": "P4", + "contracts": [], + "functions": { + "textTailClear": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailClear" + ] + }, + "textTailUpdateAllTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailUpdateAllTextTail" + ] + }, + "textTailUpdateShowingTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailUpdateShowingTextTail" + ] + }, + "textTailRender": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailRender" + ] + }, + "textTailRegisterCharacterTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailRegisterCharacterTextTail" + ] + }, + "textTailGetPosition": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailGetPosition" + ] + }, + "textTailIsChat": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailIsChat" + ] + }, + "textTailRegisterChatTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailRegisterChatTail" + ] + }, + "textTailRegisterInfoTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailRegisterInfoTail" + ] + }, + "textTailAttachTitle": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailAttachTitle" + ] + }, + "textTailShowCharacterTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailShowCharacterTextTail" + ] + }, + "textTailShowItemTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailShowItemTextTail" + ] + }, + "textTailArrangeTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailArrangeTextTail" + ] + }, + "textTailHideAllTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailHideAllTextTail" + ] + }, + "textTailShowAllTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailShowAllTextTail" + ] + }, + "textTailPick": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailPick" + ] + }, + "textTailSelectItemName": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailSelectItemName" + ] + }, + "textTailEnablePKTitle": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:textTailEnablePKTitle" + ] + }, + "initTextTail": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonTextTailModule.cpp:initTextTail" + ] + } + } +} diff --git a/audit/port-map/UserInterface/UserInterface.cpp.json b/audit/port-map/UserInterface/UserInterface.cpp.json index 422f23e5..2654bd65 100644 --- a/audit/port-map/UserInterface/UserInterface.cpp.json +++ b/audit/port-map/UserInterface/UserInterface.cpp.json @@ -1,78 +1,88 @@ { - "reference": "UserInterface/UserInterface.cpp", - "reference_sha256": "beed987e4b1de627acafe7ea6f5fde989b8f4b33c8643fe2abb0f7152ea4a9eb", - "priority": "P4", - "contracts": [], - "functions": { - "ApplicationSetErrorString": { - "status": "TODO" - }, - "CheckPythonLibraryFilenames": { - "status": "TODO" - }, - "ApplicationStringTable_Initialize": { - "status": "TODO" - }, - "ApplicationStringTable_GetString": { - "status": "TODO" - }, - "ApplicationStringTable_GetStringz": { - "status": "TODO" - }, - "PackInitialize": { - "impl": [ - "extension/src/platform/UserInterface/UserInterface.cpp:PackInitialize" - ], - "status": "ADAPTED", - "note": "platform PackInitialize: Index pack names resolved to the on-disk .eix spelling case-insensitively; no LogBoxf; CTextFileLoader::SetCacheMode / CSoundData::SetPackMode not called (units not ported, neither affects pack registration)", - "test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects); extension/tests/proto_test.cpp (40250 locale/en item_proto/mob_proto through pack://)" - }, - "RunMainScript": { - "impl": [ - "extension/src/platform/ScriptLib/PythonBoot.cpp:PythonBoot::RunMainScript" - ], - "status": "ADAPTED", - "note": "40250 runs it on Main()'s stack and takes the CPythonLauncher by reference; Godot owns this process, so PythonBoot::Start holds the launcher and RunMainScript takes only the command line plus a std::string* for the text 40250 shows in LogBox's message box. Of the 37 module initializers only initpack() is called — the rest are 2V slices, which is why system.py still stops at its first import. RegisterCommandLine passes the line through unchanged: the -cs/-ncs auto-login decoding needs EterBase/Utils.cpp SplitLine and EterLib/Util.cpp base64_decode, both still TODO (批次 2V1). '--pause-before-create-window' (system(\"pause\")) and the XTrap init check are dropped: no console and no XTrap on this platform.", - "test": "project/python_host_test.gd (godot --headless --path project --script python_host_test.gd) and the same check through MT_TEST_MODE=python in project/client_main.gd -> system.py stops at ImportError: No module named app, the same point as ctest port.python_launcher" - }, - "Main": { - "status": "TODO" - }, - "CreateMetin2GameMutex": { - "status": "TODO" - }, - "DestroyMetin2GameMutex": { - "status": "TODO" - }, - "__ErrorPythonLibraryIsNotExist": { - "status": "TODO" - }, - "__IsTimeStampOption": { - "status": "TODO" - }, - "__PrintTimeStamp": { - "status": "TODO" - }, - "__IsLocaleOption": { - "status": "TODO" - }, - "__IsLocaleVersion": { - "status": "TODO" - }, - "__IsOpenIDAuthKeyOption": { - "status": "TODO" - }, - "__IsOpenIDTestOption": { - "status": "TODO" - }, - "WinMain": { - "status": "TODO" - }, - "GrannyError": { - "status": "TODO" - }, - "Setup": { - "status": "TODO" + "reference": "UserInterface/UserInterface.cpp", + "reference_sha256": "beed987e4b1de627acafe7ea6f5fde989b8f4b33c8643fe2abb0f7152ea4a9eb", + "priority": "P4", + "contracts": [], + "functions": { + "ApplicationSetErrorString": { + "status": "TODO" + }, + "CheckPythonLibraryFilenames": { + "status": "TODO" + }, + "ApplicationStringTable_Initialize": { + "status": "TODO" + }, + "ApplicationStringTable_GetString": { + "status": "ADAPTED", + "impl": [ + "extension/src/platform/UserInterface/UserInterface.cpp:ApplicationStringTable_GetString" + ], + "note": "Only the (DWORD) overload: LoadString becomes a switch over UserInterface.rc's LANG_ENGLISH/SUBLANG_ENGLISH_US STRINGTABLE. The (DWORD, LPCSTR) metin2client.dat overload is declared but not defined (no caller ported yet).", + "test": "extension/tests/port_login_flow_test.cpp ground-item step (port.login_flow: ALT frame projects the item name tail, textTail Pick returns the ground item VID); PythonTextTail::SetItemTextTailOwner uses IDS_POSSESSIVE_MORPHENE" + }, + "ApplicationStringTable_GetStringz": { + "status": "ADAPTED", + "impl": [ + "extension/src/platform/UserInterface/UserInterface.cpp:ApplicationStringTable_GetStringz" + ], + "note": "Only the (DWORD) overload: LoadString becomes a switch over UserInterface.rc's LANG_ENGLISH/SUBLANG_ENGLISH_US STRINGTABLE. The (DWORD, LPCSTR) metin2client.dat overload is declared but not defined (no caller ported yet).", + "test": "extension/tests/port_login_flow_test.cpp ground-item step (port.login_flow: ALT frame projects the item name tail, textTail Pick returns the ground item VID); PythonTextTail::SetItemTextTailOwner uses IDS_POSSESSIVE_MORPHENE" + }, + "PackInitialize": { + "impl": [ + "extension/src/platform/UserInterface/UserInterface.cpp:PackInitialize" + ], + "status": "ADAPTED", + "note": "platform PackInitialize: Index pack names resolved to the on-disk .eix spelling case-insensitively; no LogBoxf; CTextFileLoader::SetCacheMode / CSoundData::SetPackMode not called (units not ported, neither affects pack registration)", + "test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects); extension/tests/proto_test.cpp (40250 locale/en item_proto/mob_proto through pack://)" + }, + "RunMainScript": { + "impl": [ + "extension/src/platform/ScriptLib/PythonBoot.cpp:PythonBoot::RunMainScript" + ], + "status": "ADAPTED", + "note": "40250 runs it on Main()'s stack and takes the CPythonLauncher by reference; Godot owns this process, so PythonBoot::Start holds the launcher and RunMainScript takes only the command line plus a std::string* for the text 40250 shows in LogBox's message box. Of the 37 module initializers only initpack() is called — the rest are 2V slices, which is why system.py still stops at its first import. RegisterCommandLine passes the line through unchanged: the -cs/-ncs auto-login decoding needs EterBase/Utils.cpp SplitLine and EterLib/Util.cpp base64_decode, both still TODO (批次 2V1). '--pause-before-create-window' (system(\"pause\")) and the XTrap init check are dropped: no console and no XTrap on this platform.", + "test": "project/python_host_test.gd (godot --headless --path project --script python_host_test.gd) and the same check through MT_TEST_MODE=python in project/client_main.gd -> system.py stops at ImportError: No module named app, the same point as ctest port.python_launcher" + }, + "Main": { + "status": "TODO" + }, + "CreateMetin2GameMutex": { + "status": "TODO" + }, + "DestroyMetin2GameMutex": { + "status": "TODO" + }, + "__ErrorPythonLibraryIsNotExist": { + "status": "TODO" + }, + "__IsTimeStampOption": { + "status": "TODO" + }, + "__PrintTimeStamp": { + "status": "TODO" + }, + "__IsLocaleOption": { + "status": "TODO" + }, + "__IsLocaleVersion": { + "status": "TODO" + }, + "__IsOpenIDAuthKeyOption": { + "status": "TODO" + }, + "__IsOpenIDTestOption": { + "status": "TODO" + }, + "WinMain": { + "status": "TODO" + }, + "GrannyError": { + "status": "TODO" + }, + "Setup": { + "status": "TODO" + } } - } } diff --git a/docs/PORT-PLAN.md b/docs/PORT-PLAN.md index 0b263a79..faebcae5 100644 --- a/docs/PORT-PLAN.md +++ b/docs/PORT-PLAN.md @@ -440,6 +440,8 @@ extension/third_party/cpython-2.7.18/ # 静态库(2P 批次 P3-c(2026-09-24):`PythonExchange.cpp` 与 `PythonExchangeModule.cpp` 原样移植(无手改),删除 pending 替身与 exchange 模块桩,`initTrade()` 按 40250 顺序在 `initNonPlayer()` 之后。假服按 `CInputMain::Exchange` 应答:`CG_EXCHANGE` START 回 `GC_EXCHANGE` START(arg1 = 请求的 vid)+ 对方 ITEM_ADD(vnum 19,格 0)+ GOLD_ADD 1000,CANCEL 回 END;`port.login_flow` 用 `net.SendExchangeStartPacket` 开交易,验证 `exchange.isTrading/GetItemVnumFromTarget/GetItemCountFromTarget/GetElkFromTarget`,uiexchange 打开后 `net.SendExchangeExitPacket` 收到 END 关闭。注意:40250 `SendExchangeExitPacket` 的 arg1 未初始化(原样保留,假服不记录它);`uiexchange.CloseDialog` 调 `wndMgr.OnceIgnoreMouseLeftButtonUpEvent()`,测试先消耗这次被忽略的左键抬起,否则下一次点地的抬起被吞、角色一直按住行走。下一步 `PythonTextTail`(物品名牌)与 `PythonMiniMap`。 + P3-d(2026-09-24):`PythonTextTail.cpp` 与 `PythonTextTailModule.cpp` 原样移植(无手改),删除 pending 替身(含 P3-b 的物品名牌桩)、textTail 模块桩与 `TextTail_SetLivingTime` 桩,`initTextTail()` 按 40250 顺序在 `initChat()` 之后,平台 `CPythonApplication::Create` 按原序调 `CPythonTextTail::Initialize()`。连带补齐:`CScreen::ProjectPosition/UnprojectPosition` 原样实现(此前是桩,名牌永远停在 -100,-100),`D3DXMath` 补 `D3DXVec3Project/Unproject`(D3DX 语义);`ApplicationStringTable_GetString(z)(DWORD)` 在平台 `UserInterface.cpp` 以 UserInterface.rc 英文 STRINGTABLE 代替 `LoadString`(物品归属名 "'s"),记 ADAPTED。`port.login_flow` 验证:假服地面物品注册了物品名牌(ItemTail Map 1),按住 ALT 一帧后 `game.GameWindow.OnRender` 在 `app.RenderGame` 之后投影名牌,`CPythonTextTail::Pick` 在屏幕上找回该物品 VID;拾取后名牌删除。下一步 `PythonMiniMap`。 + 每个切片都必须同时跑 legacy/port 双路回归;只有 2V3 稳定并把默认路由切到 `port` 后,才能删除总开关和剩余 legacy 宿主。 ## 5. 数据源切换:m2dev assets → 40250 diff --git a/extension/src/platform/EterLib/GrpScreen.cpp b/extension/src/platform/EterLib/GrpScreen.cpp index ddd98b24..a9af8190 100644 --- a/extension/src/platform/EterLib/GrpScreen.cpp +++ b/extension/src/platform/EterLib/GrpScreen.cpp @@ -298,19 +298,37 @@ auto CScreen::GetPickingPosition(float, float *, float *, float *) -> void MT_PLATFORM_STUB(); } -auto CScreen::ProjectPosition(float, float, float, float *, float *) -> void +// 40250 GrpScreen.cpp:749-779. +void CScreen::ProjectPosition(float x, float y, float z, float * pfX, float * pfY) { - MT_PLATFORM_STUB(); + D3DXVECTOR3 Input(x, y, z); + D3DXVECTOR3 Output; + D3DXVec3Project(&Output, &Input, &ms_Viewport, &ms_matProj, &ms_matView, &ms_matWorld); + + *pfX = Output.x; + *pfY = Output.y; } -auto CScreen::ProjectPosition(float, float, float, float *, float *, float *) -> void +void CScreen::ProjectPosition(float x, float y, float z, float * pfX, float * pfY, float * pfZ) { - MT_PLATFORM_STUB(); + D3DXVECTOR3 Input(x, y, z); + D3DXVECTOR3 Output; + D3DXVec3Project(&Output, &Input, &ms_Viewport, &ms_matProj, &ms_matView, &ms_matWorld); + + *pfX = Output.x; + *pfY = Output.y; + *pfZ = Output.z; } -auto CScreen::UnprojectPosition(float, float, float, float *, float *, float *) -> void +void CScreen::UnprojectPosition(float x, float y, float z, float * pfX, float * pfY, float * pfZ) { - MT_PLATFORM_STUB(); + D3DXVECTOR3 Input(x, y, z); + D3DXVECTOR3 Output; + D3DXVec3Unproject(&Output, &Input, &ms_Viewport, &ms_matProj, &ms_matView, &ms_matWorld); + + *pfX = Output.x; + *pfY = Output.y; + *pfZ = Output.z; } auto CScreen::IsLostDevice() -> BOOL diff --git a/extension/src/platform/ScriptLib/GameplayModuleConstants.cpp b/extension/src/platform/ScriptLib/GameplayModuleConstants.cpp index 2f47d251..f61e8727 100644 --- a/extension/src/platform/ScriptLib/GameplayModuleConstants.cpp +++ b/extension/src/platform/ScriptLib/GameplayModuleConstants.cpp @@ -188,12 +188,6 @@ void AddStubConstants_systemSetting(PyObject* poModule) PyModule_AddIntConstant(poModule, "WINDOW_CHAT", CPythonSystem::WINDOW_CHAT); } -// 40250 UserInterface/PythonTextTailModule.cpp -void AddStubConstants_textTail(PyObject* poModule) -{ - (void) poModule; -} - // 40250 UserInterface/PythonNetworkDatagramModule.cpp void AddStubConstants_udp(PyObject* poModule) { diff --git a/extension/src/platform/ScriptLib/GameplayModules.cpp b/extension/src/platform/ScriptLib/GameplayModules.cpp index 2995245e..dfe2e6dd 100644 --- a/extension/src/platform/ScriptLib/GameplayModules.cpp +++ b/extension/src/platform/ScriptLib/GameplayModules.cpp @@ -19,7 +19,6 @@ void AddStubConstants_shop(PyObject* poModule); void AddStubConstants_skill(PyObject* poModule); void AddStubConstants_snd(PyObject* poModule); void AddStubConstants_systemSetting(PyObject* poModule); -void AddStubConstants_textTail(PyObject* poModule); void AddStubConstants_udp(PyObject* poModule); namespace { @@ -691,46 +690,6 @@ PyMethodDef methods_systemSetting[] = { { "SetShadowLevel", stub_systemSetting_SetShadowLevel, METH_VARARGS }, { NULL, NULL } }; -// 40250 UserInterface/PythonTextTailModule.cpp: textTail export names. -PyObject* stub_textTail_Clear(PyObject*, PyObject*) { return stub_call_named("textTail.Clear", Py_BuildNone()); } -PyObject* stub_textTail_UpdateAllTextTail(PyObject*, PyObject*) { return stub_call_named("textTail.UpdateAllTextTail", Py_BuildNone()); } -PyObject* stub_textTail_UpdateShowingTextTail(PyObject*, PyObject*) { return stub_call_named("textTail.UpdateShowingTextTail", Py_BuildNone()); } -PyObject* stub_textTail_Render(PyObject*, PyObject*) { return stub_call_named("textTail.Render", Py_BuildNone()); } -PyObject* stub_textTail_ShowCharacterTextTail(PyObject*, PyObject*) { return stub_call_named("textTail.ShowCharacterTextTail", Py_BuildNone()); } -PyObject* stub_textTail_ShowItemTextTail(PyObject*, PyObject*) { return stub_call_named("textTail.ShowItemTextTail", Py_BuildNone()); } -PyObject* stub_textTail_GetPosition(PyObject*, PyObject*) { return stub_call_named("textTail.GetPosition", Py_BuildValue("fff", 0.0, 0.0, 0.0)); } -PyObject* stub_textTail_IsChat(PyObject*, PyObject*) { return stub_call_named("textTail.IsChat", Py_BuildValue("i", 0)); } -PyObject* stub_textTail_ArrangeTextTail(PyObject*, PyObject*) { return stub_call_named("textTail.ArrangeTextTail", Py_BuildNone()); } -PyObject* stub_textTail_HideAllTextTail(PyObject*, PyObject*) { return stub_call_named("textTail.HideAllTextTail", Py_BuildNone()); } -PyObject* stub_textTail_ShowAllTextTail(PyObject*, PyObject*) { return stub_call_named("textTail.ShowAllTextTail", Py_BuildNone()); } -PyObject* stub_textTail_Pick(PyObject*, PyObject*) { return stub_call_named("textTail.Pick", Py_BuildValue("i", 0)); } -PyObject* stub_textTail_SelectItemName(PyObject*, PyObject*) { return stub_call_named("textTail.SelectItemName", Py_BuildNone()); } -PyObject* stub_textTail_EnablePKTitle(PyObject*, PyObject*) { return stub_call_named("textTail.EnablePKTitle", Py_BuildNone()); } -PyObject* stub_textTail_RegisterCharacterTextTail(PyObject*, PyObject*) { return stub_call_named("textTail.RegisterCharacterTextTail", Py_BuildNone()); } -PyObject* stub_textTail_RegisterChatTail(PyObject*, PyObject*) { return stub_call_named("textTail.RegisterChatTail", Py_BuildNone()); } -PyObject* stub_textTail_RegisterInfoTail(PyObject*, PyObject*) { return stub_call_named("textTail.RegisterInfoTail", Py_BuildNone()); } -PyObject* stub_textTail_AttachTitle(PyObject*, PyObject*) { return stub_call_named("textTail.AttachTitle", Py_BuildNone()); } -PyMethodDef methods_textTail[] = { - { "Clear", stub_textTail_Clear, METH_VARARGS }, - { "UpdateAllTextTail", stub_textTail_UpdateAllTextTail, METH_VARARGS }, - { "UpdateShowingTextTail", stub_textTail_UpdateShowingTextTail, METH_VARARGS }, - { "Render", stub_textTail_Render, METH_VARARGS }, - { "ShowCharacterTextTail", stub_textTail_ShowCharacterTextTail, METH_VARARGS }, - { "ShowItemTextTail", stub_textTail_ShowItemTextTail, METH_VARARGS }, - { "GetPosition", stub_textTail_GetPosition, METH_VARARGS }, - { "IsChat", stub_textTail_IsChat, METH_VARARGS }, - { "ArrangeTextTail", stub_textTail_ArrangeTextTail, METH_VARARGS }, - { "HideAllTextTail", stub_textTail_HideAllTextTail, METH_VARARGS }, - { "ShowAllTextTail", stub_textTail_ShowAllTextTail, METH_VARARGS }, - { "Pick", stub_textTail_Pick, METH_VARARGS }, - { "SelectItemName", stub_textTail_SelectItemName, METH_VARARGS }, - { "EnablePKTitle", stub_textTail_EnablePKTitle, METH_VARARGS }, - { "RegisterCharacterTextTail", stub_textTail_RegisterCharacterTextTail, METH_VARARGS }, - { "RegisterChatTail", stub_textTail_RegisterChatTail, METH_VARARGS }, - { "RegisterInfoTail", stub_textTail_RegisterInfoTail, METH_VARARGS }, - { "AttachTitle", stub_textTail_AttachTitle, METH_VARARGS }, - { NULL, NULL } -}; // 40250 UserInterface/PythonNetworkDatagramModule.cpp: udp export names. PyObject* stub_udp_Enable(PyObject*, PyObject*) { return stub_call_named("udp.Enable", Py_BuildNone()); } PyObject* stub_udp_Disable(PyObject*, PyObject*) { return stub_call_named("udp.Disable", Py_BuildNone()); } @@ -812,11 +771,6 @@ void init_2v0_gameplay_stubs() { PyModule_AddIntConstant(module, "__port_stub__", 1); AddStubConstants_systemSetting(module); } - { - PyObject* module = Py_InitModule("textTail", methods_textTail); - PyModule_AddIntConstant(module, "__port_stub__", 1); - AddStubConstants_textTail(module); - } { PyObject* module = Py_InitModule("udp", methods_udp); PyModule_AddIntConstant(module, "__port_stub__", 1); diff --git a/extension/src/platform/ScriptLib/PythonBoot.cpp b/extension/src/platform/ScriptLib/PythonBoot.cpp index f7b685b6..92b8a8ab 100644 --- a/extension/src/platform/ScriptLib/PythonBoot.cpp +++ b/extension/src/platform/ScriptLib/PythonBoot.cpp @@ -351,6 +351,7 @@ bool run_main_script_body(const char* lpCmdLine, std::string* error) initNonPlayer(); initTrade(); initChat(); + initTextTail(); initnet(); initeffect(); initfly(); diff --git a/extension/src/platform/UserInterface/PythonApplication.cpp b/extension/src/platform/UserInterface/PythonApplication.cpp index aa6067b2..b18667a5 100644 --- a/extension/src/platform/UserInterface/PythonApplication.cpp +++ b/extension/src/platform/UserInterface/PythonApplication.cpp @@ -8,6 +8,7 @@ #include "UserInterface/PythonNetworkStream.h" #include "UserInterface/AccountConnector.h" #include "UserInterface/PythonItem.h" +#include "UserInterface/PythonTextTail.h" #include "UserInterface/PythonSkill.h" #include "UserInterface/PythonNonPlayer.h" #include "GameLib/ItemManager.h" @@ -124,6 +125,9 @@ bool CPythonApplication::Create(PyObject*, const char*, int, int, int) // 40250 m_pyItem.Create() (PythonApplication.cpp:1253): registers dropitem.mse with CEffectManager, // still a platform stub. CPythonItem::Instance().Create(); + + // 40250 PythonApplication.cpp:1260, after DefaultFont_Startup and the IME. + CPythonTextTail::Instance().Initialize(); return true; } // 40250 PythonApplication.cpp:105 @@ -404,7 +408,6 @@ bool LoadLocaleData(const char* localePath) NANOEND return true; } -void TextTail_SetLivingTime(long) { MT_PLATFORM_STUB(); } namespace { // The supplied 40250 Client/locale.cfg selects "en" (code page 1252). // Keep these mutable for app.ForceSetLocale, as Locale.cpp does. diff --git a/extension/src/platform/UserInterface/UserInterface.cpp b/extension/src/platform/UserInterface/UserInterface.cpp index 03bc36f4..a67423ed 100644 --- a/extension/src/platform/UserInterface/UserInterface.cpp +++ b/extension/src/platform/UserInterface/UserInterface.cpp @@ -26,6 +26,41 @@ // 40250 UserInterface.cpp:49. bool __IS_TEST_SERVER_MODE__=false; +// 40250 UserInterface.cpp:98. PORT: LoadString reads the executable's STRINGTABLE; the one the supplied English +// client resolves is UserInterface.rc's LANG_ENGLISH, SUBLANG_ENGLISH_US section (ids from resource.h). +namespace +{ +const char* LoadAppString(DWORD dwID) +{ + switch (dwID) + { + case 1: return "Metin 2"; // IDS_APP_NAME + case 2: return "'s"; // IDS_POSSESSIVE_MORPHENE + case 3: return "IDS_WARN_BAD_DRIVER"; // IDS_WARN_BAD_DRIVER + case 4: return "IDS_WARN_NO_TNL"; // IDS_WARN_NO_TNL + case 5: return "Cannot read %s file"; // IDS_ERR_CANNOT_READ_FILE + case 6: return "File '%s' is not latest version. Please launch patcher."; // IDS_ERR_NOT_LATEST_FILE + case 7: return "Please run patcher."; // IDS_ERR_MUST_LAUNCH_FROM_PATCHER + } + return ""; +} + +std::map gs_kMap_dwID_stLocale; +} // namespace + +const std::string& ApplicationStringTable_GetString(DWORD dwID) +{ + std::string& rstLocale=gs_kMap_dwID_stLocale[dwID]; + rstLocale=LoadAppString(dwID); + + return rstLocale; +} + +const char* ApplicationStringTable_GetStringz(DWORD dwID) +{ + return ApplicationStringTable_GetString(dwID).c_str(); +} + namespace { // Lowercased file name -> on-disk spelling, for the files directly in `folder`. diff --git a/extension/src/platform/pending/UserInterface/PythonTextTail.cpp b/extension/src/platform/pending/UserInterface/PythonTextTail.cpp deleted file mode 100644 index 11d5ed45..00000000 --- a/extension/src/platform/pending/UserInterface/PythonTextTail.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// Stand-in for the 40250 logic unit UserInterface/PythonTextTail.cpp, not ported yet: the members the ported units -// reference, as platform_stub.py stubs. Delete this file in the commit that ports the unit into port/ -// (docs/PORT-PLAN.md). -#include "UserInterface/StdAfx.h" -#include "UserInterface/PythonTextTail.h" - -#include "../../PlatformStub.h" - -CPythonTextTail::CPythonTextTail() -{ - MT_PLATFORM_STUB(); -} - -CPythonTextTail::~CPythonTextTail() -{ - MT_PLATFORM_STUB(); -} - -auto CPythonTextTail::RegisterChatTail(DWORD, const char *) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonTextTail::RegisterCharacterTextTail(DWORD, DWORD, const D3DXCOLOR &, float) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonTextTail::SetCharacterTextTailColor(DWORD, const D3DXCOLOR &) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonTextTail::DeleteCharacterTextTail(DWORD) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonTextTail::AttachTitle(DWORD, const char *, const D3DXCOLOR &) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonTextTail::DetachTitle(DWORD) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonTextTail::AttachLevel(DWORD, const char *, const D3DXCOLOR &) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonTextTail::RegisterItemTextTail(DWORD, const char *, CGraphicObjectInstance *) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonTextTail::DeleteItemTextTail(DWORD) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonTextTail::SetItemTextTailOwner(DWORD, const char *) -> void -{ - MT_PLATFORM_STUB(); -} - -// 40250 returns -1 when no item text tail is under the mouse; RegisterItemTextTail is still a stub, so -// m_ItemTextTailMap stays empty and that is always the answer (0 would pick ground item VID 0). -auto CPythonTextTail::Pick(int, int) -> int -{ - MT_PLATFORM_STUB(); - return -1; -} diff --git a/extension/src/port/UserInterface/PythonTextTail.cpp b/extension/src/port/UserInterface/PythonTextTail.cpp new file mode 100644 index 00000000..1ce58ec0 --- /dev/null +++ b/extension/src/port/UserInterface/PythonTextTail.cpp @@ -0,0 +1,1029 @@ +// +// 캐릭터를 따라다니는 텍스트 관련 소스 (이름, 길드이름, 길드마크 등) +// +#include "StdAfx.h" +#include "InstanceBase.h" +#include "resource.h" +#include "PythonTextTail.h" +#include "PythonCharacterManager.h" +#include "PythonGuild.h" +#include "Locale.h" +#include "MarkManager.h" + +const D3DXCOLOR c_TextTail_Player_Color = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); +const D3DXCOLOR c_TextTail_Monster_Color = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f); +const D3DXCOLOR c_TextTail_Item_Color = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); +const D3DXCOLOR c_TextTail_Chat_Color = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); +const D3DXCOLOR c_TextTail_Info_Color = D3DXCOLOR(1.0f, 0.785f, 0.785f, 1.0f); +const D3DXCOLOR c_TextTail_Guild_Name_Color = 0xFFEFD3FF; +const float c_TextTail_Name_Position = -10.0f; +const float c_fxMarkPosition = 1.5f; +const float c_fyGuildNamePosition = 15.0f; +const float c_fyMarkPosition = 15.0f + 11.0f; +BOOL bPKTitleEnable = TRUE; + +// TEXTTAIL_LIVINGTIME_CONTROL +long gs_TextTail_LivingTime = 5000; + +long TextTail_GetLivingTime() +{ + assert(gs_TextTail_LivingTime>1000); + return gs_TextTail_LivingTime; +} + +void TextTail_SetLivingTime(long livingTime) +{ + gs_TextTail_LivingTime = livingTime; +} +// END_OF_TEXTTAIL_LIVINGTIME_CONTROL + +CGraphicText * ms_pFont = NULL; + +void CPythonTextTail::GetInfo(std::string* pstInfo) +{ + char szInfo[256]; + sprintf(szInfo, "TextTail: ChatTail %d, ChrTail (Map %d, List %d), ItemTail (Map %d, List %d), Pool %d", + m_ChatTailMap.size(), + m_CharacterTextTailMap.size(), m_CharacterTextTailList.size(), + m_ItemTextTailMap.size(), m_ItemTextTailList.size(), + m_TextTailPool.GetCapacity()); + + pstInfo->append(szInfo); +} + +void CPythonTextTail::UpdateAllTextTail() +{ + CInstanceBase * pInstance = CPythonCharacterManager::Instance().GetMainInstancePtr(); + if (pInstance) + { + TPixelPosition pixelPos; + pInstance->NEW_GetPixelPosition(&pixelPos); + + TTextTailMap::iterator itorMap; + + for (itorMap = m_CharacterTextTailMap.begin(); itorMap != m_CharacterTextTailMap.end(); ++itorMap) + { + UpdateDistance(pixelPos, itorMap->second); + } + + for (itorMap = m_ItemTextTailMap.begin(); itorMap != m_ItemTextTailMap.end(); ++itorMap) + { + UpdateDistance(pixelPos, itorMap->second); + } + + for (TChatTailMap::iterator itorChat=m_ChatTailMap.begin(); itorChat!=m_ChatTailMap.end(); ++itorChat) + { + UpdateDistance(pixelPos, itorChat->second); + + // NOTE : Chat TextTail이 있으면 캐릭터 이름도 출력한다. + if (itorChat->second->bNameFlag) + { + DWORD dwVID = itorChat->first; + ShowCharacterTextTail(dwVID); + } + } + } +} + +void CPythonTextTail::UpdateShowingTextTail() +{ + TTextTailList::iterator itor; + + for (itor = m_ItemTextTailList.begin(); itor != m_ItemTextTailList.end(); ++itor) + { + UpdateTextTail(*itor); + } + + for (TChatTailMap::iterator itorChat=m_ChatTailMap.begin(); itorChat!=m_ChatTailMap.end(); ++itorChat) + { + UpdateTextTail(itorChat->second); + } + + for (itor = m_CharacterTextTailList.begin(); itor != m_CharacterTextTailList.end(); ++itor) + { + TTextTail * pTextTail = *itor; + UpdateTextTail(pTextTail); + + // NOTE : Chat TextTail이 있을 경우 위치를 바꾼다. + TChatTailMap::iterator itor = m_ChatTailMap.find(pTextTail->dwVirtualID); + if (m_ChatTailMap.end() != itor) + { + TTextTail * pChatTail = itor->second; + if (pChatTail->bNameFlag) + { + pTextTail->y = pChatTail->y - 17.0f; + } + } + } +} + +void CPythonTextTail::UpdateTextTail(TTextTail * pTextTail) +{ + if (!pTextTail->pOwner) + return; + + ///// + + CPythonGraphic & rpyGraphic = CPythonGraphic::Instance(); + rpyGraphic.Identity(); + + const D3DXVECTOR3 & c_rv3Position = pTextTail->pOwner->GetPosition(); + rpyGraphic.ProjectPosition(c_rv3Position.x, + c_rv3Position.y, + c_rv3Position.z + pTextTail->fHeight, + &pTextTail->x, + &pTextTail->y, + &pTextTail->z); + + pTextTail->x = floorf(pTextTail->x); + pTextTail->y = floorf(pTextTail->y); + + // NOTE : 13m 밖에 있을때만 깊이를 넣습니다 - [levites] + if (pTextTail->fDistanceFromPlayer < 1300.0f) + { + pTextTail->z = 0.0f; + } + else + { + pTextTail->z = pTextTail->z * CPythonGraphic::Instance().GetOrthoDepth() * -1.0f; + pTextTail->z += 10.0f; + } +} + +void CPythonTextTail::ArrangeTextTail() +{ + TTextTailList::iterator itor; + TTextTailList::iterator itorCompare; + + DWORD dwTime = CTimer::Instance().GetCurrentMillisecond(); + + for (itor = m_ItemTextTailList.begin(); itor != m_ItemTextTailList.end(); ++itor) + { + TTextTail * pInsertTextTail = *itor; + + int yTemp = 5; + int LimitCount = 0; + + for (itorCompare = m_ItemTextTailList.begin(); itorCompare != m_ItemTextTailList.end();) + { + TTextTail * pCompareTextTail = *itorCompare; + + if (*itorCompare == *itor) + { + ++itorCompare; + continue; + } + + if (LimitCount >= 20) + break; + + if (isIn(pInsertTextTail, pCompareTextTail)) + { + pInsertTextTail->y = (pCompareTextTail->y + pCompareTextTail->yEnd + yTemp); + + itorCompare = m_ItemTextTailList.begin(); + ++LimitCount; + continue; + } + + ++itorCompare; + } + + + if (pInsertTextTail->pOwnerTextInstance) + { + pInsertTextTail->pOwnerTextInstance->SetPosition(pInsertTextTail->x, pInsertTextTail->y, pInsertTextTail->z); + pInsertTextTail->pOwnerTextInstance->Update(); + + pInsertTextTail->pTextInstance->SetColor(pInsertTextTail->Color.r, pInsertTextTail->Color.g, pInsertTextTail->Color.b); + pInsertTextTail->pTextInstance->SetPosition(pInsertTextTail->x, pInsertTextTail->y + 15.0f, pInsertTextTail->z); + pInsertTextTail->pTextInstance->Update(); + + } + else + { + pInsertTextTail->pTextInstance->SetColor(pInsertTextTail->Color.r, pInsertTextTail->Color.g, pInsertTextTail->Color.b); + pInsertTextTail->pTextInstance->SetPosition(pInsertTextTail->x, pInsertTextTail->y, pInsertTextTail->z); + pInsertTextTail->pTextInstance->Update(); + + } + } + + for (itor = m_CharacterTextTailList.begin(); itor != m_CharacterTextTailList.end(); ++itor) + { + TTextTail * pTextTail = *itor; + + float fxAdd = 0.0f; + + // Mark 위치 업데이트 + CGraphicMarkInstance * pMarkInstance = pTextTail->pMarkInstance; + CGraphicTextInstance * pGuildNameInstance = pTextTail->pGuildNameTextInstance; + if (pMarkInstance && pGuildNameInstance) + { + int iWidth, iHeight; + int iImageHalfSize = pMarkInstance->GetWidth()/2 + c_fxMarkPosition; + pGuildNameInstance->GetTextSize(&iWidth, &iHeight); + + pMarkInstance->SetPosition(pTextTail->x - iWidth/2 - iImageHalfSize, pTextTail->y - c_fyMarkPosition); + pGuildNameInstance->SetPosition(pTextTail->x + iImageHalfSize, pTextTail->y - c_fyGuildNamePosition, pTextTail->z); + pGuildNameInstance->Update(); + } + + int iNameWidth, iNameHeight; + pTextTail->pTextInstance->GetTextSize(&iNameWidth, &iNameHeight); + + // Title 위치 업데이트 + CGraphicTextInstance * pTitle = pTextTail->pTitleTextInstance; + if (pTitle) + { + int iTitleWidth, iTitleHeight; + pTitle->GetTextSize(&iTitleWidth, &iTitleHeight); + + fxAdd = 8.0f; + + if (LocaleService_IsEUROPE()) // 독일어는 명칭이 길어 오른정렬 + { + if( GetDefaultCodePage() == CP_ARABIC ) + { + pTitle->SetPosition(pTextTail->x - (iNameWidth / 2) - iTitleWidth - 4.0f, pTextTail->y, pTextTail->z); + } + else + { + // pTitle->SetPosition(pTextTail->x - (iNameWidth / 2), pTextTail->y, pTextTail->z); + pTitle->SetPosition(pTextTail->x - ((iNameWidth / 2)-4.0f), pTextTail->y, pTextTail->z); // Space between level and alignment + } + } + else + { + pTitle->SetPosition(pTextTail->x - (iNameWidth / 2) - fxAdd, pTextTail->y, pTextTail->z); + } + pTitle->Update(); + + // Level 위치 업데이트 + CGraphicTextInstance * pLevel = pTextTail->pLevelTextInstance; + if (pLevel) + { + int iLevelWidth, iLevelHeight; + pLevel->GetTextSize(&iLevelWidth, &iLevelHeight); + + if (LocaleService_IsEUROPE()) // 독일어는 명칭이 길어 오른정렬 + { + if( GetDefaultCodePage() == CP_ARABIC ) + { + pLevel->SetPosition(pTextTail->x - (iNameWidth / 2) - iLevelWidth - iTitleWidth - 8.0f, pTextTail->y, pTextTail->z); + } + else + { + pLevel->SetPosition(pTextTail->x - (iNameWidth / 2) - iTitleWidth, pTextTail->y, pTextTail->z); + } + } + else + { + pLevel->SetPosition(pTextTail->x - (iNameWidth / 2) - fxAdd - iTitleWidth, pTextTail->y, pTextTail->z); + } + + pLevel->Update(); + } + } + else + { + fxAdd = 4.0f; + + // Level 위치 업데이트 + CGraphicTextInstance * pLevel = pTextTail->pLevelTextInstance; + if (pLevel) + { + int iLevelWidth, iLevelHeight; + pLevel->GetTextSize(&iLevelWidth, &iLevelHeight); + + if (LocaleService_IsEUROPE()) // 독일어는 명칭이 길어 오른정렬 + { + if( GetDefaultCodePage() == CP_ARABIC ) + { + pLevel->SetPosition(pTextTail->x - (iNameWidth / 2) - iLevelWidth - 4.0f, pTextTail->y, pTextTail->z); + } + else + { + pLevel->SetPosition(pTextTail->x - (iNameWidth / 2), pTextTail->y, pTextTail->z); + } + } + else + { + pLevel->SetPosition(pTextTail->x - (iNameWidth / 2) - fxAdd, pTextTail->y, pTextTail->z); + } + + pLevel->Update(); + } + } + + pTextTail->pTextInstance->SetColor(pTextTail->Color.r, pTextTail->Color.g, pTextTail->Color.b); + pTextTail->pTextInstance->SetPosition(pTextTail->x + fxAdd, pTextTail->y, pTextTail->z); + pTextTail->pTextInstance->Update(); + } + + for (TChatTailMap::iterator itorChat=m_ChatTailMap.begin(); itorChat!=m_ChatTailMap.end();) + { + TTextTail * pTextTail = itorChat->second; + + if (pTextTail->LivingTime < dwTime) + { + DeleteTextTail(pTextTail); + itorChat = m_ChatTailMap.erase(itorChat); + continue; + } + else + ++itorChat; + + pTextTail->pTextInstance->SetColor(pTextTail->Color); + pTextTail->pTextInstance->SetPosition(pTextTail->x, pTextTail->y, pTextTail->z); + pTextTail->pTextInstance->Update(); + } +} + +void CPythonTextTail::Render() +{ + TTextTailList::iterator itor; + + for (itor = m_CharacterTextTailList.begin(); itor != m_CharacterTextTailList.end(); ++itor) + { + TTextTail * pTextTail = *itor; + pTextTail->pTextInstance->Render(); + if (pTextTail->pMarkInstance && pTextTail->pGuildNameTextInstance) + { + pTextTail->pMarkInstance->Render(); + pTextTail->pGuildNameTextInstance->Render(); + } + if (pTextTail->pTitleTextInstance) + { + pTextTail->pTitleTextInstance->Render(); + } + if (pTextTail->pLevelTextInstance) + { + pTextTail->pLevelTextInstance->Render(); + } + } + + for (itor = m_ItemTextTailList.begin(); itor != m_ItemTextTailList.end(); ++itor) + { + TTextTail * pTextTail = *itor; + + RenderTextTailBox(pTextTail); + pTextTail->pTextInstance->Render(); + if (pTextTail->pOwnerTextInstance) + pTextTail->pOwnerTextInstance->Render(); + } + + for (TChatTailMap::iterator itorChat = m_ChatTailMap.begin(); itorChat!=m_ChatTailMap.end(); ++itorChat) + { + TTextTail * pTextTail = itorChat->second; + if (pTextTail->pOwner->isShow()) + RenderTextTailName(pTextTail); + } +} + +void CPythonTextTail::RenderTextTailBox(TTextTail * pTextTail) +{ + // 검은색 테두리 + CPythonGraphic::Instance().SetDiffuseColor(0.0f, 0.0f, 0.0f, 1.0f); + CPythonGraphic::Instance().RenderBox2d(pTextTail->x + pTextTail->xStart, + pTextTail->y + pTextTail->yStart, + pTextTail->x + pTextTail->xEnd, + pTextTail->y + pTextTail->yEnd, + pTextTail->z); + + // 검은색 투명박스 + CPythonGraphic::Instance().SetDiffuseColor(0.0f, 0.0f, 0.0f, 0.3f); + CPythonGraphic::Instance().RenderBar2d(pTextTail->x + pTextTail->xStart, + pTextTail->y + pTextTail->yStart, + pTextTail->x + pTextTail->xEnd, + pTextTail->y + pTextTail->yEnd, + pTextTail->z); +} + +void CPythonTextTail::RenderTextTailName(TTextTail * pTextTail) +{ + pTextTail->pTextInstance->Render(); +} + +void CPythonTextTail::HideAllTextTail() +{ + // NOTE : Show All을 해준뒤 Hide All을 해주지 않으면 문제 발생 가능성 있음 + // 디자인 자체가 그렇게 깔끔하게 되지 않았음 - [levites] + m_CharacterTextTailList.clear(); + m_ItemTextTailList.clear(); +} + +void CPythonTextTail::UpdateDistance(const TPixelPosition & c_rCenterPosition, TTextTail * pTextTail) +{ + const D3DXVECTOR3 & c_rv3Position = pTextTail->pOwner->GetPosition(); + D3DXVECTOR2 v2Distance(c_rv3Position.x - c_rCenterPosition.x, -c_rv3Position.y - c_rCenterPosition.y); + pTextTail->fDistanceFromPlayer = D3DXVec2Length(&v2Distance); +} + +void CPythonTextTail::ShowAllTextTail() +{ + TTextTailMap::iterator itor; + for (itor = m_CharacterTextTailMap.begin(); itor != m_CharacterTextTailMap.end(); ++itor) + { + TTextTail * pTextTail = itor->second; + if (pTextTail->fDistanceFromPlayer < 3500.0f) + ShowCharacterTextTail(itor->first); + } + for (itor = m_ItemTextTailMap.begin(); itor != m_ItemTextTailMap.end(); ++itor) + { + TTextTail * pTextTail = itor->second; + if (pTextTail->fDistanceFromPlayer < 3500.0f) + ShowItemTextTail(itor->first); + } +} + +void CPythonTextTail::ShowCharacterTextTail(DWORD VirtualID) +{ + TTextTailMap::iterator itor = m_CharacterTextTailMap.find(VirtualID); + + if (m_CharacterTextTailMap.end() == itor) + return; + + TTextTail * pTextTail = itor->second; + + if (m_CharacterTextTailList.end() != std::find(m_CharacterTextTailList.begin(), m_CharacterTextTailList.end(), pTextTail)) + { + //Tracef("이미 리스트에 있음 : %d\n", VirtualID); + return; + } + + // NOTE : ShowAll 시에는 모든 Instance 의 Pointer 를 찾아서 체크하므로 부하가 걸릴 가능성도 있다. + // CInstanceBase 가 TextTail 을 직접 가지고 있는 것이 가장 좋은 형태일 듯.. + if (!pTextTail->pOwner->isShow()) + return; + + CInstanceBase * pInstance = CPythonCharacterManager::Instance().GetInstancePtr(pTextTail->dwVirtualID); + if (!pInstance) + return; + + if (pInstance->IsGuildWall()) + return; + + if (pInstance->CanPickInstance()) + m_CharacterTextTailList.push_back(pTextTail); +} + +void CPythonTextTail::ShowItemTextTail(DWORD VirtualID) +{ + TTextTailMap::iterator itor = m_ItemTextTailMap.find(VirtualID); + + if (m_ItemTextTailMap.end() == itor) + return; + + TTextTail * pTextTail = itor->second; + + if (m_ItemTextTailList.end() != std::find(m_ItemTextTailList.begin(), m_ItemTextTailList.end(), pTextTail)) + { + //Tracef("이미 리스트에 있음 : %d\n", VirtualID); + return; + } + + m_ItemTextTailList.push_back(pTextTail); +} + +bool CPythonTextTail::isIn(CPythonTextTail::TTextTail * pSource, CPythonTextTail::TTextTail * pTarget) +{ + float x1Source = pSource->x + pSource->xStart; + float y1Source = pSource->y + pSource->yStart; + float x2Source = pSource->x + pSource->xEnd; + float y2Source = pSource->y + pSource->yEnd; + float x1Target = pTarget->x + pTarget->xStart; + float y1Target = pTarget->y + pTarget->yStart; + float x2Target = pTarget->x + pTarget->xEnd; + float y2Target = pTarget->y + pTarget->yEnd; + + if (x1Source <= x2Target && x2Source >= x1Target && + y1Source <= y2Target && y2Source >= y1Target) + { + return true; + } + + return false; +} + +void CPythonTextTail::RegisterCharacterTextTail(DWORD dwGuildID, DWORD dwVirtualID, const D3DXCOLOR & c_rColor, float fAddHeight) +{ + CInstanceBase * pCharacterInstance = CPythonCharacterManager::Instance().GetInstancePtr(dwVirtualID); + + if (!pCharacterInstance) + return; + + TTextTail * pTextTail = RegisterTextTail(dwVirtualID, + pCharacterInstance->GetNameString(), + pCharacterInstance->GetGraphicThingInstancePtr(), + pCharacterInstance->GetGraphicThingInstanceRef().GetHeight() + fAddHeight, + c_rColor); + + CGraphicTextInstance * pTextInstance = pTextTail->pTextInstance; + pTextInstance->SetOutline(true); + pTextInstance->SetVerticalAlign(CGraphicTextInstance::VERTICAL_ALIGN_BOTTOM); + + pTextTail->pMarkInstance=NULL; + pTextTail->pGuildNameTextInstance=NULL; + pTextTail->pTitleTextInstance=NULL; + pTextTail->pLevelTextInstance=NULL; + + if (0 != dwGuildID) + { + pTextTail->pMarkInstance = CGraphicMarkInstance::New(); + + DWORD dwMarkID = CGuildMarkManager::Instance().GetMarkID(dwGuildID); + + if (dwMarkID != CGuildMarkManager::INVALID_MARK_ID) + { + std::string markImagePath; + + if (CGuildMarkManager::Instance().GetMarkImageFilename(dwMarkID / CGuildMarkImage::MARK_TOTAL_COUNT, markImagePath)) + { + pTextTail->pMarkInstance->SetImageFileName(markImagePath.c_str()); + pTextTail->pMarkInstance->Load(); + pTextTail->pMarkInstance->SetIndex(dwMarkID % CGuildMarkImage::MARK_TOTAL_COUNT); + } + } + + std::string strGuildName; + if (!CPythonGuild::Instance().GetGuildName(dwGuildID, &strGuildName)) + strGuildName = "Noname"; + + CGraphicTextInstance *& prGuildNameInstance = pTextTail->pGuildNameTextInstance; + prGuildNameInstance = CGraphicTextInstance::New(); + prGuildNameInstance->SetTextPointer(ms_pFont); + prGuildNameInstance->SetOutline(true); + prGuildNameInstance->SetHorizonalAlign(CGraphicTextInstance::HORIZONTAL_ALIGN_CENTER); + prGuildNameInstance->SetVerticalAlign(CGraphicTextInstance::VERTICAL_ALIGN_BOTTOM); + prGuildNameInstance->SetValue(strGuildName.c_str()); + prGuildNameInstance->SetColor(c_TextTail_Guild_Name_Color.r, c_TextTail_Guild_Name_Color.g, c_TextTail_Guild_Name_Color.b); + prGuildNameInstance->Update(); + } + + m_CharacterTextTailMap.insert(TTextTailMap::value_type(dwVirtualID, pTextTail)); +} + +void CPythonTextTail::RegisterItemTextTail(DWORD VirtualID, const char * c_szText, CGraphicObjectInstance * pOwner) +{ +#ifdef __DEBUG + char szName[256]; + spritnf(szName, "%s[%d]", c_szText, VirtualID); + + TTextTail * pTextTail = RegisterTextTail(VirtualID, c_szText, pOwner, c_TextTail_Name_Position, c_TextTail_Item_Color); + m_ItemTextTailMap.insert(TTextTailMap::value_type(VirtualID, pTextTail)); +#else + TTextTail * pTextTail = RegisterTextTail(VirtualID, c_szText, pOwner, c_TextTail_Name_Position, c_TextTail_Item_Color); + m_ItemTextTailMap.insert(TTextTailMap::value_type(VirtualID, pTextTail)); +#endif +} + +void CPythonTextTail::RegisterChatTail(DWORD VirtualID, const char * c_szChat) +{ + CInstanceBase * pCharacterInstance = CPythonCharacterManager::Instance().GetInstancePtr(VirtualID); + + if (!pCharacterInstance) + return; + + TChatTailMap::iterator itor = m_ChatTailMap.find(VirtualID); + + if (m_ChatTailMap.end() != itor) + { + TTextTail * pTextTail = itor->second; + + pTextTail->pTextInstance->SetValue(c_szChat); + pTextTail->pTextInstance->Update(); + pTextTail->Color = c_TextTail_Chat_Color; + pTextTail->pTextInstance->SetColor(c_TextTail_Chat_Color); + + // TEXTTAIL_LIVINGTIME_CONTROL + pTextTail->LivingTime = CTimer::Instance().GetCurrentMillisecond() + TextTail_GetLivingTime(); + // END_OF_TEXTTAIL_LIVINGTIME_CONTROL + + pTextTail->bNameFlag = TRUE; + + return; + } + + TTextTail * pTextTail = RegisterTextTail(VirtualID, + c_szChat, + pCharacterInstance->GetGraphicThingInstancePtr(), + pCharacterInstance->GetGraphicThingInstanceRef().GetHeight() + 10.0f, + c_TextTail_Chat_Color); + + // TEXTTAIL_LIVINGTIME_CONTROL + pTextTail->LivingTime = CTimer::Instance().GetCurrentMillisecond() + TextTail_GetLivingTime(); + // END_OF_TEXTTAIL_LIVINGTIME_CONTROL + + pTextTail->bNameFlag = TRUE; + pTextTail->pTextInstance->SetOutline(true); + pTextTail->pTextInstance->SetVerticalAlign(CGraphicTextInstance::VERTICAL_ALIGN_BOTTOM); + m_ChatTailMap.insert(TTextTailMap::value_type(VirtualID, pTextTail)); +} + +void CPythonTextTail::RegisterInfoTail(DWORD VirtualID, const char * c_szChat) +{ + CInstanceBase * pCharacterInstance = CPythonCharacterManager::Instance().GetInstancePtr(VirtualID); + + if (!pCharacterInstance) + return; + + TChatTailMap::iterator itor = m_ChatTailMap.find(VirtualID); + + if (m_ChatTailMap.end() != itor) + { + TTextTail * pTextTail = itor->second; + + pTextTail->pTextInstance->SetValue(c_szChat); + pTextTail->pTextInstance->Update(); + pTextTail->Color = c_TextTail_Info_Color; + pTextTail->pTextInstance->SetColor(c_TextTail_Info_Color); + + // TEXTTAIL_LIVINGTIME_CONTROL + pTextTail->LivingTime = CTimer::Instance().GetCurrentMillisecond() + TextTail_GetLivingTime(); + // END_OF_TEXTTAIL_LIVINGTIME_CONTROL + + pTextTail->bNameFlag = FALSE; + + return; + } + + TTextTail * pTextTail = RegisterTextTail(VirtualID, + c_szChat, + pCharacterInstance->GetGraphicThingInstancePtr(), + pCharacterInstance->GetGraphicThingInstanceRef().GetHeight() + 10.0f, + c_TextTail_Info_Color); + + // TEXTTAIL_LIVINGTIME_CONTROL + pTextTail->LivingTime = CTimer::Instance().GetCurrentMillisecond() + TextTail_GetLivingTime(); + // END_OF_TEXTTAIL_LIVINGTIME_CONTROL + + pTextTail->bNameFlag = FALSE; + pTextTail->pTextInstance->SetOutline(true); + pTextTail->pTextInstance->SetVerticalAlign(CGraphicTextInstance::VERTICAL_ALIGN_BOTTOM); + m_ChatTailMap.insert(TTextTailMap::value_type(VirtualID, pTextTail)); +} + +bool CPythonTextTail::GetTextTailPosition(DWORD dwVID, float* px, float* py, float* pz) +{ + TTextTailMap::iterator itorCharacter = m_CharacterTextTailMap.find(dwVID); + + if (m_CharacterTextTailMap.end() == itorCharacter) + { + return false; + } + + TTextTail * pTextTail = itorCharacter->second; + *px=pTextTail->x; + *py=pTextTail->y; + *pz=pTextTail->z; + + return true; +} + +bool CPythonTextTail::IsChatTextTail(DWORD dwVID) +{ + TChatTailMap::iterator itorChat = m_ChatTailMap.find(dwVID); + + if (m_ChatTailMap.end() == itorChat) + return false; + + return true; +} + +void CPythonTextTail::SetCharacterTextTailColor(DWORD VirtualID, const D3DXCOLOR & c_rColor) +{ + TTextTailMap::iterator itorCharacter = m_CharacterTextTailMap.find(VirtualID); + + if (m_CharacterTextTailMap.end() == itorCharacter) + return; + + TTextTail * pTextTail = itorCharacter->second; + pTextTail->pTextInstance->SetColor(c_rColor); + pTextTail->Color = c_rColor; +} + +void CPythonTextTail::SetItemTextTailOwner(DWORD dwVID, const char * c_szName) +{ + TTextTailMap::iterator itor = m_ItemTextTailMap.find(dwVID); + if (m_ItemTextTailMap.end() == itor) + return; + + TTextTail * pTextTail = itor->second; + + if (strlen(c_szName) > 0) + { + if (!pTextTail->pOwnerTextInstance) + { + pTextTail->pOwnerTextInstance = CGraphicTextInstance::New(); + } + + std::string strName = c_szName; + static const std::string & strOwnership = ApplicationStringTable_GetString(IDS_POSSESSIVE_MORPHENE) == "" ? "'s" : ApplicationStringTable_GetString(IDS_POSSESSIVE_MORPHENE); + strName += strOwnership; + + + pTextTail->pOwnerTextInstance->SetTextPointer(ms_pFont); + pTextTail->pOwnerTextInstance->SetHorizonalAlign(CGraphicTextInstance::HORIZONTAL_ALIGN_CENTER); + pTextTail->pOwnerTextInstance->SetValue(strName.c_str()); + pTextTail->pOwnerTextInstance->SetColor(1.0f, 1.0f, 0.0f); + pTextTail->pOwnerTextInstance->Update(); + + int xOwnerSize, yOwnerSize; + pTextTail->pOwnerTextInstance->GetTextSize(&xOwnerSize, &yOwnerSize); + pTextTail->yStart = -2.0f; + pTextTail->yEnd += float(yOwnerSize + 4); + pTextTail->xStart = fMIN(pTextTail->xStart, float(-xOwnerSize / 2 - 1)); + pTextTail->xEnd = fMAX(pTextTail->xEnd, float(xOwnerSize / 2 + 1)); + } + else + { + if (pTextTail->pOwnerTextInstance) + { + CGraphicTextInstance::Delete(pTextTail->pOwnerTextInstance); + pTextTail->pOwnerTextInstance = NULL; + } + + int xSize, ySize; + pTextTail->pTextInstance->GetTextSize(&xSize, &ySize); + pTextTail->xStart = (float) (-xSize / 2 - 2); + pTextTail->yStart = -2.0f; + pTextTail->xEnd = (float) (xSize / 2 + 2); + pTextTail->yEnd = (float) ySize; + } +} + +void CPythonTextTail::DeleteCharacterTextTail(DWORD VirtualID) +{ + TTextTailMap::iterator itorCharacter = m_CharacterTextTailMap.find(VirtualID); + TTextTailMap::iterator itorChat = m_ChatTailMap.find(VirtualID); + + if (m_CharacterTextTailMap.end() != itorCharacter) + { + DeleteTextTail(itorCharacter->second); + m_CharacterTextTailMap.erase(itorCharacter); + } + else + { + Tracenf("CPythonTextTail::DeleteCharacterTextTail - Find VID[%d] Error", VirtualID); + } + + if (m_ChatTailMap.end() != itorChat) + { + DeleteTextTail(itorChat->second); + m_ChatTailMap.erase(itorChat); + } +} + +void CPythonTextTail::DeleteItemTextTail(DWORD VirtualID) +{ + TTextTailMap::iterator itor = m_ItemTextTailMap.find(VirtualID); + + if (m_ItemTextTailMap.end() == itor) + { + Tracef(" CPythonTextTail::DeleteItemTextTail - None Item Text Tail\n"); + return; + } + + DeleteTextTail(itor->second); + m_ItemTextTailMap.erase(itor); +} + +CPythonTextTail::TTextTail * CPythonTextTail::RegisterTextTail(DWORD dwVirtualID, const char * c_szText, CGraphicObjectInstance * pOwner, float fHeight, const D3DXCOLOR & c_rColor) +{ + TTextTail * pTextTail = m_TextTailPool.Alloc(); + + pTextTail->dwVirtualID = dwVirtualID; + pTextTail->pOwner = pOwner; + pTextTail->pTextInstance = CGraphicTextInstance::New(); + pTextTail->pOwnerTextInstance = NULL; + pTextTail->fHeight = fHeight; + + pTextTail->pTextInstance->SetTextPointer(ms_pFont); + pTextTail->pTextInstance->SetHorizonalAlign(CGraphicTextInstance::HORIZONTAL_ALIGN_CENTER); + pTextTail->pTextInstance->SetValue(c_szText); + pTextTail->pTextInstance->SetColor(c_rColor.r, c_rColor.g, c_rColor.b); + pTextTail->pTextInstance->Update(); + + int xSize, ySize; + pTextTail->pTextInstance->GetTextSize(&xSize, &ySize); + pTextTail->xStart = (float) (-xSize / 2 - 2); + pTextTail->yStart = -2.0f; + pTextTail->xEnd = (float) (xSize / 2 + 2); + pTextTail->yEnd = (float) ySize; + pTextTail->Color = c_rColor; + pTextTail->fDistanceFromPlayer = 0.0f; + pTextTail->x = -100.0f; + pTextTail->y = -100.0f; + pTextTail->z = 0.0f; + pTextTail->pMarkInstance = NULL; + pTextTail->pGuildNameTextInstance = NULL; + pTextTail->pTitleTextInstance = NULL; + pTextTail->pLevelTextInstance = NULL; + return pTextTail; +} + +void CPythonTextTail::DeleteTextTail(TTextTail * pTextTail) +{ + if (pTextTail->pTextInstance) + { + CGraphicTextInstance::Delete(pTextTail->pTextInstance); + pTextTail->pTextInstance = NULL; + } + if (pTextTail->pOwnerTextInstance) + { + CGraphicTextInstance::Delete(pTextTail->pOwnerTextInstance); + pTextTail->pOwnerTextInstance = NULL; + } + if (pTextTail->pMarkInstance) + { + CGraphicMarkInstance::Delete(pTextTail->pMarkInstance); + pTextTail->pMarkInstance = NULL; + } + if (pTextTail->pGuildNameTextInstance) + { + CGraphicTextInstance::Delete(pTextTail->pGuildNameTextInstance); + pTextTail->pGuildNameTextInstance = NULL; + } + if (pTextTail->pTitleTextInstance) + { + CGraphicTextInstance::Delete(pTextTail->pTitleTextInstance); + pTextTail->pTitleTextInstance = NULL; + } + if (pTextTail->pLevelTextInstance) + { + CGraphicTextInstance::Delete(pTextTail->pLevelTextInstance); + pTextTail->pLevelTextInstance = NULL; + } + + m_TextTailPool.Free(pTextTail); +} + +int CPythonTextTail::Pick(int ixMouse, int iyMouse) +{ + for (TTextTailMap::iterator itor = m_ItemTextTailMap.begin(); itor != m_ItemTextTailMap.end(); ++itor) + { + TTextTail * pTextTail = itor->second; + + if (ixMouse >= pTextTail->x + pTextTail->xStart && ixMouse <= pTextTail->x + pTextTail->xEnd && + iyMouse >= pTextTail->y + pTextTail->yStart && iyMouse <= pTextTail->y + pTextTail->yEnd) + { + SelectItemName(itor->first); + return (itor->first); + } + } + + return -1; +} + +void CPythonTextTail::SelectItemName(DWORD dwVirtualID) +{ + TTextTailMap::iterator itor = m_ItemTextTailMap.find(dwVirtualID); + + if (m_ItemTextTailMap.end() == itor) + return; + + TTextTail * pTextTail = itor->second; + pTextTail->pTextInstance->SetColor(0.1f, 0.9f, 0.1f); +} + +void CPythonTextTail::AttachTitle(DWORD dwVID, const char * c_szName, const D3DXCOLOR & c_rColor) +{ + if (!bPKTitleEnable) + return; + + TTextTailMap::iterator itor = m_CharacterTextTailMap.find(dwVID); + if (m_CharacterTextTailMap.end() == itor) + return; + + TTextTail * pTextTail = itor->second; + + CGraphicTextInstance *& prTitle = pTextTail->pTitleTextInstance; + if (!prTitle) + { + prTitle = CGraphicTextInstance::New(); + prTitle->SetTextPointer(ms_pFont); + prTitle->SetOutline(true); + + if (LocaleService_IsEUROPE()) + prTitle->SetHorizonalAlign(CGraphicTextInstance::HORIZONTAL_ALIGN_RIGHT); + else + prTitle->SetHorizonalAlign(CGraphicTextInstance::HORIZONTAL_ALIGN_CENTER); + prTitle->SetVerticalAlign(CGraphicTextInstance::VERTICAL_ALIGN_BOTTOM); + } + + prTitle->SetValue(c_szName); + prTitle->SetColor(c_rColor.r, c_rColor.g, c_rColor.b); + prTitle->Update(); +} + +void CPythonTextTail::DetachTitle(DWORD dwVID) +{ + if (!bPKTitleEnable) + return; + + TTextTailMap::iterator itor = m_CharacterTextTailMap.find(dwVID); + if (m_CharacterTextTailMap.end() == itor) + return; + + TTextTail * pTextTail = itor->second; + + if (pTextTail->pTitleTextInstance) + { + CGraphicTextInstance::Delete(pTextTail->pTitleTextInstance); + pTextTail->pTitleTextInstance = NULL; + } +} + +void CPythonTextTail::EnablePKTitle(BOOL bFlag) +{ + bPKTitleEnable = bFlag; +} + +void CPythonTextTail::AttachLevel(DWORD dwVID, const char * c_szText, const D3DXCOLOR & c_rColor) +{ + if (!bPKTitleEnable) + return; + + TTextTailMap::iterator itor = m_CharacterTextTailMap.find(dwVID); + if (m_CharacterTextTailMap.end() == itor) + return; + + TTextTail * pTextTail = itor->second; + + CGraphicTextInstance *& prLevel = pTextTail->pLevelTextInstance; + if (!prLevel) + { + prLevel = CGraphicTextInstance::New(); + prLevel->SetTextPointer(ms_pFont); + prLevel->SetOutline(true); + + prLevel->SetHorizonalAlign(CGraphicTextInstance::HORIZONTAL_ALIGN_RIGHT); + prLevel->SetVerticalAlign(CGraphicTextInstance::VERTICAL_ALIGN_BOTTOM); + } + + prLevel->SetValue(c_szText); + prLevel->SetColor(c_rColor.r, c_rColor.g, c_rColor.b); + prLevel->Update(); +} + +void CPythonTextTail::DetachLevel(DWORD dwVID) +{ + if (!bPKTitleEnable) + return; + + TTextTailMap::iterator itor = m_CharacterTextTailMap.find(dwVID); + if (m_CharacterTextTailMap.end() == itor) + return; + + TTextTail * pTextTail = itor->second; + + if (pTextTail->pLevelTextInstance) + { + CGraphicTextInstance::Delete(pTextTail->pLevelTextInstance); + pTextTail->pLevelTextInstance = NULL; + } +} + + +void CPythonTextTail::Initialize() +{ + // DEFAULT_FONT + //ms_pFont = (CGraphicText *)CResourceManager::Instance().GetTypeResourcePointer(g_strDefaultFontName.c_str()); + + CGraphicText* pkDefaultFont = static_cast(DefaultFont_GetResource()); + if (!pkDefaultFont) + { + TraceError("CPythonTextTail::Initialize - CANNOT_FIND_DEFAULT_FONT"); + return; + } + + ms_pFont = pkDefaultFont; + // END_OF_DEFAULT_FONT +} + +void CPythonTextTail::Destroy() +{ + m_TextTailPool.Clear(); +} + +void CPythonTextTail::Clear() +{ + m_CharacterTextTailMap.clear(); + m_CharacterTextTailList.clear(); + m_ItemTextTailMap.clear(); + m_ItemTextTailList.clear(); + m_ChatTailMap.clear(); + + m_TextTailPool.Clear(); +} + +CPythonTextTail::CPythonTextTail() +{ + Clear(); +} + +CPythonTextTail::~CPythonTextTail() +{ + Destroy(); +} diff --git a/extension/src/port/UserInterface/PythonTextTailModule.cpp b/extension/src/port/UserInterface/PythonTextTailModule.cpp new file mode 100644 index 00000000..81af20a6 --- /dev/null +++ b/extension/src/port/UserInterface/PythonTextTailModule.cpp @@ -0,0 +1,235 @@ +#include "StdAfx.h" +#include "PythonTextTail.h" +#include "PythonCharacterManager.h" + +PyObject * textTailClear(PyObject * poSelf, PyObject * poArgs) +{ + CPythonTextTail::Instance().Clear(); + return Py_BuildNone(); +} + +PyObject * textTailUpdateAllTextTail(PyObject * poSelf, PyObject * poArgs) +{ + CPythonTextTail::Instance().UpdateAllTextTail(); + return Py_BuildNone(); +} + +PyObject * textTailUpdateShowingTextTail(PyObject * poSelf, PyObject * poArgs) +{ + CPythonTextTail::Instance().UpdateShowingTextTail(); + return Py_BuildNone(); +} + +PyObject * textTailRender(PyObject * poSelf, PyObject * poArgs) +{ + CPythonTextTail::Instance().Render(); + return Py_BuildNone(); +} + +PyObject * textTailRegisterCharacterTextTail(PyObject * poSelf, PyObject * poArgs) +{ + int iGuildID; + if (!PyTuple_GetInteger(poArgs, 0, &iGuildID)) + return Py_BuildException(); + int iVirtualID; + if (!PyTuple_GetInteger(poArgs, 1, &iVirtualID)) + return Py_BuildException(); + + CPythonTextTail::Instance().RegisterCharacterTextTail(iGuildID, iVirtualID, D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f)); + + return Py_BuildNone(); +} + +PyObject * textTailGetPosition(PyObject * poSelf, PyObject * poArgs) +{ + int VirtualID; + if (!PyTuple_GetInteger(poArgs, 0, &VirtualID)) + return Py_BuildException(); + + float x=0.0f; + float y=0.0f; + float z=0.0f; + bool isData=CPythonTextTail::Instance().GetTextTailPosition(VirtualID, &x, &y, &z); + if (!isData) + { + CPythonCharacterManager& rkChrMgr=CPythonCharacterManager::Instance(); + CInstanceBase* pkInstMain=rkChrMgr.GetMainInstancePtr(); + if (pkInstMain) + { + const D3DXVECTOR3 & c_rv3Position = pkInstMain->GetGraphicThingInstanceRef().GetPosition(); + CPythonGraphic::Instance().ProjectPosition(c_rv3Position.x, c_rv3Position.y, c_rv3Position.z, &x, &y); + } + } + + return Py_BuildValue("fff", x, y, z); +} + +PyObject * textTailIsChat(PyObject * poSelf, PyObject * poArgs) +{ + int VirtualID; + if (!PyTuple_GetInteger(poArgs, 0, &VirtualID)) + return Py_BuildException(); + + return Py_BuildValue("i", CPythonTextTail::Instance().IsChatTextTail(VirtualID)); +} + +PyObject * textTailRegisterChatTail(PyObject * poSelf, PyObject * poArgs) +{ + int VirtualID; + if (!PyTuple_GetInteger(poArgs, 0, &VirtualID)) + return Py_BuildException(); + + char * szText; + if (!PyTuple_GetString(poArgs, 1, &szText)) + return Py_BuildException(); + + CPythonTextTail::Instance().RegisterChatTail(VirtualID, szText); + + return Py_BuildNone(); +} + +PyObject * textTailRegisterInfoTail(PyObject * poSelf, PyObject * poArgs) +{ + int VirtualID; + if (!PyTuple_GetInteger(poArgs, 0, &VirtualID)) + return Py_BuildException(); + + char * szText; + if (!PyTuple_GetString(poArgs, 1, &szText)) + return Py_BuildException(); + + CPythonTextTail::Instance().RegisterInfoTail(VirtualID, szText); + + return Py_BuildNone(); +} + +PyObject * textTailAttachTitle(PyObject * poSelf, PyObject * poArgs) +{ + int iVirtualID; + if (!PyTuple_GetInteger(poArgs, 0, &iVirtualID)) + return Py_BuildException(); + char * szName; + if (!PyTuple_GetString(poArgs, 1, &szName)) + return Py_BuildException(); + float fr; + if (!PyTuple_GetFloat(poArgs, 2, &fr)) + return Py_BuildException(); + float fg; + if (!PyTuple_GetFloat(poArgs, 3, &fg)) + return Py_BuildException(); + float fb; + if (!PyTuple_GetFloat(poArgs, 4, &fb)) + return Py_BuildException(); + + CPythonTextTail::Instance().AttachTitle(iVirtualID, szName, D3DXCOLOR(fr, fg, fb, 1.0f)); + + return Py_BuildNone(); +} + +PyObject * textTailShowCharacterTextTail(PyObject * poSelf, PyObject * poArgs) +{ + int VirtualID; + if (!PyTuple_GetInteger(poArgs, 0, &VirtualID)) + return Py_BuildException(); + + CPythonTextTail::Instance().ShowCharacterTextTail(VirtualID); + return Py_BuildNone(); +} + +PyObject * textTailShowItemTextTail(PyObject * poSelf, PyObject * poArgs) +{ + int VirtualID; + if (!PyTuple_GetInteger(poArgs, 0, &VirtualID)) + return Py_BuildException(); + + CPythonTextTail::Instance().ShowItemTextTail(VirtualID); + return Py_BuildNone(); +} + +PyObject * textTailArrangeTextTail(PyObject * poSelf, PyObject * poArgs) +{ + CPythonTextTail::Instance().ArrangeTextTail(); + return Py_BuildNone(); +} + +PyObject * textTailHideAllTextTail(PyObject * poSelf, PyObject * poArgs) +{ + CPythonTextTail::Instance().HideAllTextTail(); + return Py_BuildNone(); +} + +PyObject * textTailShowAllTextTail(PyObject * poSelf, PyObject * poArgs) +{ + CPythonTextTail::Instance().ShowAllTextTail(); + return Py_BuildNone(); +} + +PyObject * textTailPick(PyObject * poSelf, PyObject * poArgs) +{ + int ix; + if (!PyTuple_GetInteger(poArgs, 0, &ix)) + return Py_BuildException(); + int iy; + if (!PyTuple_GetInteger(poArgs, 1, &iy)) + return Py_BuildException(); + + int iValue = CPythonTextTail::Instance().Pick(ix, iy); + return Py_BuildValue("i", iValue); +} + +PyObject * textTailSelectItemName(PyObject * poSelf, PyObject * poArgs) +{ + int iVirtualID; + if (!PyTuple_GetInteger(poArgs, 0, &iVirtualID)) + return Py_BuildException(); + + CPythonTextTail::Instance().SelectItemName(iVirtualID); + return Py_BuildNone(); +} + +PyObject * textTailEnablePKTitle(PyObject * poSelf, PyObject * poArgs) +{ + int iFlag; + if (!PyTuple_GetInteger(poArgs, 0, &iFlag)) + return Py_BuildException(); + + CPythonTextTail::Instance().EnablePKTitle(iFlag); + return Py_BuildNone(); +} + +void initTextTail() +{ + static PyMethodDef s_methods[] = + { + { "Clear", textTailClear, METH_VARARGS }, + + { "UpdateAllTextTail", textTailUpdateAllTextTail, METH_VARARGS }, + { "UpdateShowingTextTail", textTailUpdateShowingTextTail, METH_VARARGS }, + { "Render", textTailRender, METH_VARARGS }, + + { "ShowCharacterTextTail", textTailShowCharacterTextTail, METH_VARARGS }, + { "ShowItemTextTail", textTailShowItemTextTail, METH_VARARGS }, + + { "GetPosition", textTailGetPosition, METH_VARARGS }, + { "IsChat", textTailIsChat, METH_VARARGS }, + + { "ArrangeTextTail", textTailArrangeTextTail, METH_VARARGS }, + { "HideAllTextTail", textTailHideAllTextTail, METH_VARARGS }, + { "ShowAllTextTail", textTailShowAllTextTail, METH_VARARGS }, + + { "Pick", textTailPick, METH_VARARGS }, + { "SelectItemName", textTailSelectItemName, METH_VARARGS }, + + { "EnablePKTitle", textTailEnablePKTitle, METH_VARARGS }, + + // For Test + { "RegisterCharacterTextTail", textTailRegisterCharacterTextTail, METH_VARARGS }, + { "RegisterChatTail", textTailRegisterChatTail, METH_VARARGS }, + { "RegisterInfoTail", textTailRegisterInfoTail, METH_VARARGS }, + { "AttachTitle", textTailAttachTitle, METH_VARARGS }, + + { NULL, NULL, NULL }, + }; + + Py_InitModule("textTail", s_methods); +} \ No newline at end of file diff --git a/extension/src/port/UserInterface/StdAfx.h b/extension/src/port/UserInterface/StdAfx.h index 3e8cdfa6..c593a2da 100644 --- a/extension/src/port/UserInterface/StdAfx.h +++ b/extension/src/port/UserInterface/StdAfx.h @@ -50,8 +50,15 @@ void initPlayer(); void initItem(); void initNonPlayer(); void initTrade(); +void initTextTail(); void initChat(); void initnet(); void initeffect(); void initfly(); void initServerStateChecker(); + +extern const std::string& ApplicationStringTable_GetString(DWORD dwID); +extern const std::string& ApplicationStringTable_GetString(DWORD dwID, LPCSTR szKey); + +extern const char* ApplicationStringTable_GetStringz(DWORD dwID); +extern const char* ApplicationStringTable_GetStringz(DWORD dwID, LPCSTR szKey); diff --git a/extension/src/port/common/D3DXMath.cpp b/extension/src/port/common/D3DXMath.cpp index 35600c42..caa63494 100644 --- a/extension/src/port/common/D3DXMath.cpp +++ b/extension/src/port/common/D3DXMath.cpp @@ -101,6 +101,45 @@ D3DXVECTOR3* D3DXVec3TransformCoord(D3DXVECTOR3* pOut, const D3DXVECTOR3* pV, co return pOut; } +namespace +{ +// World * View * Projection, skipping the null ones. +D3DXMATRIX world_view_projection(const D3DXMATRIX* pProjection, const D3DXMATRIX* pView, const D3DXMATRIX* pWorld) +{ + D3DXMATRIX m; + D3DXMatrixIdentity(&m); + for (const D3DXMATRIX* p : {pWorld, pView, pProjection}) + if (p) + D3DXMatrixMultiply(&m, &m, p); + return m; +} +} // namespace + +D3DXVECTOR3* D3DXVec3Project(D3DXVECTOR3* pOut, const D3DXVECTOR3* pV, const D3DVIEWPORT8* pViewport, + const D3DXMATRIX* pProjection, const D3DXMATRIX* pView, const D3DXMATRIX* pWorld) +{ + const D3DXMATRIX m = world_view_projection(pProjection, pView, pWorld); + D3DXVECTOR3 v; + D3DXVec3TransformCoord(&v, pV, &m); + pOut->x = pViewport->X + (1.0f + v.x) * pViewport->Width / 2.0f; + pOut->y = pViewport->Y + (1.0f - v.y) * pViewport->Height / 2.0f; + pOut->z = pViewport->MinZ + v.z * (pViewport->MaxZ - pViewport->MinZ); + return pOut; +} + +D3DXVECTOR3* D3DXVec3Unproject(D3DXVECTOR3* pOut, const D3DXVECTOR3* pV, const D3DVIEWPORT8* pViewport, + const D3DXMATRIX* pProjection, const D3DXMATRIX* pView, const D3DXMATRIX* pWorld) +{ + D3DXMATRIX m = world_view_projection(pProjection, pView, pWorld); + D3DXMatrixInverse(&m, nullptr, &m); + D3DXVECTOR3 v; + v.x = 2.0f * (pV->x - pViewport->X) / pViewport->Width - 1.0f; + v.y = 1.0f - 2.0f * (pV->y - pViewport->Y) / pViewport->Height; + v.z = (pV->z - pViewport->MinZ) / (pViewport->MaxZ - pViewport->MinZ); + D3DXVec3TransformCoord(pOut, &v, &m); + return pOut; +} + D3DXVECTOR3* D3DXVec3TransformNormal(D3DXVECTOR3* pOut, const D3DXVECTOR3* pV, const D3DXMATRIX* pM) { D3DXVECTOR3 out; diff --git a/extension/src/port/common/D3DXMath.h b/extension/src/port/common/D3DXMath.h index c4141912..d6a2f482 100644 --- a/extension/src/port/common/D3DXMath.h +++ b/extension/src/port/common/D3DXMath.h @@ -337,6 +337,11 @@ D3DXVECTOR4* D3DXVec3Transform(D3DXVECTOR4* pOut, const D3DXVECTOR3* pV, const D D3DXVECTOR3* D3DXVec3TransformCoord(D3DXVECTOR3* pOut, const D3DXVECTOR3* pV, const D3DXMATRIX* pM); D3DXVECTOR3* D3DXVec3TransformNormal(D3DXVECTOR3* pOut, const D3DXVECTOR3* pV, const D3DXMATRIX* pM); D3DXVECTOR4* D3DXVec4Transform(D3DXVECTOR4* pOut, const D3DXVECTOR4* pV, const D3DXMATRIX* pM); +// D3DX semantics: object space → viewport pixels (x, y) and depth in [MinZ, MaxZ], and back; a null matrix is identity. +D3DXVECTOR3* D3DXVec3Project(D3DXVECTOR3* pOut, const D3DXVECTOR3* pV, const D3DVIEWPORT8* pViewport, + const D3DXMATRIX* pProjection, const D3DXMATRIX* pView, const D3DXMATRIX* pWorld); +D3DXVECTOR3* D3DXVec3Unproject(D3DXVECTOR3* pOut, const D3DXVECTOR3* pV, const D3DVIEWPORT8* pViewport, + const D3DXMATRIX* pProjection, const D3DXMATRIX* pView, const D3DXMATRIX* pWorld); // --- D3DXMATRIX --- inline D3DXMATRIX* D3DXMatrixIdentity(D3DXMATRIX* pOut) diff --git a/extension/tests/port_login_flow_test.cpp b/extension/tests/port_login_flow_test.cpp index 5e6da489..a18abc68 100644 --- a/extension/tests/port_login_flow_test.cpp +++ b/extension/tests/port_login_flow_test.cpp @@ -51,6 +51,7 @@ #include "UserInterface/PythonItem.h" #include "UserInterface/StdAfx.h" #include "UserInterface/PythonExchange.h" +#include "UserInterface/PythonTextTail.h" #include "UserInterface/PythonCharacterManager.h" #include "UserInterface/InstanceBase.h" #include "UserInterface/PythonBackground.h" @@ -513,10 +514,34 @@ int main(int argc, char** argv) std::string(owner) == FakeLoginServer::kCharacterName); CHECK(ground.GetVirtualNumberOfGroundItem(FakeLoginServer::kGroundItemVID) == FakeLoginServer::kGroundItemVnum); std::printf("port_login_flow_test: ground item at %.0f %.0f\n", item_pos.x, item_pos.y); + // CPythonItem::CreateItem registered the item's name tail (CPythonTextTail::RegisterItemTextTail) next to + // the actor's character tail. Shown and projected, the name is pickable on screen: textTail.Pick is how + // game.GameWindow.OnRender finds the item under the cursor while names are shown. + auto tail_info = [] { + std::string info; + CPythonTextTail::Instance().GetInfo(&info); + return info; + }; + std::printf("port_login_flow_test: %s\n", tail_info().c_str()); + CHECK(tail_info().find("ItemTail (Map 1,") != std::string::npos); + CHECK(tail_info().find("ChrTail (Map 1,") != std::string::npos); + // Holding ALT (game.GameWindow.ShowName) makes OnRender show every tail in range and project it after + // app.RenderGame; the item tail keeps that screen rect, which is what textTail.Pick tests. + PythonBoot::UIKey(DIK_LMENU, true); + pump_until(0.1, [] { return false; }); + PythonBoot::UIKey(DIK_LMENU, false); + int picked = -1, pick_x = 0, pick_y = 0; + for (int y = 0; y < 1200 && picked < 0; y += 2) + for (int x = 0; x < 1600 && picked < 0; x += 2) + if ((picked = CPythonTextTail::Instance().Pick(x, y)) >= 0) + pick_x = x, pick_y = y; + std::printf("port_login_flow_test: item name tail picked at %d %d\n", pick_x, pick_y); + CHECK(picked == (int) FakeLoginServer::kGroundItemVID); CHECK(py("import player\nplayer.PickCloseItem()")); const std::string pickup = "game2:pickup " + std::to_string(FakeLoginServer::kGroundItemVID); CHECK(pump_until(2, [&] { return server->Has(pickup); })); CHECK(pump_until(2, [&] { return !ground.GetGroundItemPosition(FakeLoginServer::kGroundItemVID, &item_pos); })); + CHECK(tail_info().find("ItemTail (Map 0,") != std::string::npos); } // Exchange (CPythonExchange + exchange module): net.SendExchangeStartPacket → the fake partner's GC_EXCHANGE