← 回到遊戲

天堂 3.80C 銀騎士村 × 點裝備(離線 HTML 1:1 復刻)

打開 index.html 就能玩(file:// 也可以,不需要網路、不需要伺服器)。法師 費倫 站在她在資料庫裡的真實位置 (map 4,33080, 33390,銀騎士村的 GM 商人區「古代XX商人^潘朵拉」),背包是她在 DB 裡的真實背包(72 件)。

地點說明:派工單寫「說話之島村莊」,但 DB 中法師 費倫 的實際位置、以及有 GM NPC(古代XX商人^潘朵拉)的村莊是 銀騎士村(map 4),本作以實際位置為準。

遊戲畫面

驗收對照

驗收項目 狀態 證據
1. 打開 index.html:原素材村莊地圖+NPC,法師可走動 完成 client s32/Tile.pak 地圖、spawnlist_npc 的 96 隻 NPC、list.spr 動畫;docs/remake_village.png
2. 背包有武器/防具/各種強化卷軸,點裝結果與伺服器一致,README 附公式與檔案行號 完成 下方第 1 節;node tests/enchant.test.js(15 種情境蒙地卡羅+訊息逐字比對)
3. 整包 zip 離線可跑 完成 python tools/package.py 產生的 zip 解壓後,用 Chromium 離線模式從 file:// 跑完整流程(tests/shot.js)
4. 實機對照截圖+比較說明 部分完成 比較說明見 docs/COMPARISON.md;實機截圖因 UAC/PIN 卡住,只放了 client 原始素材對照,另附 tools/capture_game.ps1 補拍

1. 點裝備公式(全部取自伺服器原始碼,未自行編造)

伺服器:E:\天堂工具\workspace\L1J-TW_3.80c(下稱 SRC = server/src/main/java/l1j/server)。 設定值:config/rates.properties 第 30–31 行 EnchantChanceWeapon = 30、EnchantChanceArmor = 30(第 35 行 AttrEnchantChance = 10)。 讀取處:SRC/Config.java 第 521–523 行。

1.1 亂數 SRC/server/utils/Random.java 第 17–34 行

伺服器不是用 java.util.Random,而是一張 32768 格的表:每格 = (Math.random()×5 次相加) % 1.0, nextInt(n) = (int)(table[idx++] * n) 循環讀取。本作 web/js/core.js 的 L1Random 照做。

1.2 卷軸分派 SRC/server/clientpackets/C_ItemUSe.java 第 301–318 行

卷軸 item_id 進入
40087 一般、140087 受祝福、240087 受咀咒、40077、40130、140130、40660(試煉)、40128(幻象) Enchant.scrollOfEnchantWeapon
40074 一般、140074 受祝福、240074 受咀咒、40078、40129、140129、40127(幻象) Enchant.scrollOfEnchantArmor
49148 飾品強化卷軸(第 2743 行) scrollOfEnchantAccessory
41429–41432 屬性強化卷軸 scrollOfEnchantWeaponAttr

1.3 對武器施法 SRC/server/model/item/action/Enchant.java 第 34–110 行

  1. 目標不是武器、安定值 < 0、封印(bless ≥ 128)→ 訊息 79「沒有任何事情發生。」,卷軸不消耗(39–43)。
  2. 象牙塔武器(7, 35, 48, 73, 105, 120, 147, 156, 174, 175, 224)→ 79(44–49);試煉之劍、幻象武器只吃對應卷軸(50–73)。
  3. 受咀咒卷軸(77–84):消耗 1 張;強化值 < −6 → 蒸發;否則 −1(一定成功)。
  4. 強化值 < 安定值(85–87):必定成功,升級量 = RandomELevel。
  5. 其他(88–109):rnd = Random.nextInt(100) + 1(1–100)
  6. 強化值 ≥ 9:chance = (100 + 3 × EnchantChanceWeapon) / 6(整數除法)= 31
  7. 否則:chance = (100 + 3 × EnchantChanceWeapon) / 3 = 63
  8. rnd < chance → 成功;強化值 ≥ 9 && rnd < chance × 2 → 訊息 160「…但是沒有任何事情發生。」(卷軸消耗、裝備不變);否則蒸發。

1.4 對盔甲施法 Enchant.java 第 113–183 行

同上,但機率(158–181): - tmp = (安定值 == 0) ? 強化值 + 2 : 強化值(骨、黑色米索莉等安定值 0 的防具補正) - 強化值 ≥ 9:chance = (100 + tmp × EnchantChanceArmor) / (tmp × 2);否則 chance = (100 + tmp × EnchantChanceArmor) / tmp - 成功/無事(≥ 9 且 rnd < chance×2,訊息 160 用「銀色的」)/蒸發 判定同武器。 - 注意:第 116 行一開頭就把目標轉型成 L1Armor。所以把盔甲卷軸點在武器或其他非防具上,會丟出 ClassCastException, 而道具封包的工作執行緒只記 log(ClientThread.java 第 339–340 行),畫面上沒有任何訊息,卷軸也不會消耗。 武器卷軸點在防具上則走正常檢查,回訊息 79。本作兩種情況都照做(右側面板會說明)。

1.5 升級量 RandomELevel 第 493–515 行

只有 受祝福 卷軸(140074、140087、140129、140130)會 +2/+3: - 強化值 ≤ 2:j = nextInt(100)+1;j < 32 → +1、33 ≤ j ≤ 76 → +2、77 ≤ j ≤ 100 → +3、j == 32 沒被任何分支接住 → +1 → 實際 +1 32%、+2 44%、+3 24% - 強化值 3–5:j < 50 → +2(49%),否則 +1(51%) - 其他情況:+1 - 祝福卷軸失敗一樣蒸發(沒有「失敗只退 1」的規則——舊工具這點是錯的)。

1.6 成功/失敗訊息 第 414–490 行(文字來自 client Text.pak 的 string-c.tbl、desc-c.tbl)

情況 伺服器送出 client 顯示
成功 +1 S_ServerMessage(161, 名稱, "$245", "$247") +7 武士刀 一瞬間發出 藍色的 光芒。
成功 +2/+3 161, 名稱, "$245", "$248" +2 武士刀 持續發出 藍色的 光芒。
防具成功 同上,$252 … 銀色的 光芒。
受咀咒 −1 161, 名稱, "$246", "$247" … 一瞬間發出 黑色的 光芒。
≥ +9 無事 160, 名稱, "$245"/"$252", "$248" +9 武士刀 持續發出 產生激烈的 藍色的 光芒,但是沒有任何事情發生。
蒸發 164, 名稱, "$245"/"$252"(負值時 $246) +8 武士刀 強烈的發出藍色的光芒就消失了。
不能點 79 沒有任何事情發生。

名稱是 施法前 的 getLogName()(L1ItemInstance.java 第 595–689 行:+N/負值、屬性前綴 $6115…、未鑑定時用未鑑定名且不顯示 +N、數量 (N)); 道具欄的 getViewName() 另外加上 ($9)「(揮舞)」/($117)「(使用中)」(第 546–581 行)。

1.7 本伺服器設定下的機率表(EnchantChance* = 30)

強化值 武器 安定 6(例:武士刀) 武器 安定 0(例:巴風特魔杖) 防具 安定 4(例:巫妖斗篷) 防具 安定 0(例:混沌頭盔)
+0 ~ +3 100%(安定值內) 62% 成 / 38% 蒸發 100% +0 79/21、+1 62/38、+2 54/46、+3 49/51
+4 100% 62 / 38 54% 成 / 46% 蒸發 45 / 55
+5 100% 62 / 38 49 / 51 43 / 57
+6 62% 成 / 38% 蒸發 62 / 38 45 / 55 41 / 59
+7 62 / 38 62 / 38 43 / 57 40 / 60
+8 62 / 38 62 / 38 41 / 59 39 / 61
+9、+10 30% 成 / 31% 無事 / 39% 蒸發 同左 19 / 20 / 61 18 / 19 / 63
+11、+12 30 / 31 / 39 同左 18 / 19 / 63 17 / 18 / 65

(武器的成功率在 +6 ~ +8 是固定的 62%——這是這版 L1J-TW 的公式,不隨強化值遞減。)

驗證:node tests/enchant.test.js 對 13 種武器/防具情境各跑 40,000 次(另外還有飾品、屬性卷軸、ClassCastException 情境),實測頻率與上表的封閉解一致,訊息文字逐字比對 string-c.tbl。

1.8 其他照原始碼實作的細節


2. 素材與資料來源

內容 來源 產生工具
地圖(地板、地表、建物、樹、陰影) client map/4/8003~8006 × 8008~800b .s32 + Tile.pak tools/map_build.py
通行 伺服器 maps/4.txt(TextMapReader,L1V1Map.isPassable 第 205–278 行照搬) 同上 / web/js/engine.js
角色與 NPC 動畫、影子 client Sprite*.pak 的 .spr + Text.pak/list.spr 動作表 tools/spr.py、tools/export_sprites.py
道具圖示 client Sprite*.pak 的 {invgfx}.tbt 同上
UI(HUD、視窗、按鈕)、音效 client Sprite*.pak 的 .img、Sound/*.wav、Text.pak/equipsound.def tools/export_ui.py
道具、商店、NPC、背包 MySQL l1jtw(weapon / armor / etcitem / shop / spawnlist_npc / npc / npcaction / character_items) tools/data_build.py
訊息、道具名稱、NPC 對話 client Text.pak:string-c.tbl、desc-c.tbl、*-c.html(L1 加密,已解) tools/l1pak.py

重建:

python tools/map_build.py      # 地圖 → web/assets/map, web/data/map.js
python tools/export_sprites.py # 精靈與圖示 → web/assets/sprites, web/assets/icons, web/data/sprites.js
python tools/export_ui.py      # UI 點陣圖與音效 → web/assets/ui, web/assets/sound, web/data/ui.js
python tools/data_build.py     # 資料 → web/data/game.js(需要本機 DB)
node tests/enchant.test.js     # 機率驗證
python tools/package.py        # 打包 dist/lineage-enchant-380c.zip

3. 已知差異

見 docs/COMPARISON.md(有截圖;實機截圖因 UAC/PIN 無法自動取得,附 tools/capture_game.ps1)。

線上版:https://www.toolhubmaster.com/tools/8fd3ef07-4810-431f-ac84-8732dceb00ee/index.html


與實機的比較(哪裡已經 1:1、哪裡還差)

實機截圖狀態:BLOCKED。 這台電腦上的 3.80C client 在工作開始時並沒有在執行(只有 Java 伺服器在跑)。 Login.exe/Lin.bin 都宣告 requireAdministrator,一啟動就會跳 UAC,要輸入 PIN,無法自動完成。 改用 __COMPAT_LAYER=RunAsInvoker 可以不經 UAC 開出登入器(已驗證),但當時桌面停在 PIN/安全桌面畫面, 螢幕擷取和 PrintWindow 都只拿到黑畫面。 因此本頁的「原版」一欄是 client 原始檔直接解出的素材(同一份資料,不是實機畫面)。 要補實機截圖,執行 powershell -ExecutionPolicy Bypass -File tools\capture_game.ps1 -Launch(不跳 UAC)登入後, 再用 -Shot village、-Shot inventory、-Shot pandora、-Shot shop、-Shot enchant 存到 docs/ref/。

畫面

復刻(640×480,本作) 說明
村莊 銀騎士村 GM 商人區,法師 費倫 站在 DB 位置 (33080, 33390)。地圖是 client map/4 的 s32+Tile.pak,NPC 位置/朝向/外觀來自 spawnlist_npc,動畫來自 list.spr。
對話 點 古代卷軸商人:顯示 client 的 pandora-c.html,按鈕是 TW 版 #312c 買/#314c 賣,視窗底圖 562.img。
商店 商店底圖 549.img;品項與價格是 DB shop(npc 81028);點一列跳出數量框 895.img。
角色 角色/裝備視窗 567c.img(女性剪影),能力值來自 DB characters,身上裝備顯示在剪影旁的格子。
點裝 雙擊卷軸再點道具:+1、+2(持續發出)、咀咒 −1(黑色的)、+9 以上無事發生、防具(銀色的)、蒸發、象牙塔(79),文字逐字來自 string-c.tbl。
遮擋 走到屋後會被屋頂擋住(把角色後方的物件 tile 重畫在角色上)。

原始素材對照:UI 點陣圖、角色/NPC 動畫、整張地圖渲染。

已經 1:1(有原始碼或原始檔佐證)

項目 依據
點裝備全部分支:安定值、成功率、+9 以上「無事發生」、失敗蒸發、祝福 +1/+2/+3、咀咒 −1 與 −7 蒸發、象牙塔/試煉/幻象限制、封印不可點 Enchant.java(行號見 README),rates.properties 30/30,Random.java 的 32768 格亂數表
系統訊息文字 client string-c.tbl 79/160/161/164、desc-c.tbl $245–$248、$252
道具名稱格式(+N、未鑑定、數量、(揮舞)/(使用中)) L1ItemInstance.getNumberedName/getViewName
背包內容 DB character_items(費倫 的 72 件,含裝備中與強化值)
道具圖示 client {invgfx}.tbt
NPC 位置/朝向/外觀/對話 spawnlist_npc、npc、npcaction,client *-c.html
商店品項、價格、購買檢查 DB shop、L1Shop.ensureSell(189/263/904)
地圖、建物、樹、陰影與半透明 client .s32+Tile.pak,繪製順序照 L1MapViewer,tile type 0x04/0x10/0x20 的 alpha
可走範圍 伺服器 maps/4.txt+L1V1Map.isPassable 八方向規則
角色與 NPC 動畫、影子、走路節奏(4 格 × 4 tick) client .spr+list.spr
UI 點陣圖(HUD、道具欄、角色視窗、對話框、商店、按鈕、關閉標籤) client Sprite*.pak 的 .img
走路腳步聲、穿裝音效 client Sound/478,479.wav、equipsound.def

推定或近似(原始檔裡沒有答案,需要實機確認)

項目 目前做法 怎麼確認
\f1 等顏色碼 \f1 用淺藍,其他見 ui.js 的 FCOLOR 表 實機截一張「沒有任何事情發生」的訊息
受祝福/受詛咒的名稱 道具欄與商店名稱前加 client 字串 $227 受祝福的、$228 受詛咒的(DB 名稱不含這兩個字,推定由 client 依 bless 位元加上) 實機滑過祝福卷軸
角色視窗哪一格放哪種裝備 從 567c 偵測出 12 個格子,依身體位置對應(頭→頭盔、手→武器、腳→長靴…) 實機穿脫各部位
HUD 上 AC/MR 與 HP/MP 數字的位置和字型 用瀏覽器的細明體畫在推定位置 實機截 HUD
滑過道具顯示名稱的方式 黑底小提示框 實機滑過道具
滑鼠游標 游標圖存在 Lin.bin 裡而且被加殼加密,所以用 CSS 十字游標 —
角色在畫面上的位置 畫面寬度置中,y = 196(HUD 上方地圖區的中間) 實機截圖量測
商店的 O.K./Cancel 位置 放在 549 底部 Total 欄右側 實機開商店
角色被建物遮擋 以同一物件群組、同一欄位的最低點當地面線來判斷前後 實機走到屋後
字型 瀏覽器的細明體 12px,不是 client 的點陣字 —

沒有做(超出範圍)

傳送、倉庫、販賣(GM 商店的 purchasing_price 全是 −1,本來就不收購)、重量上限(訊息 82)、魔法、戰鬥、其他道具的使用效果、聊天輸入、F5–F12 快捷鍵、其他玩家、日夜變化。

與舊工具(toolhubmaster d95fe2e5「天堂 衝裝模擬器」)的差異

舊工具 本作
訊息 自己寫的句子(例如「成功變成 +7」) client string-c.tbl 原文,包含 %0/$245 的組合方式
祝福卷軸失敗 退 1 級 照 Enchant.java 會蒸發(沒有退級規則)
機率 設定檔自訂 Enchant.java 公式+伺服器實際的 rates.properties(30/30),並重現伺服器的亂數表
UI 網路上的 LineageEmulator 圖 client .img 原圖,640×480
NPC 黑色剪影,用騎士圖代替 正確解碼(包含平民 NPC 的分層部件合成)與影子
背包 預設清單 費倫 在 DB 裡的真實背包
地圖 物件 x 座標用有號數讀,跨區塊的 tile 位置錯 無號數+16 位元 tile id+layer 排序+半透明