메이플 전사 전직 스킬 카드 및 연계 기믹 구현 #110

Merged
maple merged 2 commits from codex/maple-warrior-skill-cards into main 2026-07-04 02:29:13 +09:00
6 changed files with 153 additions and 91 deletions
Showing only changes of commit 758ebd19f2 - Show all commits

File diff suppressed because one or more lines are too long

View File

@@ -45,7 +45,7 @@
"fx": "48754be05be344358cddd55aa8fe11f4" "fx": "48754be05be344358cddd55aa8fe11f4"
}, },
"MoltenFist": { "MoltenFist": {
"name": "녹아내리는 주먹", "name": "불꽃 강타",
"cost": 1, "cost": 1,
"kind": "Attack", "kind": "Attack",
"damage": 10, "damage": 10,
@@ -58,7 +58,7 @@
"fx": "6f283d96d5804b4fb88009685a11c1f8" "fx": "6f283d96d5804b4fb88009685a11c1f8"
}, },
"BodySlam": { "BodySlam": {
"name": "몸통 박치기", "name": "방패 밀어치기",
"cost": 1, "cost": 1,
"kind": "Attack", "kind": "Attack",
"damageFromCurrentBlock": 1, "damageFromCurrentBlock": 1,
@@ -81,7 +81,7 @@
"fx": "2799562e984c4a4da3b73e1f3431057c" "fx": "2799562e984c4a4da3b73e1f3431057c"
}, },
"SwordBoomerang": { "SwordBoomerang": {
"name": "부메랑 칼날", "name": "회전 검격",
"cost": 1, "cost": 1,
"kind": "Attack", "kind": "Attack",
"damage": 3, "damage": 3,
@@ -94,7 +94,7 @@
"fx": "1b0afc410a1a458598eb7ca2fb26e97d" "fx": "1b0afc410a1a458598eb7ca2fb26e97d"
}, },
"SetupStrike": { "SetupStrike": {
"name": "사전 타격", "name": "기선 타격",
"cost": 1, "cost": 1,
"kind": "Attack", "kind": "Attack",
"damage": 7, "damage": 7,
@@ -106,7 +106,7 @@
"fx": "291b2298db88476f8ae3c6c78f53c9b7" "fx": "291b2298db88476f8ae3c6c78f53c9b7"
}, },
"TwinStrike": { "TwinStrike": {
"name": "이중 타격", "name": "연속 타격",
"cost": 1, "cost": 1,
"kind": "Attack", "kind": "Attack",
"damage": 5, "damage": 5,
@@ -118,7 +118,7 @@
"fx": "863812c5c2f84132ac7465b50ec2283e" "fx": "863812c5c2f84132ac7465b50ec2283e"
}, },
"Breakthrough": { "Breakthrough": {
"name": "정면 돌파", "name": "전선 돌파",
"cost": 1, "cost": 1,
"kind": "Attack", "kind": "Attack",
"damage": 9, "damage": 9,
@@ -130,7 +130,7 @@
"fx": "e8a145a6c43d493f9ad50fab03b200aa" "fx": "e8a145a6c43d493f9ad50fab03b200aa"
}, },
"Thunderclap": { "Thunderclap": {
"name": "천둥", "name": "전장의 벼락",
"cost": 1, "cost": 1,
"kind": "Attack", "kind": "Attack",
"damage": 4, "damage": 4,
@@ -144,7 +144,7 @@
"fx": "48754be05be344358cddd55aa8fe11f4" "fx": "48754be05be344358cddd55aa8fe11f4"
}, },
"IronWave": { "IronWave": {
"name": "철의 파동", "name": "강철 검기",
"cost": 1, "cost": 1,
"kind": "Attack", "kind": "Attack",
"damage": 5, "damage": 5,
@@ -156,7 +156,7 @@
"fx": "6f283d96d5804b4fb88009685a11c1f8" "fx": "6f283d96d5804b4fb88009685a11c1f8"
}, },
"PommelStrike": { "PommelStrike": {
"name": "폼멜 타격", "name": "칼자루 타격",
"cost": 1, "cost": 1,
"kind": "Attack", "kind": "Attack",
"damage": 9, "damage": 9,
@@ -168,7 +168,7 @@
"fx": "997fa6999aa04dbb97a1dd99025fa2ba" "fx": "997fa6999aa04dbb97a1dd99025fa2ba"
}, },
"PerfectedStrike": { "PerfectedStrike": {
"name": "완벽한 타격", "name": "숙련된 타격",
"cost": 2, "cost": 2,
"kind": "Attack", "kind": "Attack",
"damage": 6, "damage": 6,
@@ -181,7 +181,7 @@
"fx": "2799562e984c4a4da3b73e1f3431057c" "fx": "2799562e984c4a4da3b73e1f3431057c"
}, },
"Cinder": { "Cinder": {
"name": "잿불", "name": "잿불 검격",
"cost": 2, "cost": 2,
"kind": "Attack", "kind": "Attack",
"damage": 18, "damage": 18,
@@ -193,7 +193,7 @@
"fx": "1b0afc410a1a458598eb7ca2fb26e97d" "fx": "1b0afc410a1a458598eb7ca2fb26e97d"
}, },
"Bloodletting": { "Bloodletting": {
"name": "사혈", "name": "투지 분출",
"cost": 0, "cost": 0,
"kind": "Skill", "kind": "Skill",
"gainEnergy": 2, "gainEnergy": 2,
@@ -203,7 +203,7 @@
"rarity": "normal" "rarity": "normal"
}, },
"Tremble": { "Tremble": {
"name": "떨림", "name": "위압",
"cost": 1, "cost": 1,
"kind": "Skill", "kind": "Skill",
"vuln": 3, "vuln": 3,
@@ -225,7 +225,7 @@
"rarity": "normal" "rarity": "normal"
}, },
"TrueGrit": { "TrueGrit": {
"name": "진정한 끈기", "name": "강철 의지",
"cost": 1, "cost": 1,
"kind": "Skill", "kind": "Skill",
"block": 7, "block": 7,
@@ -236,7 +236,7 @@
"rarity": "normal" "rarity": "normal"
}, },
"Havoc": { "Havoc": {
"name": "파괴", "name": "전투 전술",
"cost": 1, "cost": 1,
"kind": "Skill", "kind": "Skill",
"nextSkillCostZero": true, "nextSkillCostZero": true,
@@ -246,7 +246,7 @@
"rarity": "normal" "rarity": "normal"
}, },
"ShrugItOff": { "ShrugItOff": {
"name": "흘려보내기", "name": "충격 흘리기",
"cost": 1, "cost": 1,
"kind": "Skill", "kind": "Skill",
"block": 8, "block": 8,
@@ -257,7 +257,7 @@
"rarity": "normal" "rarity": "normal"
}, },
"BloodWall": { "BloodWall": {
"name": "피의 벽", "name": "붉은 방벽",
"cost": 2, "cost": 2,
"kind": "Skill", "kind": "Skill",
"block": 16, "block": 16,
@@ -281,7 +281,7 @@
"fx": "1b0afc410a1a458598eb7ca2fb26e97d" "fx": "1b0afc410a1a458598eb7ca2fb26e97d"
}, },
"Spite": { "Spite": {
"name": "악의", "name": "맹공",
"cost": 0, "cost": 0,
"kind": "Attack", "kind": "Attack",
"damage": 5, "damage": 5,
@@ -293,7 +293,7 @@
"fx": "291b2298db88476f8ae3c6c78f53c9b7" "fx": "291b2298db88476f8ae3c6c78f53c9b7"
}, },
"Bully": { "Bully": {
"name": "박", "name": "전장의 압박",
"cost": 0, "cost": 0,
"kind": "Attack", "kind": "Attack",
"damage": 4, "damage": 4,
@@ -305,7 +305,7 @@
"fx": "863812c5c2f84132ac7465b50ec2283e" "fx": "863812c5c2f84132ac7465b50ec2283e"
}, },
"Pillage": { "Pillage": {
"name": "갈취", "name": "전리품 확보",
"cost": 1, "cost": 1,
"kind": "Attack", "kind": "Attack",
"damage": 6, "damage": 6,
@@ -317,7 +317,7 @@
"fx": "e8a145a6c43d493f9ad50fab03b200aa" "fx": "e8a145a6c43d493f9ad50fab03b200aa"
}, },
"Rampage": { "Rampage": {
"name": "광란", "name": "거듭된 맹공",
"cost": 2, "cost": 2,
"kind": "Attack", "kind": "Attack",
"damage": 12, "damage": 12,
@@ -329,7 +329,7 @@
"fx": "48754be05be344358cddd55aa8fe11f4" "fx": "48754be05be344358cddd55aa8fe11f4"
}, },
"AshenStrike": { "AshenStrike": {
"name": "잿빛 타격", "name": "누적 타격",
"cost": 1, "cost": 1,
"kind": "Attack", "kind": "Attack",
"damage": 6, "damage": 6,
@@ -341,7 +341,7 @@
"fx": "6f283d96d5804b4fb88009685a11c1f8" "fx": "6f283d96d5804b4fb88009685a11c1f8"
}, },
"Dismantle": { "Dismantle": {
"name": "해체", "name": "갑주 가르기",
"cost": 1, "cost": 1,
"kind": "Attack", "kind": "Attack",
"damage": 8, "damage": 8,
@@ -353,7 +353,7 @@
"fx": "997fa6999aa04dbb97a1dd99025fa2ba" "fx": "997fa6999aa04dbb97a1dd99025fa2ba"
}, },
"Hemokinesis": { "Hemokinesis": {
"name": "혈류", "name": "혼신의 일격",
"cost": 1, "cost": 1,
"kind": "Attack", "kind": "Attack",
"damage": 15, "damage": 15,
@@ -364,7 +364,7 @@
"fx": "2799562e984c4a4da3b73e1f3431057c" "fx": "2799562e984c4a4da3b73e1f3431057c"
}, },
"FightMe": { "FightMe": {
"name": "덤벼라!", "name": "결투 신청",
"cost": 2, "cost": 2,
"kind": "Attack", "kind": "Attack",
"damage": 5, "damage": 5,
@@ -377,7 +377,7 @@
"fx": "1b0afc410a1a458598eb7ca2fb26e97d" "fx": "1b0afc410a1a458598eb7ca2fb26e97d"
}, },
"Unrelenting": { "Unrelenting": {
"name": "무자비", "name": "진격의 일격",
"cost": 2, "cost": 2,
"kind": "Attack", "kind": "Attack",
"damage": 14, "damage": 14,
@@ -402,7 +402,7 @@
"fx": "863812c5c2f84132ac7465b50ec2283e" "fx": "863812c5c2f84132ac7465b50ec2283e"
}, },
"Bludgeon": { "Bludgeon": {
"name": "몽둥이질", "name": "대검 강타",
"cost": 3, "cost": 3,
"kind": "Attack", "kind": "Attack",
"damage": 32, "damage": 32,
@@ -413,7 +413,7 @@
"fx": "e8a145a6c43d493f9ad50fab03b200aa" "fx": "e8a145a6c43d493f9ad50fab03b200aa"
}, },
"HowlFromBeyond": { "HowlFromBeyond": {
"name": "저편의 울음소리", "name": "전장의 포효",
"cost": 3, "cost": 3,
"kind": "Attack", "kind": "Attack",
"damage": 16, "damage": 16,
@@ -426,7 +426,7 @@
"fx": "48754be05be344358cddd55aa8fe11f4" "fx": "48754be05be344358cddd55aa8fe11f4"
}, },
"Stomp": { "Stomp": {
"name": "짓밟기", "name": "진형 붕괴",
"cost": 3, "cost": 3,
"kind": "Attack", "kind": "Attack",
"damage": 12, "damage": 12,
@@ -450,7 +450,7 @@
"rarity": "unique" "rarity": "unique"
}, },
"BattleTrance": { "BattleTrance": {
"name": "전투 최면", "name": "전투 집중",
"cost": 0, "cost": 0,
"kind": "Skill", "kind": "Skill",
"draw": 3, "draw": 3,
@@ -461,7 +461,7 @@
"rarity": "unique" "rarity": "unique"
}, },
"Colossus": { "Colossus": {
"name": "거상", "name": "철벽 자세",
"cost": 1, "cost": 1,
"kind": "Skill", "kind": "Skill",
"block": 5, "block": 5,
@@ -494,7 +494,7 @@
"rarity": "unique" "rarity": "unique"
}, },
"BurningPact": { "BurningPact": {
"name": "불타는 조약", "name": "결사의 각오",
"cost": 1, "cost": 1,
"kind": "Skill", "kind": "Skill",
"discard": 1, "discard": 1,
@@ -505,7 +505,7 @@
"rarity": "unique" "rarity": "unique"
}, },
"EvilEye": { "EvilEye": {
"name": "악마의 눈", "name": "빈틈없는 방어",
"cost": 1, "cost": 1,
"kind": "Skill", "kind": "Skill",
"block": 16, "block": 16,
@@ -515,7 +515,7 @@
"rarity": "unique" "rarity": "unique"
}, },
"ForgottenRitual": { "ForgottenRitual": {
"name": "잊힌 의식", "name": "비상 전력",
"cost": 1, "cost": 1,
"kind": "Skill", "kind": "Skill",
"gainEnergy": 2, "gainEnergy": 2,
@@ -550,7 +550,7 @@
"rarity": "unique" "rarity": "unique"
}, },
"InfernalBlade": { "InfernalBlade": {
"name": "지옥검", "name": "임시 무장",
"cost": 1, "cost": 1,
"kind": "Skill", "kind": "Skill",
"addRandomCardCount": 1, "addRandomCardCount": 1,
@@ -585,7 +585,7 @@
"rarity": "unique" "rarity": "unique"
}, },
"StoneArmor": { "StoneArmor": {
"name": " 갑옷", "name": "강철 갑옷",
"cost": 1, "cost": 1,
"kind": "Power", "kind": "Power",
"powerEffect": "blockPerTurn", "powerEffect": "blockPerTurn",
@@ -596,7 +596,7 @@
"rarity": "unique" "rarity": "unique"
}, },
"FeelNoPain": { "FeelNoPain": {
"name": "무감각", "name": "고통 인내",
"cost": 1, "cost": 1,
"kind": "Power", "kind": "Power",
"powerEffect": "blockPerTurn", "powerEffect": "blockPerTurn",
@@ -629,7 +629,7 @@
"rarity": "unique" "rarity": "unique"
}, },
"Juggling": { "Juggling": {
"name": "저글링", "name": "연속 공세",
"cost": 1, "cost": 1,
"kind": "Power", "kind": "Power",
"cardPlayedRandomDamage": 3, "cardPlayedRandomDamage": 3,
@@ -670,7 +670,7 @@
"rarity": "unique" "rarity": "unique"
}, },
"PactsEnd": { "PactsEnd": {
"name": "조약의 끝", "name": "최후의 일격",
"cost": 0, "cost": 0,
"kind": "Attack", "kind": "Attack",
"damage": 17, "damage": 17,
@@ -722,7 +722,7 @@
"fx": "997fa6999aa04dbb97a1dd99025fa2ba" "fx": "997fa6999aa04dbb97a1dd99025fa2ba"
}, },
"TearAsunder": { "TearAsunder": {
"name": "갈가리 찢기", "name": "삼연참",
"cost": 2, "cost": 2,
"kind": "Attack", "kind": "Attack",
"damage": 5, "damage": 5,
@@ -734,7 +734,7 @@
"fx": "2799562e984c4a4da3b73e1f3431057c" "fx": "2799562e984c4a4da3b73e1f3431057c"
}, },
"FiendFire": { "FiendFire": {
"name": "지옥불", "name": "최후의 패",
"cost": 2, "cost": 2,
"kind": "Attack", "kind": "Attack",
"damage": 0, "damage": 0,
@@ -748,7 +748,7 @@
"fx": "1b0afc410a1a458598eb7ca2fb26e97d" "fx": "1b0afc410a1a458598eb7ca2fb26e97d"
}, },
"Mangle": { "Mangle": {
"name": "난도질", "name": "공포의 일격",
"cost": 3, "cost": 3,
"kind": "Attack", "kind": "Attack",
"damage": 15, "damage": 15,
@@ -771,7 +771,7 @@
"rarity": "legend" "rarity": "legend"
}, },
"Cascade": { "Cascade": {
"name": "연", "name": "연속 전개",
"cost": 0, "cost": 0,
"kind": "Skill", "kind": "Skill",
"useAllEnergy": true, "useAllEnergy": true,
@@ -783,7 +783,7 @@
"rarity": "legend" "rarity": "legend"
}, },
"PrimalForce": { "PrimalForce": {
"name": "원시의 힘", "name": "전사의 본능",
"cost": 0, "cost": 0,
"kind": "Skill", "kind": "Skill",
"handCostZeroThisTurn": true, "handCostZeroThisTurn": true,
@@ -793,7 +793,7 @@
"rarity": "legend" "rarity": "legend"
}, },
"Offering": { "Offering": {
"name": "제물", "name": "전력 방출",
"cost": 0, "cost": 0,
"kind": "Skill", "kind": "Skill",
"gainEnergy": 2, "gainEnergy": 2,
@@ -805,7 +805,7 @@
"rarity": "legend" "rarity": "legend"
}, },
"OneTwoPunch": { "OneTwoPunch": {
"name": "원투 펀치", "name": "연계 전술",
"cost": 1, "cost": 1,
"kind": "Skill", "kind": "Skill",
"nextSkillRepeatCount": 1, "nextSkillRepeatCount": 1,
@@ -815,7 +815,7 @@
"rarity": "legend" "rarity": "legend"
}, },
"Stoke": { "Stoke": {
"name": "화력 증폭", "name": "전투 재정비",
"cost": 1, "cost": 1,
"kind": "Skill", "kind": "Skill",
"exhaustHandAll": true, "exhaustHandAll": true,
@@ -849,7 +849,7 @@
"rarity": "legend" "rarity": "legend"
}, },
"Aggression": { "Aggression": {
"name": "공격", "name": "공격 태세",
"cost": 1, "cost": 1,
"kind": "Power", "kind": "Power",
"turnStartDraw": 1, "turnStartDraw": 1,
@@ -859,7 +859,7 @@
"rarity": "legend" "rarity": "legend"
}, },
"Cruelty": { "Cruelty": {
"name": "악랄함", "name": "전투 광기",
"cost": 1, "cost": 1,
"kind": "Power", "kind": "Power",
"powerEffect": "strengthPerTurn", "powerEffect": "strengthPerTurn",
@@ -870,7 +870,7 @@
"rarity": "legend" "rarity": "legend"
}, },
"CrimsonMantle": { "CrimsonMantle": {
"name": "핏빛 망토", "name": "붉은 수호",
"cost": 1, "cost": 1,
"kind": "Power", "kind": "Power",
"powerEffect": "blockPerTurn", "powerEffect": "blockPerTurn",
@@ -892,7 +892,7 @@
"rarity": "legend" "rarity": "legend"
}, },
"DarkEmbrace": { "DarkEmbrace": {
"name": "어둠의 포옹", "name": "소멸의 숙련",
"cost": 2, "cost": 2,
"kind": "Power", "kind": "Power",
"drawOnExhaust": 1, "drawOnExhaust": 1,
@@ -914,7 +914,7 @@
"rarity": "legend" "rarity": "legend"
}, },
"Juggernaut": { "Juggernaut": {
"name": "절대적인 힘", "name": "검격 파동",
"cost": 2, "cost": 2,
"kind": "Power", "kind": "Power",
"cardPlayedRandomDamage": 5, "cardPlayedRandomDamage": 5,
@@ -924,7 +924,7 @@
"rarity": "legend" "rarity": "legend"
}, },
"Hellraiser": { "Hellraiser": {
"name": "지옥검무", "name": "타격의 달인",
"cost": 2, "cost": 2,
"kind": "Power", "kind": "Power",
"drawNameMatchAutoPlay": "타격", "drawNameMatchAutoPlay": "타격",
@@ -945,7 +945,7 @@
"rarity": "legend" "rarity": "legend"
}, },
"DemonForm": { "DemonForm": {
"name": "악마의 형상", "name": "전신 투지",
"cost": 3, "cost": 3,
"kind": "Power", "kind": "Power",
"powerEffect": "strengthPerTurn", "powerEffect": "strengthPerTurn",
@@ -1045,11 +1045,12 @@
}, },
"ComboAttack": { "ComboAttack": {
"name": "콤보 어택", "name": "콤보 어택",
"cost": 1, "cost": 0,
"kind": "Power", "kind": "Power",
"comboOnAttack": 1, "comboOnAttack": 1,
"comboMax": 5, "comboMax": 5,
"desc": "공격 카드를 사용할 때마다 콤보 1 획득. 최대 5", "attackDamagePerCombo": 0.5,
"desc": "공격마다 콤보 1 획득. 모든 공격이 콤보 2당 피해 +1. 최대 5",
"image": "e2580523efc6457385114b78ad0d7cce", "image": "e2580523efc6457385114b78ad0d7cce",
"class": "fighter", "class": "fighter",
"rarity": "unique" "rarity": "unique"
@@ -1099,7 +1100,7 @@
}, },
"FighterPhysicalTraining": { "FighterPhysicalTraining": {
"name": "피지컬 트레이닝", "name": "피지컬 트레이닝",
"cost": 2, "cost": 1,
"kind": "Power", "kind": "Power",
"strength": 1, "strength": 1,
"dex": 1, "dex": 1,
@@ -1139,7 +1140,7 @@
}, },
"Rush": { "Rush": {
"name": "돌진", "name": "돌진",
"cost": 1, "cost": 2,
"kind": "Attack", "kind": "Attack",
"damage": 8, "damage": 8,
"aoe": true, "aoe": true,
@@ -1162,12 +1163,13 @@
}, },
"ComboSynergy": { "ComboSynergy": {
"name": "콤보 시너지", "name": "콤보 시너지",
"cost": 2, "cost": 1,
"kind": "Power", "kind": "Power",
"comboOnAttack": 1, "comboOnAttack": 1,
"comboMax": 5, "comboMax": 5,
"attackDamagePerCombo": 1, "comboGain": 2,
"desc": "공격마다 콤보 1 획득. 모든 공격이 콤보당 피해 +1. 최대 5", "attackDamagePerCombo": 0.5,
"desc": "콤보 2 획득. 공격마다 콤보 1 획득. 모든 공격이 콤보 2당 피해 +1. 최대 5",
"image": "e2580523efc6457385114b78ad0d7cce", "image": "e2580523efc6457385114b78ad0d7cce",
"class": "crusader", "class": "crusader",
"rarity": "legend" "rarity": "legend"
@@ -1188,7 +1190,8 @@
"cost": 1, "cost": 1,
"kind": "Power", "kind": "Power",
"attackDamageVsWeakMultiplier": 1.5, "attackDamageVsWeakMultiplier": 1.5,
"desc": "약화 상태인 적에게 주는 공격 피해 1.5배", "comboGain": 2,
"desc": "콤보 2 획득. 약화 상태인 적에게 주는 공격 피해 1.5배",
"image": "291b2298db88476f8ae3c6c78f53c9b7", "image": "291b2298db88476f8ae3c6c78f53c9b7",
"class": "crusader", "class": "crusader",
"rarity": "unique" "rarity": "unique"
@@ -1263,8 +1266,8 @@
"cost": 1, "cost": 1,
"kind": "Power", "kind": "Power",
"powerEffect": "blockPerTurn", "powerEffect": "blockPerTurn",
"value": 4, "value": 3,
"desc": "매턴 방어도 4", "desc": "매턴 방어도 3",
"image": "90a9bf8eeb844b578b4e2d93ac43fedf", "image": "90a9bf8eeb844b578b4e2d93ac43fedf",
"class": "page", "class": "page",
"rarity": "normal" "rarity": "normal"
@@ -1304,28 +1307,28 @@
}, },
"DivineCharge": { "DivineCharge": {
"name": "디바인 차지", "name": "디바인 차지",
"cost": 2, "cost": 1,
"kind": "Attack", "kind": "Attack",
"damage": 4, "damage": 3,
"hits": 4, "hits": 4,
"aoe": true, "aoe": true,
"weak": 1, "weak": 1,
"affectsAllEnemies": true, "affectsAllEnemies": true,
"holyForce": true, "holyForce": true,
"damagePerHolyCharge": 1, "damagePerHolyCharge": 1,
"desc": "모든 적에게 피해 4 x 4회, 약화 1. 홀리 차지당 피해 +1. 홀리 포스.", "desc": "모든 적에게 피해 3 x 4회, 약화 1. 홀리 차지당 피해 +1. 홀리 포스.",
"image": "863812c5c2f84132ac7465b50ec2283e", "image": "863812c5c2f84132ac7465b50ec2283e",
"class": "knight", "class": "knight",
"rarity": "unique" "rarity": "unique"
}, },
"Restoration": { "Restoration": {
"name": "리스토네이션", "name": "리스토네이션",
"cost": 1, "cost": 0,
"kind": "Skill", "kind": "Skill",
"heal": 8, "heal": 6,
"healPerHolyCharge": 4, "healPerHolyCharge": 4,
"holyChargeSpendAll": true, "holyChargeSpendAll": true,
"desc": "HP 8 회복. 홀리 차지당 추가로 4 회복한 뒤 모두 소비", "desc": "HP 6 회복. 홀리 차지당 추가로 4 회복한 뒤 모두 소비",
"image": "90a9bf8eeb844b578b4e2d93ac43fedf", "image": "90a9bf8eeb844b578b4e2d93ac43fedf",
"class": "knight", "class": "knight",
"rarity": "unique" "rarity": "unique"
@@ -1358,7 +1361,7 @@
}, },
"ParashockGuard": { "ParashockGuard": {
"name": "파라쇼크 가드", "name": "파라쇼크 가드",
"cost": 2, "cost": 3,
"kind": "Power", "kind": "Power",
"strength": 1, "strength": 1,
"thorns": 3, "thorns": 3,
@@ -1370,7 +1373,7 @@
}, },
"CombatOrders": { "CombatOrders": {
"name": "컴뱃 오더스", "name": "컴뱃 오더스",
"cost": 2, "cost": 1,
"kind": "Power", "kind": "Power",
"nextSkillRepeatCount": 1, "nextSkillRepeatCount": 1,
"turnStartDraw": 1, "turnStartDraw": 1,
@@ -1384,9 +1387,9 @@
"cost": 2, "cost": 2,
"kind": "Power", "kind": "Power",
"powerEffect": "blockPerTurn", "powerEffect": "blockPerTurn",
"value": 5, "value": 4,
"thorns": 2, "thorns": 1,
"desc": "매턴 방어도 5. 가시 2", "desc": "매턴 방어도 4. 가시 1",
"image": "90a9bf8eeb844b578b4e2d93ac43fedf", "image": "90a9bf8eeb844b578b4e2d93ac43fedf",
"class": "knight", "class": "knight",
"rarity": "unique" "rarity": "unique"
@@ -2999,11 +3002,11 @@
"Strike", "Strike",
"Strike", "Strike",
"Strike", "Strike",
"Strike", "SlashBlast",
"Defend",
"Defend", "Defend",
"Defend", "Defend",
"Defend", "Defend",
"IronBody",
"Bash" "Bash"
], ],
"magician": [ "magician": [

Binary file not shown.

View File

@@ -6,7 +6,10 @@ import {
simulateCombat, simulateCombat,
} from './sim-balance.mjs'; } from './sim-balance.mjs';
const ROGUE_CLASSES = new Set(['rogue', 'thief', 'thiefmaster', 'assassin', 'hermit']); const AUDITED_CLASSES = new Set([
'rogue', 'thief', 'thiefmaster', 'assassin', 'hermit',
'warrior', 'fighter', 'crusader', 'page', 'knight',
]);
const CONTEXT_DECKS = { const CONTEXT_DECKS = {
rogue: [ rogue: [
@@ -38,6 +41,27 @@ const CONTEXT_DECKS = {
'Survivor', 'LeadingStrike', 'BladeDance', 'JavelinAcceleration', 'Survivor', 'LeadingStrike', 'BladeDance', 'JavelinAcceleration',
'JavelinMastery', 'TripleThrow', 'SpiritJavelin', 'SkilledJavelin', 'JavelinMastery', 'TripleThrow', 'SpiritJavelin', 'SkilledJavelin',
], ],
warrior: [
'Strike', 'Strike', 'Strike', 'Strike',
'Defend', 'Defend', 'Defend', 'Defend',
'Bash', 'SlashBlast', 'IronBody', 'WarriorMastery',
],
fighter: [
'Strike', 'Strike', 'Strike', 'Defend', 'Defend', 'Defend',
'Bash', 'SlashBlast', 'ComboAttack', 'Brandish', 'WeaponMastery', 'FlashSlash',
],
crusader: [
'Strike', 'Strike', 'Defend', 'Defend', 'Bash', 'SlashBlast',
'ComboAttack', 'Brandish', 'WeaponMastery', 'BraveSlash', 'ComboSynergy', 'Rush',
],
page: [
'Strike', 'Strike', 'Strike', 'Defend', 'Defend', 'Defend',
'Bash', 'SlashBlast', 'HolyCharge', 'DivineSwing', 'PageOrder', 'PageStance',
],
knight: [
'Strike', 'Strike', 'Defend', 'Defend', 'Bash', 'SlashBlast',
'HolyCharge', 'DivineSwing', 'PageOrder', 'DivineCharge', 'KnightRush', 'Restoration',
],
}; };
const ENCOUNTER_SCALE = { const ENCOUNTER_SCALE = {
@@ -46,6 +70,11 @@ const ENCOUNTER_SCALE = {
assassin: { hp: 2.25, attack: 1.65 }, assassin: { hp: 2.25, attack: 1.65 },
thiefmaster: { hp: 2.4, attack: 1.5 }, thiefmaster: { hp: 2.4, attack: 1.5 },
hermit: { hp: 2.6, attack: 1.65 }, hermit: { hp: 2.6, attack: 1.65 },
warrior: { hp: 1.9, attack: 1.5 },
fighter: { hp: 2.2, attack: 1.6 },
crusader: { hp: 2.6, attack: 1.7 },
page: { hp: 2.2, attack: 1.6 },
knight: { hp: 2.6, attack: 1.7 },
}; };
const median = (values) => { const median = (values) => {
@@ -172,7 +201,7 @@ export function auditCardEfficiency({ runs = 300, seed = 20260701 } = {}) {
const rows = []; const rows = [];
for (const [id, card] of Object.entries(cards)) { for (const [id, card] of Object.entries(cards)) {
if (!ROGUE_CLASSES.has(card.class)) continue; if (!AUDITED_CLASSES.has(card.class)) continue;
const deck = CONTEXT_DECKS[card.class].slice(); const deck = CONTEXT_DECKS[card.class].slice();
deck[replacementIndex(deck, cards, card)] = id; deck[replacementIndex(deck, cards, card)] = id;
const result = simulateDeck(scaledEncounter(data, card.class), deck, runs, seed, id); const result = simulateDeck(scaledEncounter(data, card.class), deck, runs, seed, id);
@@ -204,7 +233,7 @@ function formatPercent(value) {
export function formatEfficiencyReport(report) { export function formatEfficiencyReport(report) {
const lines = []; const lines = [];
lines.push(`도적 카드 효율 검증: 카드 ${report.rows.length}장, 카드당 ${report.runs}`); lines.push(`카드 효율 검증: 카드 ${report.rows.length}장, 카드당 ${report.runs}`);
lines.push('기준 덱:'); lines.push('기준 덱:');
for (const [classId, baseline] of Object.entries(report.baselines)) { for (const [classId, baseline] of Object.entries(report.baselines)) {
lines.push(` ${classId}: 승률 ${formatPercent(baseline.winRate)}, 평균 ${baseline.avgTurns.toFixed(2)}턴, 승리 HP ${baseline.avgHpOnWin.toFixed(1)}`); lines.push(` ${classId}: 승률 ${formatPercent(baseline.winRate)}, 평균 ${baseline.avgTurns.toFixed(2)}턴, 승리 HP ${baseline.avgHpOnWin.toFixed(1)}`);

View File

@@ -6,7 +6,7 @@ const cardsData = JSON.parse(readFileSync('data/cards.json', 'utf8'));
const enemiesData = JSON.parse(readFileSync('data/enemies.json', 'utf8')); const enemiesData = JSON.parse(readFileSync('data/enemies.json', 'utf8'));
const relicsData = JSON.parse(readFileSync('data/relics.json', 'utf8')); const relicsData = JSON.parse(readFileSync('data/relics.json', 'utf8'));
const PLAYER_MAX_HP = 70; const PLAYER_MAX_HP = { rogue: 70, warrior: 80 };
const REST_HEAL = 30; const REST_HEAL = 30;
const SECTION_COUNT = 5; const SECTION_COUNT = 5;
const NORMAL_FIGHTS = 4; const NORMAL_FIGHTS = 4;
@@ -18,6 +18,8 @@ const BOSS_POOL = ['king_slime', 'slime_boss'];
const JOBS = { const JOBS = {
thief: { tier2: 'thief', tier3: 'thiefmaster', tier2Starter: 'DaggerAcceleration', tier3Starter: 'Venom' }, thief: { tier2: 'thief', tier3: 'thiefmaster', tier2Starter: 'DaggerAcceleration', tier3Starter: 'Venom' },
assassin: { tier2: 'assassin', tier3: 'hermit', tier2Starter: 'JavelinAcceleration', tier3Starter: 'SpiritJavelin' }, assassin: { tier2: 'assassin', tier3: 'hermit', tier2Starter: 'JavelinAcceleration', tier3Starter: 'SpiritJavelin' },
fighter: { root: 'warrior', tier2: 'fighter', tier3: 'crusader', tier2Starter: 'ComboAttack', tier3Starter: 'ComboSynergy' },
page: { root: 'warrior', tier2: 'page', tier3: 'knight', tier2Starter: 'HolyCharge', tier3Starter: 'DivineCharge' },
}; };
const LINEAGES = { const LINEAGES = {
@@ -26,12 +28,17 @@ const LINEAGES = {
thiefmaster: ['rogue', 'thief', 'thiefmaster'], thiefmaster: ['rogue', 'thief', 'thiefmaster'],
assassin: ['rogue', 'assassin'], assassin: ['rogue', 'assassin'],
hermit: ['rogue', 'assassin', 'hermit'], hermit: ['rogue', 'assassin', 'hermit'],
warrior: ['warrior'],
fighter: ['warrior', 'fighter'],
crusader: ['warrior', 'fighter', 'crusader'],
page: ['warrior', 'page'],
knight: ['warrior', 'page', 'knight'],
}; };
const pick = (rng, values) => values[Math.floor(rng() * values.length)]; const pick = (rng, values) => values[Math.floor(rng() * values.length)];
export function campaignJobAtSection(branch, section) { export function campaignJobAtSection(branch, section) {
if (section <= 1) return 'rogue'; if (section <= 1) return JOBS[branch].root || 'rogue';
if (section === 2) return JOBS[branch].tier2; if (section === 2) return JOBS[branch].tier2;
return JOBS[branch].tier3; return JOBS[branch].tier3;
} }
@@ -102,11 +109,22 @@ function branchCardValue(card, branch, deck, id) {
value += card.sly ? 5 : 0; value += card.sly ? 5 : 0;
value += (card.discard || 0) * 2 + (card.drawPerDiscarded || 0) * 4; value += (card.discard || 0) * 2 + (card.drawPerDiscarded || 0) * 4;
value += (card.poisonApplicationBurstDamage || 0) * 1.5; value += (card.poisonApplicationBurstDamage || 0) * 1.5;
} else { } else if (branch === 'assassin') {
value += (card.addShiv || 0) * 3 + (card.turnStartShiv || 0) * 8; value += (card.addShiv || 0) * 3 + (card.turnStartShiv || 0) * 8;
value += (card.shivDamageBonus || 0) * 6 + (card.firstShivDamageBonus || 0) * 3; value += (card.shivDamageBonus || 0) * 6 + (card.firstShivDamageBonus || 0) * 3;
value += card.shivAoe ? 12 : 0; value += card.shivAoe ? 12 : 0;
value += card.shivRetain ? 5 : 0; value += card.shivRetain ? 5 : 0;
} else if (branch === 'fighter') {
value += (card.hits || 1) * 1.5;
value += (card.comboGain || 0) * 5 + (card.comboOnAttack || 0) * 10;
value += (card.damagePerCombo || 0) * 8 + (card.attackDamagePerCombo || 0) * 12;
value += (card.attackPlayedDamage || 0) * 5;
} else if (branch === 'page') {
value += (card.block || 0) * 0.5 + (card.cardPlayedBlock || 0) * 7;
value += (card.holyChargeOnHolyForce || 0) * 12 + (card.damagePerHolyCharge || 0) * 7;
value += (card.blockPerHolyCharge || 0) * 6 + (card.healPerHolyCharge || 0) * 3;
value += (card.damageTakenReduction || 0) * 40;
value += (card.blockOnDamaged || 0) * 3 + (card.strengthOnDamagedOnce || 0) * 5;
} }
const copies = deck.filter((cardId) => cardId === id).length; const copies = deck.filter((cardId) => cardId === id).length;
value -= copies * (card.kind === 'Power' ? 10 : 3); value -= copies * (card.kind === 'Power' ? 10 : 3);
@@ -203,11 +221,13 @@ export function simulateCampaign(branch, rng, {
minimumRewardValue = 10, minimumRewardValue = 10,
} = {}) { } = {}) {
if (!JOBS[branch]) throw new Error(`지원하지 않는 도적 분기: ${branch}`); if (!JOBS[branch]) throw new Error(`지원하지 않는 도적 분기: ${branch}`);
const root = JOBS[branch].root || 'rogue';
const maxHp = PLAYER_MAX_HP[root];
const state = { const state = {
hp: PLAYER_MAX_HP, hp: maxHp,
maxHp: PLAYER_MAX_HP, maxHp,
deck: cardsData.starterDecks.rogue.slice(), deck: cardsData.starterDecks[root].slice(),
job: 'rogue', job: root,
turns: 0, turns: 0,
sectionCleared: 0, sectionCleared: 0,
diedAt: '', diedAt: '',
@@ -306,7 +326,7 @@ function main() {
else if (args[i] === '--scale-step') scaleStep = Number.parseFloat(args[++i]); else if (args[i] === '--scale-step') scaleStep = Number.parseFloat(args[++i]);
else if (args[i] === '--reward-min') minimumRewardValue = Number.parseFloat(args[++i]); else if (args[i] === '--reward-min') minimumRewardValue = Number.parseFloat(args[++i]);
} }
for (const branch of ['thief', 'assassin']) { for (const branch of ['thief', 'assassin', 'fighter', 'page']) {
console.log(formatCampaignReport(runCampaignBatch(branch, runs, seed, { restHeal, sectionHeal, scaleStep, minimumRewardValue }))); console.log(formatCampaignReport(runCampaignBatch(branch, runs, seed, { restHeal, sectionHeal, scaleStep, minimumRewardValue })));
} }
} }

View File

@@ -17,6 +17,16 @@ test('도적 전직 시점: 1섹션 Rogue, 2섹션 2차, 3섹션부터 3차', ()
test('3차 직업은 자기 계보 카드만 사용', () => { test('3차 직업은 자기 계보 카드만 사용', () => {
assert.deepEqual(playableClassesForJob('thiefmaster'), ['rogue', 'thief', 'thiefmaster']); assert.deepEqual(playableClassesForJob('thiefmaster'), ['rogue', 'thief', 'thiefmaster']);
assert.deepEqual(playableClassesForJob('hermit'), ['rogue', 'assassin', 'hermit']); assert.deepEqual(playableClassesForJob('hermit'), ['rogue', 'assassin', 'hermit']);
assert.deepEqual(playableClassesForJob('crusader'), ['warrior', 'fighter', 'crusader']);
assert.deepEqual(playableClassesForJob('knight'), ['warrior', 'page', 'knight']);
});
test('전사 전직 시점: 1섹션 Warrior, 2섹션 2차, 3섹션부터 3차', () => {
assert.equal(campaignJobAtSection('fighter', 1), 'warrior');
assert.equal(campaignJobAtSection('fighter', 2), 'fighter');
assert.equal(campaignJobAtSection('fighter', 3), 'crusader');
assert.equal(campaignJobAtSection('page', 2), 'page');
assert.equal(campaignJobAtSection('page', 5), 'knight');
}); });
test('섹션 난이도는 3차 이후 더 빠르게 증가', () => { test('섹션 난이도는 3차 이후 더 빠르게 증가', () => {