feat(thief): 도적 클래스 + 2차 전직(어쌔신/시프) 추가 (P14-4)

- CLASSES.thief(maxHp 75), JOBS.thief = [어쌔신(CriticalThrow), 시프(SavageBlow)]
- 카드 11종: 도적1차(럭키세븐/더블스탭/다크사이트/헤이스트/드레인)
  · 어쌔신(크리티컬스로우/쉐도우스타/클로마스터리)
  · 시프(새비지블로우/스틸/메소가드), starterDecks.thief 추가
- cardframes classToFrame: thief/assassin/bandit → bandit 프레임(기존 RUID 재사용)
- CharacterSelectHud Thief 해금, BindMenuButtons ThiefButton, RenderCharacterSelect·
  StartNewGame·StartRun·JobLabel 도적 분기. 전사/법사 2차는 기존 완비 확인
- 산출물 재생성(생성기 검증 통과)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 00:38:52 +09:00
parent 8f58a90746
commit d546d62755
5 changed files with 181 additions and 305 deletions

File diff suppressed because one or more lines are too long

View File

@@ -7,6 +7,7 @@
"classToFrame": {
"warrior": "warrior", "fighter": "warrior", "page": "warrior", "spearman": "warrior",
"magician": "magician", "firepoison": "magician", "icelightning": "magician", "cleric": "magician",
"thief": "bandit", "assassin": "bandit", "bandit": "bandit",
"curse": "bandit"
},
"rewardWeights": { "normal": 70, "unique": 25, "legend": 5 }

View File

@@ -336,6 +336,115 @@
"image": "0265e103b4904f178b1c2bdcd54d5975",
"rarity": "unique"
},
"LuckySeven": {
"name": "럭키 세븐",
"cost": 1,
"kind": "Attack",
"class": "thief",
"damage": 3,
"hits": 2,
"desc": "피해 3 × 2회",
"rarity": "normal"
},
"DoubleStab": {
"name": "더블 스탭",
"cost": 2,
"kind": "Attack",
"class": "thief",
"damage": 5,
"hits": 2,
"desc": "피해 5 × 2회",
"rarity": "normal"
},
"DarkSight": {
"name": "다크 사이트",
"cost": 1,
"kind": "Skill",
"class": "thief",
"block": 6,
"desc": "방어도 6",
"rarity": "normal"
},
"Haste": {
"name": "헤이스트",
"cost": 1,
"kind": "Skill",
"class": "thief",
"block": 3,
"draw": 1,
"desc": "방어도 3, 드로 1",
"rarity": "normal"
},
"Drain": {
"name": "드레인",
"cost": 1,
"kind": "Attack",
"class": "thief",
"damage": 5,
"heal": 3,
"desc": "피해 5, HP 3 회복",
"rarity": "unique"
},
"CriticalThrow": {
"name": "크리티컬 스로우",
"cost": 2,
"kind": "Attack",
"class": "assassin",
"damage": 8,
"hits": 2,
"desc": "피해 8 × 2회",
"rarity": "unique"
},
"ShadowStar": {
"name": "쉐도우 스타",
"cost": 1,
"kind": "Attack",
"class": "assassin",
"damage": 6,
"weak": 1,
"desc": "피해 6, 약화 1",
"rarity": "unique"
},
"ClawMastery": {
"name": "클로 마스터리",
"cost": 1,
"kind": "Power",
"class": "assassin",
"powerEffect": "strengthPerTurn",
"value": 1,
"desc": "매 턴 힘 +1",
"rarity": "legend"
},
"SavageBlow": {
"name": "새비지 블로우",
"cost": 1,
"kind": "Attack",
"class": "bandit",
"damage": 3,
"hits": 3,
"desc": "피해 3 × 3회",
"rarity": "unique"
},
"Steal": {
"name": "스틸",
"cost": 1,
"kind": "Skill",
"class": "bandit",
"block": 4,
"draw": 1,
"desc": "방어도 4, 드로 1",
"rarity": "unique"
},
"MesoGuard": {
"name": "메소 가드",
"cost": 1,
"kind": "Power",
"class": "bandit",
"powerEffect": "blockPerTurn",
"value": 3,
"desc": "매 턴 방어도 +3",
"rarity": "legend"
},
"Wound": {
"name": "상처",
"cost": 0,
@@ -382,6 +491,18 @@
"MagicGuard",
"MagicGuard",
"MagicClaw"
],
"thief": [
"LuckySeven",
"LuckySeven",
"LuckySeven",
"LuckySeven",
"LuckySeven",
"DarkSight",
"DarkSight",
"DarkSight",
"DarkSight",
"DoubleStab"
]
}
}

View File

@@ -7,6 +7,7 @@ const ENEMIES = JSON.parse(readFileSync('data/enemies.json', 'utf8'));
const CLASSES = {
warrior: { label: '전사', maxHp: 80 },
magician: { label: '마법사', maxHp: 70 },
thief: { label: '도적', maxHp: 75 },
};
for (const cls of Object.keys(CLASSES)) {
if (!CARDS.starterDecks?.[cls]) throw new Error(`[gen-slaydeck] starterDecks.${cls} 없음`);
@@ -26,6 +27,10 @@ const JOBS = {
{ id: 'icelightning', name: '위자드(썬·콜)', desc: '광역·빙결 특화\n썬더 볼트(전체)\n콜드 빔 · 칠링 스텝', starter: 'ThunderBolt' },
{ id: 'cleric', name: '클레릭', desc: '회복·축복 특화\n힐 · 블레스\n홀리 애로우', starter: 'Heal' },
],
thief: [
{ id: 'assassin', name: '어쌔신', desc: '표창·치명타 특화\n크리티컬 스로우\n쉐도우 스타 · 클로', starter: 'CriticalThrow' },
{ id: 'bandit', name: '시프', desc: '단검 연타 특화\n새비지 블로우\n스틸 · 메소 가드', starter: 'SavageBlow' },
],
};
for (const [cls, jobs] of Object.entries(JOBS)) {
for (const j of jobs) {
@@ -2247,7 +2252,7 @@ function upsertUi() {
}));
const classCards = [
{ key: 'Warrior', label: '\uC804\uC0AC', desc: '\uAC15\uD55C \uACF5\uACA9\uACFC \uBC29\uC5B4', x: -360, enabled: true, tint: { r: 0.74, g: 0.32, b: 0.28, a: 1 } },
{ key: 'Thief', label: '\uB3C4\uC801', desc: '\uCD94\uD6C4 \uC5F4\uB9BC', x: 0, enabled: false, tint: { r: 0.18, g: 0.19, b: 0.21, a: 1 } },
{ key: 'Thief', label: '\uB3C4\uC801', desc: '\uBE60\uB978 \uB2E8\uAC80 \uC5F0\uD0C0', x: 0, enabled: true, tint: { r: 0.5, g: 0.32, b: 0.6, a: 1 } },
{ key: 'Mage', label: '\uB9C8\uBC95\uC0AC', desc: '\uB9C8\uBC95 \uC6D0\uAC70\uB9AC \uB51C\uB7EC', x: 360, enabled: true, tint: { r: 0.3, g: 0.4, b: 0.75, a: 1 } },
];
for (let i = 0; i < classCards.length; i++) {
@@ -2438,6 +2443,7 @@ function writeCodeblocks() {
prop('any', 'NewGameHandler'),
prop('any', 'WarriorSelectHandler'),
prop('any', 'MageSelectHandler'),
prop('any', 'ThiefSelectHandler'),
prop('any', 'AscMinusHandler'),
prop('any', 'AscPlusHandler'),
prop('any', 'JobOpts'),
@@ -2615,6 +2621,14 @@ if mage ~= nil and mage.ButtonComponent ~= nil then
end
self.MageSelectHandler = mage:ConnectEvent(ButtonClickEvent, function() self:SelectClass("magician") end)
end
local thief = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/ThiefButton")
if thief ~= nil and thief.ButtonComponent ~= nil then
if self.ThiefSelectHandler ~= nil then
thief:DisconnectEvent(ButtonClickEvent, self.ThiefSelectHandler)
self.ThiefSelectHandler = nil
end
self.ThiefSelectHandler = thief:ConnectEvent(ButtonClickEvent, function() self:SelectClass("thief") end)
end
local start = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/StartButton")
if start ~= nil and start.ButtonComponent ~= nil then
if self.StartGameHandler ~= nil then
@@ -2662,14 +2676,24 @@ if mage ~= nil and mage.SpriteGUIRendererComponent ~= nil then
mage.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)
end
end
local thief = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/ThiefButton")
if thief ~= nil and thief.SpriteGUIRendererComponent ~= nil then
if self.SelectedClass == "thief" then
thief.SpriteGUIRendererComponent.Color = Color(0.28, 0.36, 0.46, 1)
else
thief.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)
end
end
if self.SelectedClass == "warrior" then
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "전사 선택됨")
elseif self.SelectedClass == "magician" then
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "마법사 선택됨")
elseif self.SelectedClass == "thief" then
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "도적 선택됨")
else
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "직업을 선택하고 시작하세요")
end`),
method('StartNewGame', `if self.SelectedClass ~= "warrior" and self.SelectedClass ~= "magician" then
method('StartNewGame', `if self.SelectedClass ~= "warrior" and self.SelectedClass ~= "magician" and self.SelectedClass ~= "thief" then
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "직업을 먼저 선택하세요")
return
end
@@ -2684,6 +2708,9 @@ end`, [
method('StartRun', `if self.SelectedClass == "magician" then
self.PlayerMaxHp = ${CLASSES.magician.maxHp}
self.RunDeck = { ${CARDS.starterDecks.magician.map(luaStr).join(', ')} }
elseif self.SelectedClass == "thief" then
self.PlayerMaxHp = ${CLASSES.thief.maxHp}
self.RunDeck = { ${CARDS.starterDecks.thief.map(luaStr).join(', ')} }
else
self.PlayerMaxHp = ${CLASSES.warrior.maxHp}
self.RunDeck = { ${CARDS.starterDecks.warrior.map(luaStr).join(', ')} }
@@ -3805,6 +3832,8 @@ if self.SelectedClass == "warrior" then
return "전사"
elseif self.SelectedClass == "magician" then
return "마법사"
elseif self.SelectedClass == "thief" then
return "도적"
end
return "플레이어"`, [], 0, 'string'),
method('SetJob', `self.PlayerJob = jobId

View File

@@ -229259,9 +229259,9 @@
"PreserveSprite": 0,
"StartFrameIndex": 0,
"Color": {
"r": 0.11,
"g": 0.12,
"b": 0.14,
"r": 0.16,
"g": 0.2,
"b": 0.26,
"a": 1
},
"DropShadow": false,
@@ -229291,7 +229291,7 @@
"a": 1
},
"OutlineWidth": 3,
"RaycastTarget": false,
"RaycastTarget": true,
"Type": 1,
"Enable": true
},
@@ -229340,7 +229340,7 @@
"KeyCode": 0,
"OverrideSorting": false,
"Transition": 1,
"Enable": false
"Enable": true
}
],
"@version": 1
@@ -229498,9 +229498,9 @@
"DropShadowDistance": 32,
"Font": 0,
"FontColor": {
"r": 0.55,
"g": 0.58,
"b": 0.62,
"r": 0.94,
"g": 0.74,
"b": 0.26,
"a": 1
},
"FontSize": 34,
@@ -229635,9 +229635,9 @@
"PreserveSprite": 0,
"StartFrameIndex": 0,
"Color": {
"r": 0.18,
"g": 0.19,
"b": 0.21,
"r": 0.5,
"g": 0.32,
"b": 0.6,
"a": 1
},
"DropShadow": false,
@@ -229827,9 +229827,9 @@
"DropShadowDistance": 32,
"Font": 0,
"FontColor": {
"r": 0.52,
"g": 0.55,
"b": 0.59,
"r": 0.86,
"g": 0.9,
"b": 0.94,
"a": 1
},
"FontSize": 20,
@@ -229855,7 +229855,7 @@
"bottom": 0
},
"SizeFit": false,
"Text": "추후 열림",
"Text": "빠른 단검 연타",
"UseOutLine": true,
"Enable": true
}
@@ -229863,288 +229863,6 @@
"@version": 1
}
},
{
"id": "0e000097-0000-4000-8000-00000e000097",
"path": "/ui/DefaultGroup/CharacterSelectHud/ThiefButton/LockBody",
"componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent",
"jsonString": {
"name": "LockBody",
"path": "/ui/DefaultGroup/CharacterSelectHud/ThiefButton/LockBody",
"nameEditable": true,
"enable": true,
"visible": true,
"localize": true,
"displayOrder": 3,
"pathConstraints": "/////",
"revision": 1,
"origin": {
"type": "Model",
"entry_id": "UISprite",
"sub_entity_id": null,
"root_entity_id": null,
"replaced_model_id": null
},
"modelId": "uisprite",
"@components": [
{
"@type": "MOD.Core.UITransformComponent",
"ActivePlatform": 255,
"AlignmentOption": 0,
"AnchorsMax": {
"x": 0.5,
"y": 0.5
},
"AnchorsMin": {
"x": 0.5,
"y": 0.5
},
"MobileOnly": false,
"OffsetMax": {
"x": 38,
"y": 33
},
"OffsetMin": {
"x": -38,
"y": -25
},
"Pivot": {
"x": 0.5,
"y": 0.5
},
"RectSize": {
"x": 76,
"y": 58
},
"UIMode": 1,
"UIScale": {
"x": 1,
"y": 1,
"z": 1
},
"UIVersion": 2,
"anchoredPosition": {
"x": 0,
"y": 4
},
"Position": {
"x": 0,
"y": 4,
"z": 0
},
"QuaternionRotation": {
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"Scale": {
"x": 1,
"y": 1,
"z": 1
},
"Enable": true
},
{
"@type": "MOD.Core.SpriteGUIRendererComponent",
"AnimClipPlayType": 0,
"EndFrameIndex": 2147483647,
"ImageRUID": {
"DataId": ""
},
"LocalPosition": {
"x": 0,
"y": 0
},
"LocalScale": {
"x": 1,
"y": 1
},
"OverrideSorting": false,
"PlayRate": 1,
"PreserveSprite": 0,
"StartFrameIndex": 0,
"Color": {
"r": 0.78,
"g": 0.69,
"b": 0.42,
"a": 1
},
"DropShadow": false,
"DropShadowAngle": 30,
"DropShadowColor": {
"r": 0,
"g": 0,
"b": 0,
"a": 0.72
},
"DropShadowDistance": 32,
"FillAmount": 1,
"FillCenter": true,
"FillClockWise": true,
"FillMethod": 0,
"FillOrigin": 0,
"FlipX": false,
"FlipY": false,
"FrameColumn": 1,
"FrameRate": 0,
"FrameRow": 1,
"Outline": false,
"OutlineColor": {
"r": 0,
"g": 0,
"b": 0,
"a": 1
},
"OutlineWidth": 3,
"RaycastTarget": false,
"Type": 1,
"Enable": true
}
],
"@version": 1
}
},
{
"id": "0e0000a1-0000-4000-8000-00000e0000a1",
"path": "/ui/DefaultGroup/CharacterSelectHud/ThiefButton/LockShackle",
"componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent",
"jsonString": {
"name": "LockShackle",
"path": "/ui/DefaultGroup/CharacterSelectHud/ThiefButton/LockShackle",
"nameEditable": true,
"enable": true,
"visible": true,
"localize": true,
"displayOrder": 4,
"pathConstraints": "/////",
"revision": 1,
"origin": {
"type": "Model",
"entry_id": "UISprite",
"sub_entity_id": null,
"root_entity_id": null,
"replaced_model_id": null
},
"modelId": "uisprite",
"@components": [
{
"@type": "MOD.Core.UITransformComponent",
"ActivePlatform": 255,
"AlignmentOption": 0,
"AnchorsMax": {
"x": 0.5,
"y": 0.5
},
"AnchorsMin": {
"x": 0.5,
"y": 0.5
},
"MobileOnly": false,
"OffsetMax": {
"x": 27,
"y": 69
},
"OffsetMin": {
"x": -27,
"y": 27
},
"Pivot": {
"x": 0.5,
"y": 0.5
},
"RectSize": {
"x": 54,
"y": 42
},
"UIMode": 1,
"UIScale": {
"x": 1,
"y": 1,
"z": 1
},
"UIVersion": 2,
"anchoredPosition": {
"x": 0,
"y": 48
},
"Position": {
"x": 0,
"y": 48,
"z": 0
},
"QuaternionRotation": {
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"Scale": {
"x": 1,
"y": 1,
"z": 1
},
"Enable": true
},
{
"@type": "MOD.Core.SpriteGUIRendererComponent",
"AnimClipPlayType": 0,
"EndFrameIndex": 2147483647,
"ImageRUID": {
"DataId": ""
},
"LocalPosition": {
"x": 0,
"y": 0
},
"LocalScale": {
"x": 1,
"y": 1
},
"OverrideSorting": false,
"PlayRate": 1,
"PreserveSprite": 0,
"StartFrameIndex": 0,
"Color": {
"r": 0.78,
"g": 0.69,
"b": 0.42,
"a": 1
},
"DropShadow": false,
"DropShadowAngle": 30,
"DropShadowColor": {
"r": 0,
"g": 0,
"b": 0,
"a": 0.72
},
"DropShadowDistance": 32,
"FillAmount": 1,
"FillCenter": true,
"FillClockWise": true,
"FillMethod": 0,
"FillOrigin": 0,
"FlipX": false,
"FlipY": false,
"FrameColumn": 1,
"FrameRate": 0,
"FrameRow": 1,
"Outline": false,
"OutlineColor": {
"r": 0,
"g": 0,
"b": 0,
"a": 1
},
"OutlineWidth": 3,
"RaycastTarget": false,
"Type": 1,
"Enable": true
}
],
"@version": 1
}
},
{
"id": "0e000070-0000-4000-8000-00000e000070",
"path": "/ui/DefaultGroup/CharacterSelectHud/MageButton",