天堂 3.80C 銀騎士村 × 點裝備(離線 HTML 1:1 復刻)
打開 index.html 就能玩(file:// 也可以,不需要網路、不需要伺服器)。法師 費倫 站在她在資料庫裡的真實位置
(map 4,33080, 33390,銀騎士村的 GM 商人區「古代XX商人^潘朵拉」),背包是她在 DB 裡的真實背包(72 件)。
- 移動:左鍵點地面(按住可連續走);會照伺服器
maps/4.txt的方向通行規則尋路。 - NPC:點 NPC 會走過去並開啟 client 原版對話(
Text.pak裡的*-c.html);潘朵拉的「購買」可買卷軸/裝備。 - 點裝備:
Tab(或I、HUD 的背包鈕)開道具欄 → 雙擊強化卷軸 → 點要施法的武器/防具(道具欄或角色視窗裡的裝備都可以)。Esc取消。 - 角色/裝備視窗:
C或 HUD 的人形鈕。雙擊武器/防具可以穿脫。 - 畫面是 client 的 640×480,UI 全部用 client 原始點陣圖(HUD
406、道具欄538c、角色567c、對話562、商店549)。 - 右側「點裝備資訊」面板(遊戲畫面外,原版沒有):游標停在目標上時顯示伺服器公式算出的機率、統計與紀錄; 「補充卷軸」是測試用捷徑(原版請向 古代卷軸商人 買,1 金幣一張,一般/受祝福/受咀咒都有)。
地點說明:派工單寫「說話之島村莊」,但 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 行
- 目標不是武器、安定值 < 0、封印(bless ≥ 128)→ 訊息 79「沒有任何事情發生。」,卷軸不消耗(39–43)。
- 象牙塔武器(7, 35, 48, 73, 105, 120, 147, 156, 174, 175, 224)→ 79(44–49);試煉之劍、幻象武器只吃對應卷軸(50–73)。
- 受咀咒卷軸(77–84):消耗 1 張;強化值 < −6 → 蒸發;否則 −1(一定成功)。
- 強化值 < 安定值(85–87):必定成功,升級量 =
RandomELevel。 - 其他(88–109):
rnd = Random.nextInt(100) + 1(1–100) - 強化值 ≥ 9:
chance = (100 + 3 × EnchantChanceWeapon) / 6(整數除法)= 31 - 否則:
chance = (100 + 3 × EnchantChanceWeapon) / 3= 63 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 其他照原始碼實作的細節
- 裝備中的防具成功/失敗會即時改 AC(
SuccessEnchant第 448–477 行;L1EquipmentSlot的ac − 強化值)。 - 飾品強化(49148,第 186–296 行):+0 起
(50+lv)/(lv+1)+35,上限 +10,依 grade 加四屬性/HP/MP,+6 額外獎勵。 - 屬性強化(41429–41432,第 298–358 行):
AttrEnchantChance = 10,同屬性最多 3 階。 - 商店購買(
SRC/server/model/shop/L1Shop.java第 138–177 行):金幣不足 189、超過 180 件 263、總價上限 904;買到的道具是已鑑定。 (重量上限 82 未模擬。)
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 |
.idx/.pak解密:移植自 tony1223/PakViewer(PakTools.cs+ Map1–5 表);SPR/TBT 解碼同樣照 PakViewer。- 物件繪製順序:照 tony1223/L1MapViewer(
ViewportRenderer.cs)——全域依 tile 的 layer 值穩定排序; tile type bit0x04半透明、0x10/0x20反向 alpha(陰影、煙、血)。 - 角色被建物遮住:把角色「後方」的物件 tile 再畫一次在角色上(以同一物件群組、同一欄位最低點當地面線)。
- 資料庫密碼只在建置時讀取,不在任何輸出檔內。
重建:
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 排序+半透明 |




