WIP port 4-d quest dialogue modules

This commit is contained in:
shenlei
2026-09-24 09:53:02 +09:00
parent a12e7fea42
commit ee7a9cd3e2
22 changed files with 2713 additions and 205 deletions
+1
View File
@@ -543,3 +543,4 @@
{"time": "2026-09-24T19:00:00Z", "event": "port", "unit": "batch 4-a text input: EterLib/IME.cpp (ADAPTED), UserInterface/PythonIME.cpp, PythonIMEModule.cpp", "detail": "ime module ported; IMM/TSF-free CIME in platform/; Godot key events -> ui_ime_key/ui_char -> CWindowManager RunIME*; login typed through the IME; in-game chat CG_CHAT round-trip", "tests": ["port.login_flow (typed id/password with edits, chat send/echo/close)", "port.login_live", "python_game_render_test (Godot key events)"]} {"time": "2026-09-24T19:00:00Z", "event": "port", "unit": "batch 4-a text input: EterLib/IME.cpp (ADAPTED), UserInterface/PythonIME.cpp, PythonIMEModule.cpp", "detail": "ime module ported; IMM/TSF-free CIME in platform/; Godot key events -> ui_ime_key/ui_char -> CWindowManager RunIME*; login typed through the IME; in-game chat CG_CHAT round-trip", "tests": ["port.login_flow (typed id/password with edits, chat send/echo/close)", "port.login_live", "python_game_render_test (Godot key events)"]}
{"time": "2026-09-24T20:00:00Z", "event": "verify", "unit": "batch 4-b combat: InstanceBaseBattle / ActorInstanceCollisionDetection / PythonPlayerInput (already ported)", "detail": "fake server spawns a stray dog outside the BANPK safe disc, answers CG_TARGET/CG_ATTACK with GC_TARGET/GC_DAMAGE_INFO/GC_DEAD; held left button walks up and combo-attacks until death; damage numbers wait on CPythonSystem (4-g)", "tests": ["port.login_flow (step 9 held-button kill)", "port.login_live", "python_game_render_test (Godot mouse events, target HP 100/66/33/0)"]} {"time": "2026-09-24T20:00:00Z", "event": "verify", "unit": "batch 4-b combat: InstanceBaseBattle / ActorInstanceCollisionDetection / PythonPlayerInput (already ported)", "detail": "fake server spawns a stray dog outside the BANPK safe disc, answers CG_TARGET/CG_ATTACK with GC_TARGET/GC_DAMAGE_INFO/GC_DEAD; held left button walks up and combo-attacks until death; damage numbers wait on CPythonSystem (4-g)", "tests": ["port.login_flow (step 9 held-button kill)", "port.login_live", "python_game_render_test (Godot mouse events, target HP 100/66/33/0)"]}
{"time": "2026-09-24T21:00:00Z", "event": "port", "unit": "batch 4-c shop: UserInterface/PythonShop.cpp (+ PythonApplicationCursor.cpp, SetMouseHandler adapted)", "detail": "shop module ported verbatim; app cursor state moved into the platform CPythonApplication (uishop reads app.GetCursor, which dereferenced the missing application object); fake server general store answers CG_ON_CLICK/CG_SHOP BUY/SELL2/END like CShop::AddGuest/Buy and CShopManager::Sell", "tests": ["port.login_flow (step 10 buy/sell/ESC)", "port.login_live", "python_game_render_test"]} {"time": "2026-09-24T21:00:00Z", "event": "port", "unit": "batch 4-c shop: UserInterface/PythonShop.cpp (+ PythonApplicationCursor.cpp, SetMouseHandler adapted)", "detail": "shop module ported verbatim; app cursor state moved into the platform CPythonApplication (uishop reads app.GetCursor, which dereferenced the missing application object); fake server general store answers CG_ON_CLICK/CG_SHOP BUY/SELL2/END like CShop::AddGuest/Buy and CShopManager::Sell", "tests": ["port.login_flow (step 10 buy/sell/ESC)", "port.login_live", "python_game_render_test"]}
{"time": "2026-09-24T22:00:00Z", "event": "wip", "unit": "batch 4-d NPC quest dialogue", "detail": "Added event manager, quest state/module, event manager module, parser and DBCS helpers; fake server now has an Oldster NPC and preliminary GC_SCRIPT / CG_SCRIPT_ANSWER exchange. This is explicitly unfinished: login-flow UI round trip and rendering interaction are not wired/verified; all five source-unit port-map entries remain TODO.", "tests": []}
+44
View File
@@ -0,0 +1,44 @@
{
"reference": "EterLib/parser.cpp",
"reference_sha256": "1b81a802b14e1a5958aaf981ca81248e389ad5c6f572858fd31e4afb7674ba86",
"priority": "P4",
"contracts": [],
"functions": {
"LocaleString_FindChar": {
"status": "TODO"
},
"LocaleString_RightTrim": {
"status": "TODO"
},
"OLD_rtrim": {
"status": "TODO"
},
"LocaleString_Skip": {
"status": "TODO"
},
"Group::GetArg": {
"status": "TODO"
},
"Group::Create": {
"status": "TODO"
},
"Group::GetCmd": {
"status": "TODO"
},
"Group::ReadCmd": {
"status": "TODO"
},
"Group::GetError": {
"status": "TODO"
},
"Group::SetError": {
"status": "TODO"
},
"Group::Group": {
"status": "TODO"
},
"Group::~Group": {
"status": "TODO"
}
}
}
@@ -0,0 +1,128 @@
{
"reference": "UserInterface/PythonEventManager.cpp",
"reference_sha256": "db306daefe5948ad7b3d533266bea7ef04826b22311c72d421c4f23c647de8e2",
"priority": "P4",
"contracts": [],
"functions": {
"ShowArgument": {
"status": "TODO"
},
"GetArgumentString": {
"status": "TODO"
},
"GetArgument": {
"status": "TODO"
},
"GetCameraSettingFromArgList": {
"status": "TODO"
},
"CPythonEventManager::__InitEventSet": {
"status": "TODO"
},
"CPythonEventManager::RegisterEventSet": {
"status": "TODO"
},
"CPythonEventManager::RegisterEventSetFromString": {
"status": "TODO"
},
"CPythonEventManager::ClearEventSeti": {
"status": "TODO"
},
"CPythonEventManager::__ClearEventSetp": {
"status": "TODO"
},
"CPythonEventManager::GetEmptyEventSetSlot": {
"status": "TODO"
},
"CPythonEventManager::SetRestrictedCount": {
"status": "TODO"
},
"CPythonEventManager::SetEventHandler": {
"status": "TODO"
},
"CPythonEventManager::GetEventSetLocalYPosition": {
"status": "TODO"
},
"CPythonEventManager::AddEventSetLocalYPosition": {
"status": "TODO"
},
"CPythonEventManager::InsertText": {
"status": "TODO"
},
"CPythonEventManager::UpdateEventSet": {
"status": "TODO"
},
"CPythonEventManager::SetEventSetWidth": {
"status": "TODO"
},
"CPythonEventManager::ProcessEventSet": {
"status": "TODO"
},
"CPythonEventManager::RenderEventSet": {
"status": "TODO"
},
"CPythonEventManager::Skip": {
"status": "TODO"
},
"CPythonEventManager::IsWait": {
"status": "TODO"
},
"CPythonEventManager::EndEventProcess": {
"status": "TODO"
},
"CPythonEventManager::MakeNextButton": {
"status": "TODO"
},
"CPythonEventManager::MakeQuestion": {
"status": "TODO"
},
"CPythonEventManager::SelectAnswer": {
"status": "TODO"
},
"CPythonEventManager::SetVisibleStartLine": {
"status": "TODO"
},
"CPythonEventManager::SetVisibleLineCount": {
"status": "TODO"
},
"CPythonEventManager::GetVisibleStartLine": {
"status": "TODO"
},
"CPythonEventManager::GetLineCount": {
"status": "TODO"
},
"CPythonEventManager::ClearLine": {
"status": "TODO"
},
"CPythonEventManager::__InsertLine": {
"status": "TODO"
},
"CPythonEventManager::RefreshLinePosition": {
"status": "TODO"
},
"CPythonEventManager::__AddSpace": {
"status": "TODO"
},
"CPythonEventManager::GetScriptEventIndex": {
"status": "TODO"
},
"CPythonEventManager::CheckEventSetIndex": {
"status": "TODO"
},
"CPythonEventManager::Destroy": {
"status": "TODO"
},
"CPythonEventManager::SetInterfaceWindow": {
"status": "TODO"
},
"CPythonEventManager::SetLeftTimeString": {
"status": "TODO"
},
"CPythonEventManager::CPythonEventManager": {
"status": "TODO"
},
"CPythonEventManager::~CPythonEventManager": {
"status": "TODO"
}
}
}
@@ -0,0 +1,80 @@
{
"reference": "UserInterface/PythonEventManagerMoudle.cpp",
"reference_sha256": "104c2b16cef91b4d41844caa10851ff82e8e2766c264aaaeca3b7f246a17e4ae",
"priority": "P4",
"contracts": [],
"functions": {
"eventRegisterEventSet": {
"status": "TODO"
},
"eventRegisterEventSetFromString": {
"status": "TODO"
},
"eventClearEventSet": {
"status": "TODO"
},
"eventSetRestrictedCount": {
"status": "TODO"
},
"eventGetEventSetLocalYPosition": {
"status": "TODO"
},
"eventAddEventSetLocalYPosition": {
"status": "TODO"
},
"eventInsertText": {
"status": "TODO"
},
"eventInsertTextInline": {
"status": "TODO"
},
"eventUpdateEventSet": {
"status": "TODO"
},
"eventRenderEventSet": {
"status": "TODO"
},
"eventSetEventSetWidth": {
"status": "TODO"
},
"eventSkip": {
"status": "TODO"
},
"eventIsWait": {
"status": "TODO"
},
"eventEndEventProcess": {
"status": "TODO"
},
"eventSetEventHandler": {
"status": "TODO"
},
"eventSelectAnswer": {
"status": "TODO"
},
"eventGetLineCount": {
"status": "TODO"
},
"eventSetVisibleStartLine": {
"status": "TODO"
},
"eventGetVisibleStartLine": {
"status": "TODO"
},
"eventQuestButtonClick": {
"status": "TODO"
},
"eventSetInterfaceWindow": {
"status": "TODO"
},
"eventSetLeftTimeString": {
"status": "TODO"
},
"eventDestroy": {
"status": "TODO"
},
"initEvent": {
"status": "TODO"
}
}
}
@@ -0,0 +1,14 @@
{
"reference": "UserInterface/PythonGameEventManagerModule.cpp",
"reference_sha256": "233eb8e6439422e3262240f8e22e8df6a4049122fe110a218b5c952207414e2e",
"priority": "P4",
"contracts": [],
"functions": {
"eventMgrUpdate": {
"status": "TODO"
},
"initeventmgr": {
"status": "TODO"
}
}
}
@@ -0,0 +1,77 @@
{
"reference": "UserInterface/PythonQuest.cpp",
"reference_sha256": "2e48148f3da65c4e286843d1b225aaa2021f2731f9f024fe989d1489c6e5f546",
"priority": "P3",
"contracts": [],
"functions": {
"CPythonQuest::RegisterQuestInstance": {
"status": "TODO"
},
"CPythonQuest::DeleteQuestInstance": {
"status": "TODO"
},
"CPythonQuest::IsQuest": {
"status": "TODO"
},
"CPythonQuest::MakeQuest": {
"status": "TODO"
},
"CPythonQuest::SetQuestTitle": {
"status": "TODO"
},
"CPythonQuest::SetQuestClockName": {
"status": "TODO"
},
"CPythonQuest::SetQuestCounterName": {
"status": "TODO"
},
"CPythonQuest::SetQuestClockValue": {
"status": "TODO"
},
"CPythonQuest::SetQuestCounterValue": {
"status": "TODO"
},
"CPythonQuest::SetQuestIconFileName": {
"status": "TODO"
},
"CPythonQuest::GetQuestCount": {
"status": "TODO"
},
"CPythonQuest::GetQuestInstancePtr": {
"status": "TODO"
},
"CPythonQuest::__GetQuestInstancePtr": {
"status": "TODO"
},
"CPythonQuest::__Initialize": {
"status": "TODO"
},
"CPythonQuest::Clear": {
"status": "TODO"
},
"CPythonQuest::CPythonQuest": {
"status": "TODO"
},
"CPythonQuest::~CPythonQuest": {
"status": "TODO"
},
"questGetQuestCount": {
"status": "TODO"
},
"questGetQuestData": {
"status": "TODO"
},
"questGetQuestIndex": {
"status": "TODO"
},
"questGetQuestLastTime": {
"status": "TODO"
},
"questClear": {
"status": "TODO"
},
"initquest": {
"status": "TODO"
}
}
}
+2 -1
View File
@@ -450,11 +450,12 @@ extension/third_party/cpython-2.7.18/ # 静态库(2P 批次
批次 3-b EffectLib(2026-09-24):`EffectLib/` 18 个 .cpp 原样移植(EffectManager、EffectData/Instance、EffectMesh、粒子系统、SimpleLight 等),删除 `platform/EffectLib` 生成的桩骨架;唯一改动是 `Type.h` 里 `BILLBOARD_TYPE_2FACE` 行尾注释的 `\` 被 clang 当作续行吞掉下一行,去掉反斜杠。`platform/EterBase/Utils.cpp` 剩下的 21 个桩按 §3 照抄 40250 函数体(`GetOnlyPathName`/`IsGlobalFileName` 缺失会让 .msf 以相对名挂的 .mse 丢目录,106 个 RegisterEffect 报错),Win32 调用换成 POSIX 的记 ADAPTED;平台 `CPythonResource` 补上 40250 的 `mde`(CEffectMesh)和 `mdatr`(CAttributeData)资源类型。`port.utils` 覆盖路径/分词/命令行辅助函数;`port.login_flow`:在主角脚下 `CreateEffect(level_up.mse)`,RenderGame 的 `CEffectManager::Render` 录出 levelup_1 贴图的 alpha 混合粒子绘制(dust、dropitem 同样出现)。40250 声明但未定义 `GetRenderingEffectCount`,测试不用。 批次 3-b EffectLib(2026-09-24):`EffectLib/` 18 个 .cpp 原样移植(EffectManager、EffectData/Instance、EffectMesh、粒子系统、SimpleLight 等),删除 `platform/EffectLib` 生成的桩骨架;唯一改动是 `Type.h` 里 `BILLBOARD_TYPE_2FACE` 行尾注释的 `\` 被 clang 当作续行吞掉下一行,去掉反斜杠。`platform/EterBase/Utils.cpp` 剩下的 21 个桩按 §3 照抄 40250 函数体(`GetOnlyPathName`/`IsGlobalFileName` 缺失会让 .msf 以相对名挂的 .mse 丢目录,106 个 RegisterEffect 报错),Win32 调用换成 POSIX 的记 ADAPTED;平台 `CPythonResource` 补上 40250 的 `mde`(CEffectMesh)和 `mdatr`(CAttributeData)资源类型。`port.utils` 覆盖路径/分词/命令行辅助函数;`port.login_flow`:在主角脚下 `CreateEffect(level_up.mse)`,RenderGame 的 `CEffectManager::Render` 录出 levelup_1 贴图的 alpha 混合粒子绘制(dust、dropitem 同样出现)。40250 声明但未定义 `GetRenderingEffectCount`,测试不用。
批次 3-c CMapOutdoor(2026-09-24):`GameLib/` 的 Area、AreaTerrain、DungeonBlock、MapBase、MapManager、MapOutdoor(含 IndexBuffer/Load/Quadtree/Render/Update)、MapType、MonsterAreaInfo、Property/PropertyLoader/PropertyManager、SnowEnvironment/SnowParticle、TerrainDecal/Patch/Quadtree,`PRTerrainLib/` Terrain、TextureSet,`UserInterface/PythonBackground(+Module)`,`EterLib/ColorTransitionHelper` 原样移植,删除 pending 替身与 background 模块桩(含 GameplayModuleConstants 里的常量桩);`EterBase/FileDir`、`TempFile` 在 platform 下照抄 40250 函数体(Win32 查找/临时文件走 common 垫片,记 ADAPTED)。`TEMP_CAreaLoaderThread` 在 40250 已全部注释掉,.cpp 不编(N_A)。手改两处:`CMapManager::BeginEnvironment` 的 `ApplyLight` 标签指针经 `uintptr_t` 转 DWORD;`CTerrainImpl::LoadWaterMap` 按 32 位读 water.wtr 的 `long` 再逐个扩宽(LP64 下 `long` 是 8 字节,否则报 FILE DATA SIZE 16392 != 16400)。平台文件:HTP/STP 地形绘制与 `RenderWater` 为空(地形仍由 Godot 适配层画,§3),STP 的 Initialize/Create/Destroy、水面贴图载入、角色阴影贴图创建照抄;`BeginRenderCharacterShadowToTexture` 返回 false(记 DIVERGENT)。录制设备补齐 `CreateTexture/VertexBuffer/IndexBuffer/DepthStencilSurface`、`Get/SetRenderTarget`、`LightEnable`、`Clear`(CPU 表面/贴图),离屏渲染目标的绘制丢弃,light0 只在启用且为方向光时记录。小地图:`CPythonMiniMap` 在正交投影下画 minimap.dds 瓦片并以 stage 1 相机空间坐标 × `D3DTS_TEXTURE1` 采样 `minimap_image_filter.dds` 做圆形遮罩——录制设备把「正交投影 + 未变换顶点」的绘制按四边形转成 UI 命令(带 mask 贴图与四角 mask uv),`UIRenderAddImage` 按 D3DTS_WORLD 平移、按 TFACTOR 调色(标记点),`python_ui_surface.gd` 用按序的子 canvas item 画 UI 流,带遮罩的图各用一个 shader 材质。`port.login_flow`:Warp 后 `IsMapOutdoor()`、`miniMap.GetAtlasSize()` 已载入、`background.GetCurrentMapName()` 非空;`python_game_render_test` 新增「minimap tiles drawn through the filter mask」(9 块)。截图里主角旁地上的剑是假服 entergame 时按 `GC_ITEM_GROUND_ADD` 放的地面物品(vnum 19,P3-b 拾取测试用),不是武器挂点问题;假服角色没装备武器。 批次 3-c CMapOutdoor(2026-09-24):`GameLib/` 的 Area、AreaTerrain、DungeonBlock、MapBase、MapManager、MapOutdoor(含 IndexBuffer/Load/Quadtree/Render/Update)、MapType、MonsterAreaInfo、Property/PropertyLoader/PropertyManager、SnowEnvironment/SnowParticle、TerrainDecal/Patch/Quadtree,`PRTerrainLib/` Terrain、TextureSet,`UserInterface/PythonBackground(+Module)`,`EterLib/ColorTransitionHelper` 原样移植,删除 pending 替身与 background 模块桩(含 GameplayModuleConstants 里的常量桩);`EterBase/FileDir`、`TempFile` 在 platform 下照抄 40250 函数体(Win32 查找/临时文件走 common 垫片,记 ADAPTED)。`TEMP_CAreaLoaderThread` 在 40250 已全部注释掉,.cpp 不编(N_A)。手改两处:`CMapManager::BeginEnvironment` 的 `ApplyLight` 标签指针经 `uintptr_t` 转 DWORD;`CTerrainImpl::LoadWaterMap` 按 32 位读 water.wtr 的 `long` 再逐个扩宽(LP64 下 `long` 是 8 字节,否则报 FILE DATA SIZE 16392 != 16400)。平台文件:HTP/STP 地形绘制与 `RenderWater` 为空(地形仍由 Godot 适配层画,§3),STP 的 Initialize/Create/Destroy、水面贴图载入、角色阴影贴图创建照抄;`BeginRenderCharacterShadowToTexture` 返回 false(记 DIVERGENT)。录制设备补齐 `CreateTexture/VertexBuffer/IndexBuffer/DepthStencilSurface`、`Get/SetRenderTarget`、`LightEnable`、`Clear`(CPU 表面/贴图),离屏渲染目标的绘制丢弃,light0 只在启用且为方向光时记录。小地图:`CPythonMiniMap` 在正交投影下画 minimap.dds 瓦片并以 stage 1 相机空间坐标 × `D3DTS_TEXTURE1` 采样 `minimap_image_filter.dds` 做圆形遮罩——录制设备把「正交投影 + 未变换顶点」的绘制按四边形转成 UI 命令(带 mask 贴图与四角 mask uv),`UIRenderAddImage` 按 D3DTS_WORLD 平移、按 TFACTOR 调色(标记点),`python_ui_surface.gd` 用按序的子 canvas item 画 UI 流,带遮罩的图各用一个 shader 材质。`port.login_flow`:Warp 后 `IsMapOutdoor()`、`miniMap.GetAtlasSize()` 已载入、`background.GetCurrentMapName()` 非空;`python_game_render_test` 新增「minimap tiles drawn through the filter mask」(9 块)。截图里主角旁地上的剑是假服 entergame 时按 `GC_ITEM_GROUND_ADD` 放的地面物品(vnum 19,P3-b 拾取测试用),不是武器挂点问题;假服角色没装备武器。
默认路由切换(2026-09-24):`client_main` 不设 `MT_PORT_PATH` 时走 port 路由(`python_ui_surface.gd` + `python_3d_surface.gd` 跑 40250 system.py),`MT_PORT_PATH=legacy` 回到 GDScript `AppFlow`,规则见 §2「迁移方式」。已验证:实例化 `client_main.tscn`,默认/`port` 起 PythonUISurface + Python3DSurface 并产出 UI 命令,`legacy` 起 AppFlow + NetTrace。下一步:port 路由稳定后删除 legacy 宿主与开关。 默认路由切换(2026-09-24):`client_main` 不设 `MT_PORT_PATH` 时走 port 路由(`python_ui_surface.gd` + `python_3d_surface.gd` 跑 40250 system.py),`MT_PORT_PATH=legacy` 回到 GDScript `AppFlow`,规则见 §2「迁移方式」。已验证:实例化 `client_main.tscn`,默认/`port` 起 PythonUISurface + Python3DSurface 并产出 UI 命令,`legacy` 起 AppFlow + NetTrace。下一步:port 路由稳定后删除 legacy 宿主与开关。
批次 4(2026-09-24 起):补齐 port 路由的玩法,以便删除 legacy。顺序:4-a 文字输入 → 4-b 战斗 → 4-c 商店 → 4-d 任务/NPC 对话 → 4-e 仓库 → 4-f 好友/公会/公会标记 → 4-g systemSetting → 4-h 声音。 批次 4(2026-09-24 起):补齐 port 路由的玩法,以便删除 legacy。顺序:4-a 文字输入(完成) → 4-b 战斗(完成) → 4-c 商店(完成) → 4-d 任务/NPC 对话(进行中,未完成) → 4-e 仓库(未开始) → 4-f 好友/公会/公会标记(未开始) → 4-g systemSetting(未开始) → 4-h 声音(未开始)。
批次 4-a 文字输入(2026-09-24):`UserInterface/PythonIME.cpp` 与 `PythonIMEModule.cpp` 原样移植(仅把 MSVC 专有的 `unsigned char(x)` 函数式转换改成 C 式转换),删除 ime 模块桩,`initime()` 按 40250 顺序在 `initdbg()` 之后,`CPythonApplication::Create` 按原序 `CPythonIME::Create` + `SetText("", 0)`。`EterLib/IME.cpp` 在 platform 下:缓冲区、光标、`WMChar`(退格、`|` 转义、按输入代码页 `MultiByteToWideChar`)、候选/读音状态、数字/字符串模式等函数体照抄;IMM/TSF 在 Godot 下没有对应物,组字相关处理器为空、`EnableIME` 恒关,已提交的非单字节文本由 `PythonBoot::UIChar` 模拟 `GCS_RESULTSTR`(`InsertString` + `OnUpdate`),`DelCurPos` 用 memmove 替代重叠的 wcscpy(全部记 ADAPTED)。`GameApplicationAdapter` 的 `RunIME*` 转发到 `CWindowManager`;注意 `RunIMEUpdate/RunIMEKeyDown` 只送达正在渲染的活动窗口(登录框未打开时输入被丢弃)。Godot 侧 `python_ui_surface.gd` 把按键送 `ui_ime_key`(VK)再送 `ui_char`(unicode)。`port.login_flow` 与 `port.login_live` 打开登录框后逐字键入账号(含 LEFT/DELETE/BACK 编辑)、TAB 切到密码、RETURN 登录(凭据只来自环境变量,测试随后删除 Python 局部变量);进游戏后 RETURN 打开聊天、键入 hello、RETURN 发出 `CG_CHAT`(假服回显 `GC_CHAT`),空行 RETURN 关闭聊天(40250 发完后只清空、不关闭)。`python_game_render_test` 用 Godot 按键事件走同一路径登录。 批次 4-a 文字输入(2026-09-24):`UserInterface/PythonIME.cpp` 与 `PythonIMEModule.cpp` 原样移植(仅把 MSVC 专有的 `unsigned char(x)` 函数式转换改成 C 式转换),删除 ime 模块桩,`initime()` 按 40250 顺序在 `initdbg()` 之后,`CPythonApplication::Create` 按原序 `CPythonIME::Create` + `SetText("", 0)`。`EterLib/IME.cpp` 在 platform 下:缓冲区、光标、`WMChar`(退格、`|` 转义、按输入代码页 `MultiByteToWideChar`)、候选/读音状态、数字/字符串模式等函数体照抄;IMM/TSF 在 Godot 下没有对应物,组字相关处理器为空、`EnableIME` 恒关,已提交的非单字节文本由 `PythonBoot::UIChar` 模拟 `GCS_RESULTSTR`(`InsertString` + `OnUpdate`),`DelCurPos` 用 memmove 替代重叠的 wcscpy(全部记 ADAPTED)。`GameApplicationAdapter` 的 `RunIME*` 转发到 `CWindowManager`;注意 `RunIMEUpdate/RunIMEKeyDown` 只送达正在渲染的活动窗口(登录框未打开时输入被丢弃)。Godot 侧 `python_ui_surface.gd` 把按键送 `ui_ime_key`(VK)再送 `ui_char`(unicode)。`port.login_flow` 与 `port.login_live` 打开登录框后逐字键入账号(含 LEFT/DELETE/BACK 编辑)、TAB 切到密码、RETURN 登录(凭据只来自环境变量,测试随后删除 Python 局部变量);进游戏后 RETURN 打开聊天、键入 hello、RETURN 发出 `CG_CHAT`(假服回显 `GC_CHAT`),空行 RETURN 关闭聊天(40250 发完后只清空、不关闭)。`python_game_render_test` 用 Godot 按键事件走同一路径登录。
批次 4-b 战斗(2026-09-24):战斗代码此前已原样移植(`InstanceBaseBattle`、`ActorInstanceCollisionDetection`、`PythonPlayerInput*`、`uiTarget`),本批只补验证。假服在出生点北 18 m 放一只野狗(npclist 101,怪物不发 `GC_CHAR_ADDITIONAL_INFO`,名字取自 mob_proto;发了会 syserr),出生点在村庄 `ATTRIBUTE_BANPK` 圈内,`CInstanceBase::IsInSafe()` 会挡掉所有命中。`CG_TARGET` 回 `GC_TARGET`,`CG_ATTACK` 回 `GC_DAMAGE_INFO` + `GC_TARGET`,第三下回 `GC_DEAD`。与 40250 相同:单击怪物(`__OnClickActor`)只走过去,按住左键(MBF_SMART → `__OnPressActor`)才走近并连击。`port.login_flow` 第 9 步用显式透视矩阵投影野狗、按住左键直到死亡,检查 target 面板和至少 3 次 `CG_ATTACK`;`python_game_render_test` 用 Godot 鼠标事件走同一路径,截图 combat_mid/combat.png(野狗位于镜头与战士的连线上,被战士挡住,属正常)。新增宿主/测试辅助 `PythonBoot::Evaluate` / `Metin2PythonHost.evaluate`(40250 无对应物)。`python_3d_surface.gd` 修正:D3D 固定管线中材质 emissive 先加进顶点色,再被纹理 MODULATE,Godot 的 emission 却加在纹理之后,导致黑底加色的点击光圈(click_glow_select copy.jpg)显示成灰色方块,角色也被 0.32 的 emissive 洗灰;现在 emission 乘以同一张纹理(`EMISSION_OP_MULTIPLY`)。战斗步骤先等镜头稳定(上一步的走路可能尚未结束),野狗投影落在小地图或任务栏下时先点地面走近一段。已知缺口:伤害数字依赖 `CPythonSystem::IsShowDamage()`,该单元仍在 pending 桩(4-g),故暂不显示。 批次 4-b 战斗(2026-09-24):战斗代码此前已原样移植(`InstanceBaseBattle`、`ActorInstanceCollisionDetection`、`PythonPlayerInput*`、`uiTarget`),本批只补验证。假服在出生点北 18 m 放一只野狗(npclist 101,怪物不发 `GC_CHAR_ADDITIONAL_INFO`,名字取自 mob_proto;发了会 syserr),出生点在村庄 `ATTRIBUTE_BANPK` 圈内,`CInstanceBase::IsInSafe()` 会挡掉所有命中。`CG_TARGET` 回 `GC_TARGET`,`CG_ATTACK` 回 `GC_DAMAGE_INFO` + `GC_TARGET`,第三下回 `GC_DEAD`。与 40250 相同:单击怪物(`__OnClickActor`)只走过去,按住左键(MBF_SMART → `__OnPressActor`)才走近并连击。`port.login_flow` 第 9 步用显式透视矩阵投影野狗、按住左键直到死亡,检查 target 面板和至少 3 次 `CG_ATTACK`;`python_game_render_test` 用 Godot 鼠标事件走同一路径,截图 combat_mid/combat.png(野狗位于镜头与战士的连线上,被战士挡住,属正常)。新增宿主/测试辅助 `PythonBoot::Evaluate` / `Metin2PythonHost.evaluate`(40250 无对应物)。`python_3d_surface.gd` 修正:D3D 固定管线中材质 emissive 先加进顶点色,再被纹理 MODULATE,Godot 的 emission 却加在纹理之后,导致黑底加色的点击光圈(click_glow_select copy.jpg)显示成灰色方块,角色也被 0.32 的 emissive 洗灰;现在 emission 乘以同一张纹理(`EMISSION_OP_MULTIPLY`)。战斗步骤先等镜头稳定(上一步的走路可能尚未结束),野狗投影落在小地图或任务栏下时先点地面走近一段。已知缺口:伤害数字依赖 `CPythonSystem::IsShowDamage()`,该单元仍在 pending 桩(4-g),故暂不显示。
批次 4-c 商店(2026-09-24):`PythonShop.cpp` 原样移植(`shop` 模块 + `SHOP_SLOT_COUNT`/金币常量,`initshop()` 按 40250 次序接在 `initfly()` 之后;40250 中间的 `initsnd`/`initeventmgr` 尚未移植),删去 pending 桩与模块桩。uishop 在 `OnUpdate` 里读 `app.GetCursor()`,而 `GetCursorNum()` 是头文件内联、读的是不存在的 CPythonApplication 对象的成员,导致空指针崩溃;现把 40250 `PythonApplicationCursor.cpp`(SetCursorNum/Visible/Mode 等)和 `SetMouseHandler` 原样搬进 platform CPythonApplication,成员改为文件静态量,`GetCursorNum` 改为非内联(头文件 PORT 注释);宿主不画硬件光标,`[0, CURSOR_COUNT)` 内的形状都视为已加载。假服在野狗东 4 m 放杂货商(npclist 9003),`CG_ON_CLICK` 回 `GC_SHOP START`(货架 0 为 27001 红药,50 金),`CG_SHOP BUY` 先扣金(`GC_POINT_CHANGE`)再 `GC_ITEM_SET`,`SELL2` 回 `GC_ITEM_DEL` + 加 9 金,`END` 回 `GC_SHOP END`(与 40250 `CShop::Buy`/`CShopManager::Sell` 次序相同;包的附加长度按 `CInputMain::Shop` 读)。`port.login_flow` 第 10 步:左键点商人走过去打开商店窗口和背包,右键货架 0 购买,背包格 0 拾起后放到货架空格出售,ESC 关闭,逐步检查 `CG_SHOP` 字节、背包格和金币。 批次 4-c 商店(2026-09-24):`PythonShop.cpp` 原样移植(`shop` 模块 + `SHOP_SLOT_COUNT`/金币常量,`initshop()` 按 40250 次序接在 `initfly()` 之后;40250 中间的 `initsnd`/`initeventmgr` 尚未移植),删去 pending 桩与模块桩。uishop 在 `OnUpdate` 里读 `app.GetCursor()`,而 `GetCursorNum()` 是头文件内联、读的是不存在的 CPythonApplication 对象的成员,导致空指针崩溃;现把 40250 `PythonApplicationCursor.cpp`(SetCursorNum/Visible/Mode 等)和 `SetMouseHandler` 原样搬进 platform CPythonApplication,成员改为文件静态量,`GetCursorNum` 改为非内联(头文件 PORT 注释);宿主不画硬件光标,`[0, CURSOR_COUNT)` 内的形状都视为已加载。假服在野狗东 4 m 放杂货商(npclist 9003),`CG_ON_CLICK` 回 `GC_SHOP START`(货架 0 为 27001 红药,50 金),`CG_SHOP BUY` 先扣金(`GC_POINT_CHANGE`)再 `GC_ITEM_SET`,`SELL2` 回 `GC_ITEM_DEL` + 加 9 金,`END` 回 `GC_SHOP END`(与 40250 `CShop::Buy`/`CShopManager::Sell` 次序相同;包的附加长度按 `CInputMain::Shop` 读)。`port.login_flow` 第 10 步:左键点商人走过去打开商店窗口和背包,右键货架 0 购买,背包格 0 拾起后放到货架空格出售,ESC 关闭,逐步检查 `CG_SHOP` 字节、背包格和金币。
批次 4-d 任务/NPC 对话(进行中,未完成,2026-09-24):已加入 `PythonEventManager`、`PythonQuest`、`GameEventManager` 逻辑及 Python 模块,并开始扩展假服对话 NPC 和 `GC_SCRIPT` / `CG_SCRIPT_ANSWER` 应答;`parser.cpp` 和 DBCS 字符串步进也作为事件脚本解析依赖加入。当前还没有在 `port_login_flow` 中接通并验证完整的点击 NPC → 显示对话 → 选择答案 → NEXT/DONE 往返流程,也未做门禁/回归确认。相关逐函数审计仍为 `TODO`;本批不能记为完成。
每个切片都必须同时跑 legacy/port 双路回归;默认路由已切到 `port`(2026-09-24);port 路由稳定后删除总开关和剩余 legacy 宿主。 每个切片都必须同时跑 legacy/port 双路回归;默认路由已切到 `port`(2026-09-24);port 路由稳定后删除总开关和剩余 legacy 宿主。
@@ -36,21 +36,7 @@
// 40250 UserInterface/PythonBackgroundModule.cpp // 40250 UserInterface/PythonBackgroundModule.cpp
// 40250 UserInterface/PythonEventManagerMoudle.cpp // 40250 UserInterface/PythonEventManagerMoudle.cpp
void AddStubConstants_event(PyObject* poModule)
{
(void) poModule;
PyModule_AddIntConstant(poModule, "BOX_VISIBLE_LINE_COUNT", CPythonEventManager::BOX_VISIBLE_LINE_COUNT);
PyModule_AddIntConstant(poModule, "BUTTON_TYPE_NEXT", CPythonEventManager::BUTTON_TYPE_NEXT);
PyModule_AddIntConstant(poModule, "BUTTON_TYPE_DONE", CPythonEventManager::BUTTON_TYPE_DONE);
PyModule_AddIntConstant(poModule, "BUTTON_TYPE_CANCEL", CPythonEventManager::BUTTON_TYPE_CANCEL);
}
// 40250 UserInterface/PythonGameEventManagerModule.cpp // 40250 UserInterface/PythonGameEventManagerModule.cpp
void AddStubConstants_eventMgr(PyObject* poModule)
{
(void) poModule;
}
// 40250 UserInterface/PythonGuild.cpp // 40250 UserInterface/PythonGuild.cpp
void AddStubConstants_guild(PyObject* poModule) void AddStubConstants_guild(PyObject* poModule)
{ {
@@ -76,12 +62,6 @@ void AddStubConstants_profiler(PyObject* poModule)
} }
// 40250 UserInterface/PythonQuest.cpp // 40250 UserInterface/PythonQuest.cpp
void AddStubConstants_quest(PyObject* poModule)
{
(void) poModule;
PyModule_AddIntConstant(poModule, "QUEST_MAX_NUM", 5);
}
// 40250 UserInterface/PythonSafeBox.cpp // 40250 UserInterface/PythonSafeBox.cpp
void AddStubConstants_safebox(PyObject* poModule) void AddStubConstants_safebox(PyObject* poModule)
{ {
@@ -5,12 +5,9 @@
#include "../EterLib/UIRenderCommands.h" #include "../EterLib/UIRenderCommands.h"
// GameplayModuleConstants.cpp: each module's 40250 constants. // GameplayModuleConstants.cpp: each module's 40250 constants.
void AddStubConstants_event(PyObject* poModule);
void AddStubConstants_eventMgr(PyObject* poModule);
void AddStubConstants_guild(PyObject* poModule); void AddStubConstants_guild(PyObject* poModule);
void AddStubConstants_messenger(PyObject* poModule); void AddStubConstants_messenger(PyObject* poModule);
void AddStubConstants_profiler(PyObject* poModule); void AddStubConstants_profiler(PyObject* poModule);
void AddStubConstants_quest(PyObject* poModule);
void AddStubConstants_safebox(PyObject* poModule); void AddStubConstants_safebox(PyObject* poModule);
void AddStubConstants_snd(PyObject* poModule); void AddStubConstants_snd(PyObject* poModule);
void AddStubConstants_systemSetting(PyObject* poModule); void AddStubConstants_systemSetting(PyObject* poModule);
@@ -24,62 +21,6 @@ PyObject* stub_call_named(const char* name, PyObject* result = nullptr) {
return result ? result : Py_BuildValue("i", 0); return result ? result : Py_BuildValue("i", 0);
} }
// 40250 UserInterface/PythonEventManagerMoudle.cpp: event export names.
PyObject* stub_event_RegisterEventSet(PyObject*, PyObject*) { return stub_call_named("event.RegisterEventSet", Py_BuildValue("i", 0)); }
PyObject* stub_event_RegisterEventSetFromString(PyObject*, PyObject*) { return stub_call_named("event.RegisterEventSetFromString", Py_BuildValue("i", 0)); }
PyObject* stub_event_ClearEventSet(PyObject*, PyObject*) { return stub_call_named("event.ClearEventSet", Py_BuildNone()); }
PyObject* stub_event_SetRestrictedCount(PyObject*, PyObject*) { return stub_call_named("event.SetRestrictedCount", Py_BuildNone()); }
PyObject* stub_event_GetEventSetLocalYPosition(PyObject*, PyObject*) { return stub_call_named("event.GetEventSetLocalYPosition", Py_BuildValue("i", 0)); }
PyObject* stub_event_AddEventSetLocalYPosition(PyObject*, PyObject*) { return stub_call_named("event.AddEventSetLocalYPosition", Py_BuildNone()); }
PyObject* stub_event_InsertText(PyObject*, PyObject*) { return stub_call_named("event.InsertText", Py_BuildNone()); }
PyObject* stub_event_InsertTextInline(PyObject*, PyObject*) { return stub_call_named("event.InsertTextInline", Py_BuildNone()); }
PyObject* stub_event_UpdateEventSet(PyObject*, PyObject*) { return stub_call_named("event.UpdateEventSet", Py_BuildNone()); }
PyObject* stub_event_RenderEventSet(PyObject*, PyObject*) { return stub_call_named("event.RenderEventSet", Py_BuildNone()); }
PyObject* stub_event_SetEventSetWidth(PyObject*, PyObject*) { return stub_call_named("event.SetEventSetWidth", Py_BuildNone()); }
PyObject* stub_event_Skip(PyObject*, PyObject*) { return stub_call_named("event.Skip", Py_BuildNone()); }
PyObject* stub_event_IsWait(PyObject*, PyObject*) { return stub_call_named("event.IsWait", Py_BuildValue("i", 0)); }
PyObject* stub_event_EndEventProcess(PyObject*, PyObject*) { return stub_call_named("event.EndEventProcess", Py_BuildNone()); }
PyObject* stub_event_SelectAnswer(PyObject*, PyObject*) { return stub_call_named("event.SelectAnswer", Py_BuildNone()); }
PyObject* stub_event_GetLineCount(PyObject*, PyObject*) { return stub_call_named("event.GetLineCount", Py_BuildValue("i", 0)); }
PyObject* stub_event_SetVisibleStartLine(PyObject*, PyObject*) { return stub_call_named("event.SetVisibleStartLine", Py_BuildNone()); }
PyObject* stub_event_GetVisibleStartLine(PyObject*, PyObject*) { return stub_call_named("event.GetVisibleStartLine", Py_BuildValue("i", 0)); }
PyObject* stub_event_SetEventHandler(PyObject*, PyObject*) { return stub_call_named("event.SetEventHandler", Py_BuildNone()); }
PyObject* stub_event_SetInterfaceWindow(PyObject*, PyObject*) { return stub_call_named("event.SetInterfaceWindow", Py_BuildNone()); }
PyObject* stub_event_SetLeftTimeString(PyObject*, PyObject*) { return stub_call_named("event.SetLeftTimeString", Py_BuildNone()); }
PyObject* stub_event_QuestButtonClick(PyObject*, PyObject*) { return stub_call_named("event.QuestButtonClick", Py_BuildNone()); }
PyObject* stub_event_Destroy(PyObject*, PyObject*) { return stub_call_named("event.Destroy", Py_BuildNone()); }
PyMethodDef methods_event[] = {
{ "RegisterEventSet", stub_event_RegisterEventSet, METH_VARARGS },
{ "RegisterEventSetFromString", stub_event_RegisterEventSetFromString, METH_VARARGS },
{ "ClearEventSet", stub_event_ClearEventSet, METH_VARARGS },
{ "SetRestrictedCount", stub_event_SetRestrictedCount, METH_VARARGS },
{ "GetEventSetLocalYPosition", stub_event_GetEventSetLocalYPosition, METH_VARARGS },
{ "AddEventSetLocalYPosition", stub_event_AddEventSetLocalYPosition, METH_VARARGS },
{ "InsertText", stub_event_InsertText, METH_VARARGS },
{ "InsertTextInline", stub_event_InsertTextInline, METH_VARARGS },
{ "UpdateEventSet", stub_event_UpdateEventSet, METH_VARARGS },
{ "RenderEventSet", stub_event_RenderEventSet, METH_VARARGS },
{ "SetEventSetWidth", stub_event_SetEventSetWidth, METH_VARARGS },
{ "Skip", stub_event_Skip, METH_VARARGS },
{ "IsWait", stub_event_IsWait, METH_VARARGS },
{ "EndEventProcess", stub_event_EndEventProcess, METH_VARARGS },
{ "SelectAnswer", stub_event_SelectAnswer, METH_VARARGS },
{ "GetLineCount", stub_event_GetLineCount, METH_VARARGS },
{ "SetVisibleStartLine", stub_event_SetVisibleStartLine, METH_VARARGS },
{ "GetVisibleStartLine", stub_event_GetVisibleStartLine, METH_VARARGS },
{ "SetEventHandler", stub_event_SetEventHandler, METH_VARARGS },
{ "SetInterfaceWindow", stub_event_SetInterfaceWindow, METH_VARARGS },
{ "SetLeftTimeString", stub_event_SetLeftTimeString, METH_VARARGS },
{ "QuestButtonClick", stub_event_QuestButtonClick, METH_VARARGS },
{ "Destroy", stub_event_Destroy, METH_VARARGS },
{ NULL, NULL }
};
// 40250 UserInterface/PythonGameEventManagerModule.cpp: eventMgr export names.
PyObject* stub_eventMgr_Update(PyObject*, PyObject*) { return stub_call_named("eventMgr.Update", Py_BuildNone()); }
PyMethodDef methods_eventMgr[] = {
{ "Update", stub_eventMgr_Update, METH_VARARGS },
{ NULL, NULL }
};
// 40250 UserInterface/PythonGuild.cpp: guild export names. // 40250 UserInterface/PythonGuild.cpp: guild export names.
PyObject* stub_guild_IsGuildEnable(PyObject*, PyObject*) { return stub_call_named("guild.IsGuildEnable", Py_BuildValue("i", 0)); } PyObject* stub_guild_IsGuildEnable(PyObject*, PyObject*) { return stub_call_named("guild.IsGuildEnable", Py_BuildValue("i", 0)); }
PyObject* stub_guild_GuildIDToMarkID(PyObject*, PyObject*) { return stub_call_named("guild.GuildIDToMarkID", Py_BuildValue("i", 0)); } PyObject* stub_guild_GuildIDToMarkID(PyObject*, PyObject*) { return stub_call_named("guild.GuildIDToMarkID", Py_BuildValue("i", 0)); }
@@ -174,20 +115,6 @@ PyMethodDef methods_profiler[] = {
{ "Pop", stub_profiler_Pop, METH_VARARGS }, { "Pop", stub_profiler_Pop, METH_VARARGS },
{ NULL, NULL } { NULL, NULL }
}; };
// 40250 UserInterface/PythonQuest.cpp: quest export names.
PyObject* stub_quest_GetQuestCount(PyObject*, PyObject*) { return stub_call_named("quest.GetQuestCount", Py_BuildValue("i", 0)); }
PyObject* stub_quest_GetQuestData(PyObject*, PyObject*) { return stub_call_named("quest.GetQuestData", Py_BuildValue("sisi", "", 0, "", 0)); }
PyObject* stub_quest_GetQuestIndex(PyObject*, PyObject*) { return stub_call_named("quest.GetQuestIndex", Py_BuildValue("i", 0)); }
PyObject* stub_quest_GetQuestLastTime(PyObject*, PyObject*) { return stub_call_named("quest.GetQuestLastTime", Py_BuildValue("si", "", 0)); }
PyObject* stub_quest_Clear(PyObject*, PyObject*) { return stub_call_named("quest.Clear", Py_BuildNone()); }
PyMethodDef methods_quest[] = {
{ "GetQuestCount", stub_quest_GetQuestCount, METH_VARARGS },
{ "GetQuestData", stub_quest_GetQuestData, METH_VARARGS },
{ "GetQuestIndex", stub_quest_GetQuestIndex, METH_VARARGS },
{ "GetQuestLastTime", stub_quest_GetQuestLastTime, METH_VARARGS },
{ "Clear", stub_quest_Clear, METH_VARARGS },
{ NULL, NULL }
};
// 40250 UserInterface/PythonSafeBox.cpp: safebox export names. // 40250 UserInterface/PythonSafeBox.cpp: safebox export names.
PyObject* stub_safebox_GetCurrentSafeboxSize(PyObject*, PyObject*) { return stub_call_named("safebox.GetCurrentSafeboxSize", Py_BuildValue("i", 0)); } PyObject* stub_safebox_GetCurrentSafeboxSize(PyObject*, PyObject*) { return stub_call_named("safebox.GetCurrentSafeboxSize", Py_BuildValue("i", 0)); }
PyObject* stub_safebox_GetItemID(PyObject*, PyObject*) { return stub_call_named("safebox.GetItemID", Py_BuildValue("i", 0)); } PyObject* stub_safebox_GetItemID(PyObject*, PyObject*) { return stub_call_named("safebox.GetItemID", Py_BuildValue("i", 0)); }
@@ -344,16 +271,6 @@ PyMethodDef methods_udp[] = {
} // namespace } // namespace
void init_2v0_gameplay_stubs() { void init_2v0_gameplay_stubs() {
{
PyObject* module = Py_InitModule("event", methods_event);
PyModule_AddIntConstant(module, "__port_stub__", 1);
AddStubConstants_event(module);
}
{
PyObject* module = Py_InitModule("eventMgr", methods_eventMgr);
PyModule_AddIntConstant(module, "__port_stub__", 1);
AddStubConstants_eventMgr(module);
}
{ {
PyObject* module = Py_InitModule("guild", methods_guild); PyObject* module = Py_InitModule("guild", methods_guild);
PyModule_AddIntConstant(module, "__port_stub__", 1); PyModule_AddIntConstant(module, "__port_stub__", 1);
@@ -369,11 +286,6 @@ void init_2v0_gameplay_stubs() {
PyModule_AddIntConstant(module, "__port_stub__", 1); PyModule_AddIntConstant(module, "__port_stub__", 1);
AddStubConstants_profiler(module); AddStubConstants_profiler(module);
} }
{
PyObject* module = Py_InitModule("quest", methods_quest);
PyModule_AddIntConstant(module, "__port_stub__", 1);
AddStubConstants_quest(module);
}
{ {
PyObject* module = Py_InitModule("safebox", methods_safebox); PyObject* module = Py_InitModule("safebox", methods_safebox);
PyModule_AddIntConstant(module, "__port_stub__", 1); PyModule_AddIntConstant(module, "__port_stub__", 1);
@@ -400,10 +400,13 @@ bool run_main_script_body(const char* lpCmdLine, std::string* error)
initTextTail(); initTextTail();
initnet(); initnet();
initMiniMap(); initMiniMap();
initEvent();
initeffect(); initeffect();
initfly(); initfly();
initeventmgr();
initshop(); initshop();
initskill(); initskill();
initquest();
initBackground(); initBackground();
initServerStateChecker(); initServerStateChecker();
init_2v0_gameplay_stubs(); init_2v0_gameplay_stubs();
@@ -1,28 +0,0 @@
// Stand-in for the 40250 logic unit UserInterface/PythonEventManager.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/PythonEventManager.h"
#include "../../PlatformStub.h"
CPythonEventManager::CPythonEventManager()
{
MT_PLATFORM_STUB();
}
CPythonEventManager::~CPythonEventManager()
{
MT_PLATFORM_STUB();
}
auto CPythonEventManager::RegisterEventSetFromString(const std::string &) -> int
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
}
auto CPythonEventManager::SetVisibleLineCount(int, int) -> void
{
MT_PLATFORM_STUB();
}
@@ -1,68 +0,0 @@
// Stand-in for the 40250 logic unit UserInterface/PythonQuest.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/PythonQuest.h"
#include "../../PlatformStub.h"
CPythonQuest::CPythonQuest()
{
MT_PLATFORM_STUB();
}
CPythonQuest::~CPythonQuest()
{
MT_PLATFORM_STUB();
}
auto CPythonQuest::RegisterQuestInstance(const SQuestInstance &) -> void
{
MT_PLATFORM_STUB();
}
auto CPythonQuest::DeleteQuestInstance(DWORD) -> void
{
MT_PLATFORM_STUB();
}
auto CPythonQuest::IsQuest(DWORD) -> bool
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
}
auto CPythonQuest::MakeQuest(DWORD) -> void
{
MT_PLATFORM_STUB();
}
auto CPythonQuest::SetQuestTitle(DWORD, const char *) -> void
{
MT_PLATFORM_STUB();
}
auto CPythonQuest::SetQuestClockName(DWORD, const char *) -> void
{
MT_PLATFORM_STUB();
}
auto CPythonQuest::SetQuestCounterName(DWORD, const char *) -> void
{
MT_PLATFORM_STUB();
}
auto CPythonQuest::SetQuestClockValue(DWORD, int) -> void
{
MT_PLATFORM_STUB();
}
auto CPythonQuest::SetQuestCounterValue(DWORD, int) -> void
{
MT_PLATFORM_STUB();
}
auto CPythonQuest::SetQuestIconFileName(DWORD, const char *) -> void
{
MT_PLATFORM_STUB();
}
+443
View File
@@ -0,0 +1,443 @@
#include "StdAfx.h"
#include "parser.h"
using namespace script;
#define ishan(ch) (((ch) & 0xE0) > 0x90)
#define isnhspace(ch) (!ishan(ch) && isspace(ch))
extern DWORD GetDefaultCodePage();
const char* LocaleString_FindChar(const char* base, int len, char test)
{
if (!base)
return NULL;
DWORD codePage = GetDefaultCodePage();
int pos = 0;
while (pos < len)
{
const char* cur = base + pos;
const char* next = CharNextExA(codePage, cur, 0);
int cur_len = next - cur;
if (cur_len > 1)
{
pos += cur_len;
}
else if (1 == cur_len)
{
if (*cur == test)
return cur;
++pos;
}
else
{
break;
}
}
return NULL;
}
int LocaleString_RightTrim(char* base, int len)
{
DWORD codePage = GetDefaultCodePage();
int pos = len;
while (pos > 0)
{
char* cur = base + pos;
char* prev = CharPrevExA(codePage, base, cur , 0);
int prev_len = cur - prev;
if (prev_len != 1)
break;
if (!isspace((unsigned char) *prev) && *prev != '\n' && *prev != '\r')
break;
*prev = '\0';
pos -= prev_len;
}
if (pos > 0)
return pos;
return 0;
}
void LocaleString_RightTrim(char* base)
{
LocaleString_RightTrim(base, strlen(base));
}
void OLD_rtrim(char* base)
{
if (!base)
return;
DWORD codePage = GetDefaultCodePage();
if (949 == codePage || 936 == codePage)
{
char* end = base + strlen(base) - 1;
while (end != base)
{
if (!isnhspace((unsigned char) *end) && *end != '\n' && *end != '\r' || (end!=base && *((unsigned char*)end-1)>0xa0))
break;
*end = '\0';
end = CharPrevExA(codePage, base, end, 0);
}
}
else
{
char* end = base + strlen(base);
while (end != base)
{
char* prev = CharPrevExA(codePage, base, end, 0);
int prev_len = end - prev;
if (prev_len != 1)
break;
if (!isspace((unsigned char) *prev) && *prev != '\n' && *prev != '\r')
break;
*prev = '\0';
end = prev;
}
}
}
const char* LocaleString_Skip(DWORD codePage, const char* cur)
{
int loopCount = 0;
while (*cur)
{
if (++loopCount > 9216)
{
TraceError("Infinite loop in LocaleString_Skip [%s]", cur);
break;
}
const char* next = CharNextExA(codePage, cur, 0);
int cur_len = next - cur;
if (cur_len > 1)
{
cur = next;
}
else if (1 == cur_len)
{
if (!isspace((unsigned char) *cur) && *cur != '\n' && *cur != '\r')
return cur;
}
else
{
break;
}
}
return cur;
}
bool Group::GetArg(const char *c_arg_base, int arg_len, TArgList & argList)
{
char szName[32 + 1];
char szValue[64 + 1];
int iNameLen = 0;
int iValueLen = 0;
int iCharLen = 0;
int pos = 0;
bool isValue = false;
DWORD codePage = GetDefaultCodePage();
while (pos < arg_len)
{
const char* cur = c_arg_base + pos;
const char* next = CharNextExA(codePage, cur, 0);
iCharLen = next - cur;
if (iCharLen > 1)
{
if (isValue)
{
if (iValueLen >= 64)
{
TraceError("argument value overflow: must be shorter than 64 letters");
return false;
}
memcpy(szValue+iValueLen, cur, iCharLen);
iValueLen += iCharLen;
szValue[iValueLen] = '\0';
}
else
{
if (iNameLen >= 32)
{
TraceError("argument name overflow: must be shorter than 32 letters");
return false;
}
memcpy(szName+iNameLen, cur, iCharLen);
iNameLen += iCharLen;
szName[iNameLen] = '\0';
}
}
else if (iCharLen == 1)
{
const char c = *cur;
if (c == '|')
{
if (iNameLen == 0)
{
TraceError("no argument name");
return false;
}
isValue = false;
iNameLen = LocaleString_RightTrim(szName, iNameLen);
iValueLen = LocaleString_RightTrim(szValue, iValueLen);
argList.push_back(TArg(szName, szValue));
iNameLen = 0;
iValueLen = 0;
}
else if (c == ';')
{
isValue = true;
}
// 값이 아니고, 이름이 시작되지 않았을 경우 빈칸은 건너 뛴다.
else if (!isValue && iNameLen == 0 && isspace((unsigned char) c))
{
}
// 엔터는 건너 뛴다
else if (c == '\r' || c == '\n')
{
}
else
{
if (isValue)
{
if (iValueLen >= 64)
{
TraceError("argument value overflow: must be shorter than 64 letters");
return false;
}
memcpy(szValue+iValueLen, cur, iCharLen);
iValueLen += iCharLen;
szValue[iValueLen] = '\0';
}
else
{
if (iNameLen >= 32)
{
TraceError("argument name overflow: must be shorter than 32 letters");
return false;
}
memcpy(szName+iNameLen, cur, iCharLen);
iNameLen += iCharLen;
szName[iNameLen] = '\0';
}
}
}
else
{
break;
}
pos += iCharLen;
}
if (iNameLen != 0 && iValueLen != 0)
{
iNameLen = LocaleString_RightTrim(szName, iNameLen);
iValueLen = LocaleString_RightTrim(szValue, iValueLen);
argList.push_back(TArg(szName, szValue));
}
return true;
}
bool Group::Create(const std::string & stSource)
{
m_cmdList.clear();
if (stSource.empty())
return false;
const char *str_base = stSource.c_str();
if (!str_base || !*str_base)
{
TraceError("Source file has no content");
return false;
}
int str_len = stSource.length();
int str_pos = 0;
DWORD codePage = GetDefaultCodePage();
char box_data[1024 + 1];
static std::string stLetter;
while (str_pos < str_len)
{
TCmd cmd;
const char* word = str_base + str_pos;
const char* word_next = CharNextExA(codePage, word, 0);
int word_len = word_next - word;
if (word_len > 1)
{
str_pos += word_len;
{
stLetter.assign(word, word_next);
cmd.name.assign("LETTER");
cmd.argList.push_back(TArg("value", stLetter));
m_cmdList.push_back(cmd);
}
}
else if (word_len == 1)
{
const char cur = *word;
if ('[' == cur)
{
++str_pos;
const char* box_begin = str_base + str_pos;
const char* box_end = LocaleString_FindChar(box_begin, str_len - str_pos, ']');
if (!box_end)
{
TraceError(" !! PARSING ERROR - Syntax Error : %s\n", box_begin);
return false;
}
str_pos += box_end - box_begin + 1;
int data_len = 0;
{
const char* data_begin = LocaleString_Skip(codePage, box_begin);
const char* data_end = box_end;
data_len = data_end - data_begin;
if (data_len >= 1024)
{
TraceError(" !! PARSING ERROR - Buffer Overflow : %d, %s\n", data_len, str_base);
return false;
}
memcpy(box_data, data_begin, data_len);
box_data[data_len] = '\0';
data_len = LocaleString_RightTrim(box_data, data_len); // 오른쪽 빈칸 자르기
}
{
const char* space = LocaleString_FindChar(box_data, data_len, ' ');
if (space) // 인자가 있음
{
int name_len = space - box_data;
cmd.name.assign(box_data, name_len);
const char* space_next = CharNextExA(codePage, space, 0);
const char* arg = LocaleString_Skip(codePage, space_next);
int arg_len = data_len - (arg - box_data);
if (!GetArg(arg, arg_len, cmd.argList))
{
TraceError(" !! PARSING ERROR - Unknown Arguments : %d, %s\n", arg_len, arg);
return false;
}
}
else // 인자가 없으므로 모든 스트링이 명령어다.
{
cmd.name.assign(box_data);
cmd.argList.clear();
}
m_cmdList.push_back(cmd);
}
}
else if (cur == '\r' || cur == '\n')
{
++str_pos;
}
else
{
++str_pos;
{
stLetter.assign(1, cur);
cmd.name.assign("LETTER");
cmd.argList.push_back(TArg("value", stLetter));
m_cmdList.push_back(cmd);
}
}
}
else
{
break;
}
}
return true;
}
bool Group::GetCmd(TCmd & cmd)
{
if (m_cmdList.empty())
return false;
cmd = m_cmdList.front();
m_cmdList.pop_front();
return true;
}
bool Group::ReadCmd(TCmd & cmd)
{
if (m_cmdList.empty())
return false;
cmd = m_cmdList.front();
return true;
}
std::string & Group::GetError()
{
return m_stError;
}
void Group::SetError(const char * c_pszError)
{
m_stError.assign(c_pszError);
}
Group::Group()
{
}
Group::~Group()
{
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,315 @@
#include "StdAfx.h"
#include "PythonEventManager.h"
#include "PythonNetworkStream.h"
PyObject * eventRegisterEventSet(PyObject * poSelf, PyObject * poArgs)
{
char * szFileName;
if (!PyTuple_GetString(poArgs, 0, &szFileName))
return Py_BuildException();
int iEventIndex = CPythonEventManager::Instance().RegisterEventSet(szFileName);
return Py_BuildValue("i", iEventIndex);
}
PyObject * eventRegisterEventSetFromString(PyObject * poSelf, PyObject * poArgs)
{
char * szEventString;
if (!PyTuple_GetString(poArgs, 0, &szEventString))
return Py_BuildException();
int iEventIndex = CPythonEventManager::Instance().RegisterEventSetFromString(szEventString);
return Py_BuildValue("i", iEventIndex);
}
PyObject * eventClearEventSet(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
CPythonEventManager::Instance().ClearEventSeti(iIndex);
return Py_BuildNone();
}
PyObject * eventSetRestrictedCount(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
int iCount;
if (!PyTuple_GetInteger(poArgs, 1, &iCount))
return Py_BuildException();
CPythonEventManager::Instance().SetRestrictedCount(iIndex, iCount);
return Py_BuildNone();
}
PyObject * eventGetEventSetLocalYPosition(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
return Py_BuildValue("i", CPythonEventManager::Instance().GetEventSetLocalYPosition(iIndex));
}
PyObject * eventAddEventSetLocalYPosition(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
int iPos;
if (!PyTuple_GetInteger(poArgs, 1, &iPos))
return Py_BuildException();
CPythonEventManager::Instance().AddEventSetLocalYPosition(iIndex, iPos);
return Py_BuildNone();
}
PyObject * eventInsertText(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
char * szText;
if (!PyTuple_GetString(poArgs, 1, &szText))
return Py_BuildException();
CPythonEventManager::Instance().InsertText(iIndex, szText);
return Py_BuildNone();
}
PyObject * eventInsertTextInline(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
char * szText;
if (!PyTuple_GetString(poArgs, 1, &szText))
return Py_BuildException();
int iXIndex;
if (!PyTuple_GetInteger(poArgs, 2, &iXIndex))
return Py_BuildException();
CPythonEventManager::Instance().InsertText(iIndex, szText,iXIndex);
return Py_BuildNone();
}
PyObject * eventUpdateEventSet(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
int ix;
if (!PyTuple_GetInteger(poArgs, 1, &ix))
return Py_BuildException();
int iy;
if (!PyTuple_GetInteger(poArgs, 2, &iy))
return Py_BuildException();
CPythonEventManager::Instance().UpdateEventSet(iIndex, ix, -iy);
return Py_BuildNone();
}
PyObject * eventRenderEventSet(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
CPythonEventManager::Instance().RenderEventSet(iIndex);
return Py_BuildNone();
}
PyObject * eventSetEventSetWidth(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
int iWidth;
if (!PyTuple_GetInteger(poArgs, 1, &iWidth))
return Py_BuildException();
CPythonEventManager::Instance().SetEventSetWidth(iIndex, iWidth);
return Py_BuildNone();
}
PyObject * eventSkip(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
CPythonEventManager::Instance().Skip(iIndex);
return Py_BuildNone();
}
PyObject * eventIsWait(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
return Py_BuildValue("i", CPythonEventManager::Instance().IsWait(iIndex) == true ? 1 : 0);
}
PyObject * eventEndEventProcess(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
CPythonEventManager::Instance().EndEventProcess(iIndex);
return Py_BuildNone();
}
PyObject * eventSetEventHandler(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
PyObject * poEventHandler;
if (!PyTuple_GetObject(poArgs, 1, &poEventHandler))
return Py_BuildException();
CPythonEventManager::Instance().SetEventHandler(iIndex, poEventHandler);
return Py_BuildNone();
}
PyObject * eventSelectAnswer(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
int iAnswer;
if (!PyTuple_GetInteger(poArgs, 1, &iAnswer))
return Py_BuildException();
CPythonEventManager::Instance().SelectAnswer(iIndex, iAnswer);
return Py_BuildNone();
}
PyObject * eventGetLineCount(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
int iLineCount = CPythonEventManager::Instance().GetLineCount(iIndex);
return Py_BuildValue("i", iLineCount);
}
PyObject * eventSetVisibleStartLine(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
int iStartLine;
if (!PyTuple_GetInteger(poArgs, 1, &iStartLine))
return Py_BuildException();
CPythonEventManager::Instance().SetVisibleStartLine(iIndex, iStartLine);
return Py_BuildNone();
}
PyObject * eventGetVisibleStartLine(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
return Py_BuildValue("i", CPythonEventManager::Instance().GetVisibleStartLine(iIndex));
}
PyObject * eventQuestButtonClick(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BuildException();
CPythonNetworkStream::Instance().SendScriptButtonPacket(iIndex);
return Py_BuildNone();
}
PyObject * eventSetInterfaceWindow(PyObject* poSelf, PyObject* poArgs)
{
PyObject * pyHandle;
if (!PyTuple_GetObject(poArgs, 0, &pyHandle))
return Py_BadArgument();
CPythonEventManager & rpem = CPythonEventManager::Instance();
rpem.SetInterfaceWindow(pyHandle);
return Py_BuildNone();
}
PyObject * eventSetLeftTimeString(PyObject* poSelf, PyObject* poArgs)
{
char * szText;
if (!PyTuple_GetString(poArgs, 0, &szText))
return Py_BuildException();
CPythonEventManager & rpem = CPythonEventManager::Instance();
rpem.SetLeftTimeString(szText);
return Py_BuildNone();
}
PyObject * eventDestroy(PyObject* poSelf, PyObject* poArgs)
{
CPythonEventManager & rpem = CPythonEventManager::Instance();
rpem.Destroy();
return Py_BuildNone();
}
void initEvent()
{
static PyMethodDef s_methods[] =
{
{ "RegisterEventSet", eventRegisterEventSet, METH_VARARGS },
{ "RegisterEventSetFromString", eventRegisterEventSetFromString, METH_VARARGS },
{ "ClearEventSet", eventClearEventSet, METH_VARARGS },
{ "SetRestrictedCount", eventSetRestrictedCount, METH_VARARGS },
{ "GetEventSetLocalYPosition", eventGetEventSetLocalYPosition, METH_VARARGS },
{ "AddEventSetLocalYPosition", eventAddEventSetLocalYPosition, METH_VARARGS },
{ "InsertText", eventInsertText, METH_VARARGS },
{ "InsertTextInline", eventInsertTextInline, METH_VARARGS },
{ "UpdateEventSet", eventUpdateEventSet, METH_VARARGS },
{ "RenderEventSet", eventRenderEventSet, METH_VARARGS },
{ "SetEventSetWidth", eventSetEventSetWidth, METH_VARARGS },
{ "Skip", eventSkip, METH_VARARGS },
{ "IsWait", eventIsWait, METH_VARARGS },
{ "EndEventProcess", eventEndEventProcess, METH_VARARGS },
{ "SelectAnswer", eventSelectAnswer, METH_VARARGS },
{ "GetLineCount", eventGetLineCount, METH_VARARGS },
{ "SetVisibleStartLine", eventSetVisibleStartLine, METH_VARARGS },
{ "GetVisibleStartLine", eventGetVisibleStartLine, METH_VARARGS },
{ "SetEventHandler", eventSetEventHandler, METH_VARARGS },
{ "SetInterfaceWindow", eventSetInterfaceWindow, METH_VARARGS },
{ "SetLeftTimeString", eventSetLeftTimeString, METH_VARARGS },
{ "QuestButtonClick", eventQuestButtonClick, METH_VARARGS },
{ "Destroy", eventDestroy, METH_VARARGS },
{ NULL, NULL, NULL },
};
PyObject * poModule = Py_InitModule("event", s_methods);
PyModule_AddIntConstant(poModule, "BOX_VISIBLE_LINE_COUNT", CPythonEventManager::BOX_VISIBLE_LINE_COUNT);
PyModule_AddIntConstant(poModule, "BUTTON_TYPE_NEXT", CPythonEventManager::BUTTON_TYPE_NEXT);
PyModule_AddIntConstant(poModule, "BUTTON_TYPE_DONE", CPythonEventManager::BUTTON_TYPE_DONE);
PyModule_AddIntConstant(poModule, "BUTTON_TYPE_CANCEL", CPythonEventManager::BUTTON_TYPE_CANCEL);
}
@@ -0,0 +1,30 @@
#include "StdAfx.h"
#include "../gamelib/GameEventManager.h"
PyObject * eventMgrUpdate(PyObject * poSelf, PyObject * poArgs)
{
float fx;
if (!PyTuple_GetFloat(poArgs, 0, &fx))
return Py_BuildException();
float fy;
if (!PyTuple_GetFloat(poArgs, 1, &fy))
return Py_BuildException();
float fz;
if (!PyTuple_GetFloat(poArgs, 2, &fz))
return Py_BuildException();
CGameEventManager::Instance().SetCenterPosition(fx, fy, fz);
CGameEventManager::Instance().Update();
return Py_BuildNone();
}
void initeventmgr()
{
static PyMethodDef s_methods[] =
{
{ "Update", eventMgrUpdate, METH_VARARGS },
{ NULL, NULL, NULL },
};
Py_InitModule("eventMgr", s_methods);
}
@@ -0,0 +1,270 @@
#include "StdAfx.h"
#include "PythonQuest.h"
void CPythonQuest::RegisterQuestInstance(const SQuestInstance & c_rQuestInstance)
{
DeleteQuestInstance(c_rQuestInstance.dwIndex);
m_QuestInstanceContainer.push_back(c_rQuestInstance);
/////
SQuestInstance & rQuestInstance = *m_QuestInstanceContainer.rbegin();
rQuestInstance.iStartTime = int(CTimer::Instance().GetCurrentSecond());
}
struct FQuestInstanceCompare
{
DWORD dwSearchIndex;
FQuestInstanceCompare(DWORD dwIndex) : dwSearchIndex(dwIndex) {}
bool operator () (const CPythonQuest::SQuestInstance & rQuestInstance)
{
return dwSearchIndex == rQuestInstance.dwIndex;
}
};
void CPythonQuest::DeleteQuestInstance(DWORD dwIndex)
{
TQuestInstanceContainer::iterator itor = std::find_if(m_QuestInstanceContainer.begin(), m_QuestInstanceContainer.end(), FQuestInstanceCompare(dwIndex));
if (itor == m_QuestInstanceContainer.end())
return;
m_QuestInstanceContainer.erase(itor);
}
bool CPythonQuest::IsQuest(DWORD dwIndex)
{
TQuestInstanceContainer::iterator itor = std::find_if(m_QuestInstanceContainer.begin(), m_QuestInstanceContainer.end(), FQuestInstanceCompare(dwIndex));
return itor != m_QuestInstanceContainer.end();
}
void CPythonQuest::MakeQuest(DWORD dwIndex)
{
DeleteQuestInstance(dwIndex);
m_QuestInstanceContainer.push_back(SQuestInstance());
/////
SQuestInstance & rQuestInstance = *m_QuestInstanceContainer.rbegin();
rQuestInstance.dwIndex = dwIndex;
rQuestInstance.iStartTime = int(CTimer::Instance().GetCurrentSecond());
}
void CPythonQuest::SetQuestTitle(DWORD dwIndex, const char * c_szTitle)
{
SQuestInstance * pQuestInstance;
if (!__GetQuestInstancePtr(dwIndex, &pQuestInstance))
return;
pQuestInstance->strTitle = c_szTitle;
}
void CPythonQuest::SetQuestClockName(DWORD dwIndex, const char * c_szClockName)
{
SQuestInstance * pQuestInstance;
if (!__GetQuestInstancePtr(dwIndex, &pQuestInstance))
return;
pQuestInstance->strClockName = c_szClockName;
}
void CPythonQuest::SetQuestCounterName(DWORD dwIndex, const char * c_szCounterName)
{
SQuestInstance * pQuestInstance;
if (!__GetQuestInstancePtr(dwIndex, &pQuestInstance))
return;
pQuestInstance->strCounterName = c_szCounterName;
}
void CPythonQuest::SetQuestClockValue(DWORD dwIndex, int iClockValue)
{
SQuestInstance * pQuestInstance;
if (!__GetQuestInstancePtr(dwIndex, &pQuestInstance))
return;
pQuestInstance->iClockValue = iClockValue;
pQuestInstance->iStartTime = int(CTimer::Instance().GetCurrentSecond());
}
void CPythonQuest::SetQuestCounterValue(DWORD dwIndex, int iCounterValue)
{
SQuestInstance * pQuestInstance;
if (!__GetQuestInstancePtr(dwIndex, &pQuestInstance))
return;
pQuestInstance->iCounterValue = iCounterValue;
}
void CPythonQuest::SetQuestIconFileName(DWORD dwIndex, const char * c_szIconFileName)
{
SQuestInstance * pQuestInstance;
if (!__GetQuestInstancePtr(dwIndex, &pQuestInstance))
return;
pQuestInstance->strIconFileName = c_szIconFileName;
}
int CPythonQuest::GetQuestCount()
{
return m_QuestInstanceContainer.size();
}
bool CPythonQuest::GetQuestInstancePtr(DWORD dwArrayIndex, SQuestInstance ** ppQuestInstance)
{
if (dwArrayIndex >= m_QuestInstanceContainer.size())
return false;
*ppQuestInstance = &m_QuestInstanceContainer[dwArrayIndex];
return true;
}
bool CPythonQuest::__GetQuestInstancePtr(DWORD dwQuestIndex, SQuestInstance ** ppQuestInstance)
{
TQuestInstanceContainer::iterator itor = std::find_if(m_QuestInstanceContainer.begin(), m_QuestInstanceContainer.end(), FQuestInstanceCompare(dwQuestIndex));
if (itor == m_QuestInstanceContainer.end())
return false;
*ppQuestInstance = &(*itor);
return true;
}
void CPythonQuest::__Initialize()
{
/*
#ifdef _DEBUG
for (int i = 0; i < 7; ++i)
{
SQuestInstance test;
test.dwIndex = i;
test.strIconFileName = "";
test.strTitle = _getf("test%d", i);
test.strClockName = "남은 시간";
test.strCounterName = "남은 마리수";
test.iClockValue = 1000;
test.iCounterValue = 1000;
test.iStartTime = 0;
RegisterQuestInstance(test);
}
#endif
*/
}
void CPythonQuest::Clear()
{
m_QuestInstanceContainer.clear();
}
CPythonQuest::CPythonQuest()
{
__Initialize();
}
CPythonQuest::~CPythonQuest()
{
Clear();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
PyObject * questGetQuestCount(PyObject * poSelf, PyObject * poArgs)
{
return Py_BuildValue("i", CPythonQuest::Instance().GetQuestCount());
}
PyObject * questGetQuestData(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BadArgument();
CPythonQuest::SQuestInstance * pQuestInstance;
if (!CPythonQuest::Instance().GetQuestInstancePtr(iIndex, &pQuestInstance))
return Py_BuildException("Failed to find quest by index %d", iIndex);
CGraphicImage * pImage = NULL;
if (!pQuestInstance->strIconFileName.empty())
{
std::string strIconFileName;
strIconFileName = "d:/ymir work/ui/game/quest/questicon/";
strIconFileName += pQuestInstance->strIconFileName.c_str();
pImage = (CGraphicImage *)CResourceManager::Instance().GetResourcePointer(strIconFileName.c_str());
}
else
{
{
// 비어있을 경우 디폴트 이미지를 넣는다.
std::string strIconFileName = "season1/icon/scroll_open.tga";
pImage = (CGraphicImage *)CResourceManager::Instance().GetResourcePointer(strIconFileName.c_str());
}
}
return Py_BuildValue("sisi", pQuestInstance->strTitle.c_str(),
pImage,
pQuestInstance->strCounterName.c_str(),
pQuestInstance->iCounterValue);
}
PyObject * questGetQuestIndex(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BadArgument();
CPythonQuest::SQuestInstance * pQuestInstance;
if (!CPythonQuest::Instance().GetQuestInstancePtr(iIndex, &pQuestInstance))
return Py_BuildException("Failed to find quest by index %d", iIndex);
return Py_BuildValue("i", pQuestInstance->dwIndex);
}
PyObject * questGetQuestLastTime(PyObject * poSelf, PyObject * poArgs)
{
int iIndex;
if (!PyTuple_GetInteger(poArgs, 0, &iIndex))
return Py_BadArgument();
CPythonQuest::SQuestInstance * pQuestInstance;
if (!CPythonQuest::Instance().GetQuestInstancePtr(iIndex, &pQuestInstance))
return Py_BuildException("Failed to find quest by index %d", iIndex);
int iLastTime = 0;
if (pQuestInstance->iClockValue >= 0)
{
iLastTime = (pQuestInstance->iStartTime + pQuestInstance->iClockValue) - int(CTimer::Instance().GetCurrentSecond());
}
// 시간 증가 처리 코드
// else
// {
// iLastTime = int(CTimer::Instance().GetCurrentSecond()) - pQuestInstance->iStartTime;
// }
return Py_BuildValue("si", pQuestInstance->strClockName.c_str(), iLastTime);
}
PyObject * questClear(PyObject * poSelf, PyObject * poArgs)
{
CPythonQuest::Instance().Clear();
return Py_BuildNone();
}
void initquest()
{
static PyMethodDef s_methods[] =
{
{ "GetQuestCount", questGetQuestCount, METH_VARARGS },
{ "GetQuestData", questGetQuestData, METH_VARARGS },
{ "GetQuestIndex", questGetQuestIndex, METH_VARARGS },
{ "GetQuestLastTime", questGetQuestLastTime, METH_VARARGS },
{ "Clear", questClear, METH_VARARGS },
{ NULL, NULL, NULL },
};
PyObject * poModule = Py_InitModule("quest", s_methods);
PyModule_AddIntConstant(poModule, "QUEST_MAX_NUM", 5);
}
@@ -46,6 +46,9 @@ enum
void initpack(); void initpack();
void initime(); void initime();
void initshop(); void initshop();
void initquest();
void initEvent();
void initeventmgr();
void initapp(); void initapp();
void initchr(); void initchr();
void initchrmgr(); void initchrmgr();
+31
View File
@@ -391,4 +391,35 @@ int WideCharToMultiByte(UINT CodePage, DWORD, LPCWSTR lpWideCharStr, int cchWide
return int(bytes.size()); return int(bytes.size());
} }
BOOL IsDBCSLeadByteEx(UINT CodePage, BYTE TestChar)
{
switch (CodePage) {
case 932: return (TestChar >= 0x81 && TestChar <= 0x9F) || (TestChar >= 0xE0 && TestChar <= 0xFC);
case 936:
case 949:
case 950: return TestChar >= 0x81 && TestChar <= 0xFE;
default: return FALSE;
}
}
LPSTR CharNextExA(WORD CodePage, LPCSTR lpCurrentChar, DWORD)
{
if (!*lpCurrentChar) return const_cast<LPSTR>(lpCurrentChar);
if (IsDBCSLeadByteEx(CodePage, BYTE(*lpCurrentChar)) && lpCurrentChar[1]) return const_cast<LPSTR>(lpCurrentChar + 2);
return const_cast<LPSTR>(lpCurrentChar + 1);
}
LPSTR CharPrevExA(WORD CodePage, LPCSTR lpStart, LPCSTR lpCurrentChar, DWORD dwFlags)
{
// Walk forward from lpStart so a trail byte is never taken for a lead byte.
LPCSTR prev = lpStart;
for (LPCSTR p = lpStart; p < lpCurrentChar;) {
prev = p;
LPCSTR next = CharNextExA(CodePage, p, dwFlags);
if (next == p) break;
p = next;
}
return const_cast<LPSTR>(prev);
}
#endif // !_WIN32 #endif // !_WIN32
+6
View File
@@ -101,4 +101,10 @@ int MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int
int WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar, int WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar,
LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar); LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar);
// winnls.h / winuser.h DBCS stepping. Lead bytes follow the double-byte code pages 932, 936, 949 and 950;
// every other code page (1252, UTF-8, ...) steps one byte at a time, as Windows does for single-byte pages.
BOOL IsDBCSLeadByteEx(UINT CodePage, BYTE TestChar);
LPSTR CharNextExA(WORD CodePage, LPCSTR lpCurrentChar, DWORD dwFlags);
LPSTR CharPrevExA(WORD CodePage, LPCSTR lpStart, LPCSTR lpCurrentChar, DWORD dwFlags);
#endif // !_WIN32 #endif // !_WIN32
@@ -603,6 +603,19 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
keeper_update.attack_speed = 100; keeper_update.attack_speed = 100;
if (!c.Send(keeper) || !c.Send(keeper_info) || !c.Send(keeper_update)) if (!c.Send(keeper) || !c.Send(keeper_info) || !c.Send(keeper_update))
return Fail(name + ": send shopkeeper"); return Fail(name + ": send shopkeeper");
GCCharacterAdd elder = keeper;
elder.vid = kQuestVID;
elder.x = kQuestX;
elder.y = kQuestY;
elder.race = kQuestRace;
GCCharAddInfo elder_info = zeroed<GCCharAddInfo>();
elder_info.header = HDR_GC_CHAR_ADDITIONAL_INFO;
elder_info.vid = kQuestVID;
std::strncpy(elder_info.name, "Oldster", sizeof(elder_info.name) - 1);
GCCharacterUpdate elder_update = keeper_update;
elder_update.vid = kQuestVID;
if (!c.Send(elder) || !c.Send(elder_info) || !c.Send(elder_update))
return Fail(name + ": send quest NPC");
Event(name + ":burst_sent"); Event(name + ":burst_sent");
// A ping after the burst proves that GamePhase consumed every packet above and can reply. // A ping after the burst proves that GamePhase consumed every packet above and can reply.
@@ -619,6 +632,20 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
bool shopping = false; bool shopping = false;
std::int32_t gold = kStartGold; std::int32_t gold = kStartGold;
std::uint32_t bag0 = 0; // vnum in inventory cell 0 (the fake character starts with an empty bag) std::uint32_t bag0 = 0; // vnum in inventory cell 0 (the fake character starts with an empty bag)
int quest_page = 0; // the quest dialogue's last page sent (1 question, 2 next, 3 done)
// CQuestManager::SendScript: packet_script {header, size, skin, src_size} and the script text.
auto send_script = [&](const std::string& script) {
struct
{
DynHead head;
std::uint8_t skin;
std::uint16_t src_size;
} __attribute__((packed)) head = {{HDR_GC_SCRIPT, static_cast<std::uint16_t>(sizeof(head) + script.size())}, 1 /* QUEST_SKIN_NORMAL */,
static_cast<std::uint16_t>(script.size())};
std::string bytes(reinterpret_cast<const char*>(&head), sizeof(head));
bytes += script;
return c.Send(bytes.data(), bytes.size());
};
for (;;) for (;;)
{ {
std::uint8_t packet[1024] = {}; std::uint8_t packet[1024] = {};
@@ -759,6 +786,11 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
CGOnClick click; CGOnClick click;
std::memcpy(&click, packet, sizeof(click)); std::memcpy(&click, packet, sizeof(click));
Event(name + ":click " + std::to_string(click.vid)); Event(name + ":click " + std::to_string(click.vid));
// CHARACTER::OnClick → CQuestManager::Click: say(...) then select("Yes", "No") → GotoSelectState.
if (click.vid == kQuestVID && !send_script("Greetings, young warrior.[ENTER]Shall I tell you a story?[ENTER][QUESTION 1;Yes|2;No]"))
return Fail(name + ": send GC_SCRIPT question");
if (click.vid == kQuestVID)
quest_page = 1;
if (click.vid == kShopVID && !shopping) if (click.vid == kShopVID && !shopping)
{ {
shopping = true; shopping = true;
@@ -780,6 +812,25 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
return Fail(name + ": send GC_SHOP START"); return Fail(name + ": send GC_SHOP START");
} }
} }
// CInputMain::ScriptAnswer: an answer above 250 is the [NEXT] button (CQuestManager::Resume), the rest pick
// a [QUESTION] choice (CQuestManager::Select, 0-based).
if (header == HDR_CG_SCRIPT_ANSWER)
{
const std::uint8_t answer = packet[1];
Event(name + ":answer " + std::to_string(answer));
if (quest_page == 1 && answer == 0)
{
quest_page = 2;
if (!send_script("Long ago a dragon slept under this village.[ENTER][NEXT]"))
return Fail(name + ": send GC_SCRIPT next");
}
else if (quest_page == 2 && answer > 250)
{
quest_page = 3;
if (!send_script("It sleeps there still.[ENTER][DONE]"))
return Fail(name + ": send GC_SCRIPT done");
}
}
// CInputMain::Shop: END → CShop::RemoveGuest (GC_SHOP END); BUY → CShop::Buy (PointChange(POINT_GOLD), then // CInputMain::Shop: END → CShop::RemoveGuest (GC_SHOP END); BUY → CShop::Buy (PointChange(POINT_GOLD), then
// CItem::AddToCharacter's GC_ITEM_SET into the first empty cell); SELL2 → CShopManager::Sell (the cell empties // CItem::AddToCharacter's GC_ITEM_SET into the first empty cell); SELL2 → CShopManager::Sell (the cell empties
// with the 42-byte header-20 packet, then PointChange(POINT_GOLD)). // with the 42-byte header-20 packet, then PointChange(POINT_GOLD)).
+4
View File
@@ -37,6 +37,10 @@ public:
static constexpr std::int32_t kShopX = kMobX + 400, kShopY = kMobY; static constexpr std::int32_t kShopX = kMobX + 400, kShopY = kMobY;
static constexpr std::uint32_t kShopItemVnum = 27001, kShopItemPrice = 50; static constexpr std::uint32_t kShopItemVnum = 27001, kShopItemPrice = 50;
static constexpr std::int32_t kStartGold = 10000, kShopSellPrice = 9; static constexpr std::int32_t kStartGold = 10000, kShopSellPrice = 9;
// An old man (npclist 20009) 3 m east of the store whose click runs a quest dialogue the way CQuestManager
// sends it: say + select ([QUESTION]), then a [NEXT] page, then a [DONE] page.
static constexpr std::uint32_t kQuestVID = 0x00045678, kQuestRace = 20009;
static constexpr std::int32_t kQuestX = kShopX + 300, kQuestY = kShopY;
~FakeLoginServer(); ~FakeLoginServer();