Compare commits
8 Commits
f64e35668d
...
codex/band
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b7f7bb69f | |||
| 34531b184f | |||
| f6650a6c70 | |||
| acf295d56c | |||
| 9278c47901 | |||
| b2bf1bf4dd | |||
| 5da6e8f3aa | |||
| 71435a2c91 |
File diff suppressed because one or more lines are too long
@@ -14,7 +14,7 @@
|
|||||||
"Defend": {
|
"Defend": {
|
||||||
"name": "아이언 바디",
|
"name": "아이언 바디",
|
||||||
"cost": 1,
|
"cost": 1,
|
||||||
"kind": "Skill",
|
"kind": "Attack",
|
||||||
"block": 5,
|
"block": 5,
|
||||||
"desc": "방어도 5",
|
"desc": "방어도 5",
|
||||||
"image": "7648c3b8e1ca44fc8ec353561207a670",
|
"image": "7648c3b8e1ca44fc8ec353561207a670",
|
||||||
@@ -89,8 +89,8 @@
|
|||||||
"name": "분노",
|
"name": "분노",
|
||||||
"cost": 1,
|
"cost": 1,
|
||||||
"kind": "Power",
|
"kind": "Power",
|
||||||
"powerEffect": "strengthPerTurn",
|
"aoe": true,
|
||||||
"value": 1,
|
"damage": 4,
|
||||||
"desc": "매 턴 시작 시 힘 +1",
|
"desc": "매 턴 시작 시 힘 +1",
|
||||||
"image": "379d86e3de064959aa4612f71e84ccfb",
|
"image": "379d86e3de064959aa4612f71e84ccfb",
|
||||||
"class": "warrior",
|
"class": "warrior",
|
||||||
@@ -479,6 +479,7 @@
|
|||||||
"desc": "피해를 8 줍니다. 약화를 1 부여합니다.",
|
"desc": "피해를 8 줍니다. 약화를 1 부여합니다.",
|
||||||
"weak": 1,
|
"weak": 1,
|
||||||
"damage": 8,
|
"damage": 8,
|
||||||
|
"cardPlayedDamage": 2,
|
||||||
"image": "92a5020c978c46bdabab910598118b86"
|
"image": "92a5020c978c46bdabab910598118b86"
|
||||||
},
|
},
|
||||||
"LeadingStrike": {
|
"LeadingStrike": {
|
||||||
@@ -652,6 +653,8 @@
|
|||||||
"class": "bandit",
|
"class": "bandit",
|
||||||
"rarity": "unique",
|
"rarity": "unique",
|
||||||
"desc": "피해를 8만큼 X번 줍니다.",
|
"desc": "피해를 8만큼 X번 줍니다.",
|
||||||
|
"useAllEnergy": true,
|
||||||
|
"xDamagePerEnergy": 8,
|
||||||
"draw": 1,
|
"draw": 1,
|
||||||
"image": "92a5020c978c46bdabab910598118b86"
|
"image": "92a5020c978c46bdabab910598118b86"
|
||||||
},
|
},
|
||||||
@@ -728,6 +731,7 @@
|
|||||||
"rarity": "unique",
|
"rarity": "unique",
|
||||||
"desc": "피해를 12 줍니다. 다음에 사용하는 스킬 카드의 비용이 0 이 됩니다.",
|
"desc": "피해를 12 줍니다. 다음에 사용하는 스킬 카드의 비용이 0 이 됩니다.",
|
||||||
"damage": 12,
|
"damage": 12,
|
||||||
|
"nextSkillCostZero": true,
|
||||||
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
|
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
|
||||||
},
|
},
|
||||||
"Dash": {
|
"Dash": {
|
||||||
@@ -760,6 +764,7 @@
|
|||||||
"rarity": "unique",
|
"rarity": "unique",
|
||||||
"desc": "피해를 15 줍니다. 이번 턴에 스킬을 사용할 때마다 비용이 1 감소합니다.",
|
"desc": "피해를 15 줍니다. 이번 턴에 스킬을 사용할 때마다 비용이 1 감소합니다.",
|
||||||
"damage": 15,
|
"damage": 15,
|
||||||
|
"skillCostReductionThisTurn": 1,
|
||||||
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
|
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
|
||||||
},
|
},
|
||||||
"CalculatedGamble": {
|
"CalculatedGamble": {
|
||||||
@@ -801,8 +806,8 @@
|
|||||||
"class": "bandit",
|
"class": "bandit",
|
||||||
"rarity": "unique",
|
"rarity": "unique",
|
||||||
"desc": "카드를 1장 뽑습니다. 뽑은 카드가 스킬 카드라면, 방어도를 3 얻습니다.",
|
"desc": "카드를 1장 뽑습니다. 뽑은 카드가 스킬 카드라면, 방어도를 3 얻습니다.",
|
||||||
"block": 3,
|
|
||||||
"draw": 1,
|
"draw": 1,
|
||||||
|
"drawSkillBlock": 3,
|
||||||
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
|
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
|
||||||
},
|
},
|
||||||
"Acrobatics": {
|
"Acrobatics": {
|
||||||
@@ -983,8 +988,8 @@
|
|||||||
"rarity": "unique",
|
"rarity": "unique",
|
||||||
"desc": "내 턴 시작 시, 모든 적에게 중독을 2 부여합니다.",
|
"desc": "내 턴 시작 시, 모든 적에게 중독을 2 부여합니다.",
|
||||||
"poison": 2,
|
"poison": 2,
|
||||||
"powerEffect": "strengthPerTurn",
|
"powerEffect": "poisonPerTurn",
|
||||||
"value": 1,
|
"value": 2,
|
||||||
"image": "19361e72087946b1888684185b40d935"
|
"image": "19361e72087946b1888684185b40d935"
|
||||||
},
|
},
|
||||||
"Accuracy": {
|
"Accuracy": {
|
||||||
@@ -1017,9 +1022,8 @@
|
|||||||
"rarity": "unique",
|
"rarity": "unique",
|
||||||
"desc": "내 턴 동안 카드를 뽑을 때마다, 모든 적에게 피해를 2 줍니다.",
|
"desc": "내 턴 동안 카드를 뽑을 때마다, 모든 적에게 피해를 2 줍니다.",
|
||||||
"aoe": true,
|
"aoe": true,
|
||||||
"powerEffect": "strengthPerTurn",
|
"powerEffect": "damagePerTurn",
|
||||||
"value": 1,
|
"value": 2,
|
||||||
"damage": 2,
|
|
||||||
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
|
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
|
||||||
},
|
},
|
||||||
"GrandFinale": {
|
"GrandFinale": {
|
||||||
@@ -1075,6 +1079,7 @@
|
|||||||
"rarity": "legend",
|
"rarity": "legend",
|
||||||
"desc": "피해를 1 줍니다. 이번 전투 동안 뽑은 카드 1장당 피해량이 1 증가합니다.",
|
"desc": "피해를 1 줍니다. 이번 전투 동안 뽑은 카드 1장당 피해량이 1 증가합니다.",
|
||||||
"damage": 1,
|
"damage": 1,
|
||||||
|
"damagePerCardDrawnThisCombat": 1,
|
||||||
"image": "b1360ed0c4b942309d240634b8f36872"
|
"image": "b1360ed0c4b942309d240634b8f36872"
|
||||||
},
|
},
|
||||||
"Malaise": {
|
"Malaise": {
|
||||||
@@ -1084,7 +1089,8 @@
|
|||||||
"class": "bandit",
|
"class": "bandit",
|
||||||
"rarity": "legend",
|
"rarity": "legend",
|
||||||
"desc": "적이 힘을 X 잃습니다. 약화를 X 부여합니다. 소멸.",
|
"desc": "적이 힘을 X 잃습니다. 약화를 X 부여합니다. 소멸.",
|
||||||
"weak": 3,
|
"useAllEnergy": true,
|
||||||
|
"xWeakPerEnergy": 1,
|
||||||
"image": "0946f69d84464df29b24b94c744c868d"
|
"image": "0946f69d84464df29b24b94c744c868d"
|
||||||
},
|
},
|
||||||
"Adrenaline": {
|
"Adrenaline": {
|
||||||
@@ -1127,7 +1133,7 @@
|
|||||||
"class": "bandit",
|
"class": "bandit",
|
||||||
"rarity": "legend",
|
"rarity": "legend",
|
||||||
"desc": "이번 턴 동안 얻는 방어도가 2배가 됩니다.",
|
"desc": "이번 턴 동안 얻는 방어도가 2배가 됩니다.",
|
||||||
"draw": 1,
|
"blockGainMultiplier": 2,
|
||||||
"image": "0946f69d84464df29b24b94c744c868d"
|
"image": "0946f69d84464df29b24b94c744c868d"
|
||||||
},
|
},
|
||||||
"CorrosiveWave": {
|
"CorrosiveWave": {
|
||||||
@@ -1177,8 +1183,8 @@
|
|||||||
"class": "bandit",
|
"class": "bandit",
|
||||||
"rarity": "legend",
|
"rarity": "legend",
|
||||||
"desc": "이번 턴 동안 더 이상 카드를 뽑을 수 없습니다. 이번 턴 동안 손에 있는 모든 카드를 비용 없이 사용할 수 있습니다.",
|
"desc": "이번 턴 동안 더 이상 카드를 뽑을 수 없습니다. 이번 턴 동안 손에 있는 모든 카드를 비용 없이 사용할 수 있습니다.",
|
||||||
"powerEffect": "energyPerTurn",
|
"handCostZeroThisTurn": true,
|
||||||
"value": 1,
|
"drawDisabledThisTurn": true,
|
||||||
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
|
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
|
||||||
},
|
},
|
||||||
"Nightmare": {
|
"Nightmare": {
|
||||||
@@ -1232,9 +1238,7 @@
|
|||||||
"class": "bandit",
|
"class": "bandit",
|
||||||
"rarity": "legend",
|
"rarity": "legend",
|
||||||
"desc": "공격 카드가 막히지 않은 피해를 줄 때마다, 중독을 1 부여합니다.",
|
"desc": "공격 카드가 막히지 않은 피해를 줄 때마다, 중독을 1 부여합니다.",
|
||||||
"poison": 1,
|
"attackPoison": 1,
|
||||||
"powerEffect": "strengthPerTurn",
|
|
||||||
"value": 1,
|
|
||||||
"image": "19361e72087946b1888684185b40d935"
|
"image": "19361e72087946b1888684185b40d935"
|
||||||
},
|
},
|
||||||
"MasterPlanner": {
|
"MasterPlanner": {
|
||||||
@@ -1278,9 +1282,7 @@
|
|||||||
"class": "bandit",
|
"class": "bandit",
|
||||||
"rarity": "legend",
|
"rarity": "legend",
|
||||||
"desc": "카드를 사용할 때마다, 무작위 적에게 피해를 4 줍니다.",
|
"desc": "카드를 사용할 때마다, 무작위 적에게 피해를 4 줍니다.",
|
||||||
"powerEffect": "strengthPerTurn",
|
"cardPlayedRandomDamage": 4,
|
||||||
"value": 1,
|
|
||||||
"damage": 4,
|
|
||||||
"image": "19361e72087946b1888684185b40d935"
|
"image": "19361e72087946b1888684185b40d935"
|
||||||
},
|
},
|
||||||
"Abrasive": {
|
"Abrasive": {
|
||||||
@@ -1314,8 +1316,8 @@
|
|||||||
"class": "bandit",
|
"class": "bandit",
|
||||||
"rarity": "legend",
|
"rarity": "legend",
|
||||||
"desc": "불가침을 2 얻습니다. 내 턴 종료 시 민첩을 1 잃습니다.",
|
"desc": "불가침을 2 얻습니다. 내 턴 종료 시 민첩을 1 잃습니다.",
|
||||||
"powerEffect": "blockPerTurn",
|
"intangible": 2,
|
||||||
"value": 8,
|
"endTurnDexLoss": 1,
|
||||||
"image": "0946f69d84464df29b24b94c744c868d"
|
"image": "0946f69d84464df29b24b94c744c868d"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
10
docs/attack-poison.md
Normal file
10
docs/attack-poison.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# 공격 적중 독
|
||||||
|
|
||||||
|
`attackPoison`은 전투 중 파워가 들고 있는 공용 필드입니다.
|
||||||
|
|
||||||
|
동작:
|
||||||
|
|
||||||
|
- 공격 카드가 실제 피해를 주면 독을 부여합니다.
|
||||||
|
- `aoe` 공격이면 모든 적에게 같은 양의 독을 붙입니다.
|
||||||
|
- `Envenom` 같은 카드가 이 필드를 사용합니다.
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
## 구현됨
|
## 구현됨
|
||||||
|
|
||||||
`Neutralize`, `SilentStrike`, `Survivor`, `SilentDefend`, `Slice`, `DaggerSpray`, `DaggerThrow`, `PoisonedStab`, `SuckerPunch`, `LeadingStrike`, `FollowThrough`, `FlickFlack`, `Prepared`, `Deflect`, `BladeDance`, `Backflip`, `DodgeAndRoll`, `CloakAndDagger`, `DeadlyPoison`, `Snakebite`, `Untouchable`, `Backstab`, `PreciseCut`, `Finisher`, `MementoMori`, `Flechettes`, `Dash`, `Predator`, `CalculatedGamble`, `HiddenDaggers`, `Acrobatics`, `Blur`, `LegSweep`, `Reflex`, `Haze`, `Tactician`, `WellLaidPlans`, `InfiniteBlades`, `Footwork`, `GrandFinale`, `Adrenaline`, `ShadowStep`, `Assassinate`, `Nightmare`, `ToolsOfTheTrade`, `Afterimage`, `StormOfSteel`, `Abrasive`, `Suppress`, `Expertise`
|
`Neutralize`, `SilentStrike`, `Survivor`, `SilentDefend`, `Slice`, `DaggerSpray`, `DaggerThrow`, `PoisonedStab`, `SuckerPunch`, `LeadingStrike`, `FollowThrough`, `FlickFlack`, `Prepared`, `Deflect`, `BladeDance`, `Backflip`, `DodgeAndRoll`, `CloakAndDagger`, `DeadlyPoison`, `Snakebite`, `Untouchable`, `Backstab`, `PreciseCut`, `Finisher`, `MementoMori`, `Flechettes`, `Dash`, `Predator`, `CalculatedGamble`, `HiddenDaggers`, `Acrobatics`, `Blur`, `LegSweep`, `Reflex`, `Haze`, `Tactician`, `WellLaidPlans`, `InfiniteBlades`, `Footwork`, `GrandFinale`, `Adrenaline`, `ShadowStep`, `Assassinate`, `Nightmare`, `ToolsOfTheTrade`, `Afterimage`, `StormOfSteel`, `Abrasive`, `Suppress`, `Expertise`, `Shadowmeld`, `Pounce`, `Pinpoint`
|
||||||
|
|
||||||
공용 메모:
|
공용 메모:
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
- `turnStartDraw`, `turnStartDiscard` 구현됨
|
- `turnStartDraw`, `turnStartDiscard` 구현됨
|
||||||
- `nextTurnBlock`, `nextTurnDraw`, `nextTurnKeepBlock`, `nextTurnAttackMultiplier`, `nextTurnCopies`, `nextTurnSelectHandCard` 구현됨
|
- `nextTurnBlock`, `nextTurnDraw`, `nextTurnKeepBlock`, `nextTurnAttackMultiplier`, `nextTurnCopies`, `nextTurnSelectHandCard` 구현됨
|
||||||
- `damagePerOtherHandCard`, `damagePerAttackPlayedThisTurn`, `damagePerDiscardedThisTurn`, `damagePerSkillInHand`, `otherHandAtLeast`, `bonusHitsWhenOtherHandAtLeast` 구현됨
|
- `damagePerOtherHandCard`, `damagePerAttackPlayedThisTurn`, `damagePerDiscardedThisTurn`, `damagePerSkillInHand`, `otherHandAtLeast`, `bonusHitsWhenOtherHandAtLeast` 구현됨
|
||||||
- `gainEnergy`, `drawUntilHandSize`, `drawPerDiscarded`, `cardPlayedBlock` 구현됨
|
- `gainEnergy`, `drawUntilHandSize`, `drawPerDiscarded`, `cardPlayedBlock`, `blockGainMultiplier`, `nextSkillCostZero`, `skillCostReductionThisTurn` 구현됨
|
||||||
|
|
||||||
## 부분구현
|
## 부분구현
|
||||||
|
|
||||||
@@ -43,10 +43,6 @@
|
|||||||
|
|
||||||
`Strangle`: 이번 턴 카드 사용마다 추가 피해
|
`Strangle`: 이번 턴 카드 사용마다 추가 피해
|
||||||
|
|
||||||
`Pounce`: 다음 스킬 카드 비용 0
|
|
||||||
|
|
||||||
`Pinpoint`: 이번 턴 스킬 사용 시 비용 감소
|
|
||||||
|
|
||||||
`EscapePlan`: 드로우한 카드가 스킬이면 방어도 3
|
`EscapePlan`: 드로우한 카드가 스킬이면 방어도 3
|
||||||
|
|
||||||
`HandTrick`: 손패의 스킬 카드 하나에 교활 부여
|
`HandTrick`: 손패의 스킬 카드 하나에 교활 부여
|
||||||
@@ -71,7 +67,7 @@
|
|||||||
|
|
||||||
`Malaise`: X코스트 약화/피해 감소
|
`Malaise`: X코스트 약화/피해 감소
|
||||||
|
|
||||||
`Shadowmeld`: 이번 턴 얻는 방어도 2배
|
`Pinpoint`: 이번 턴 스킬 비용 감소
|
||||||
|
|
||||||
`CorrosiveWave`: 드로우할 때마다 독
|
`CorrosiveWave`: 드로우할 때마다 독
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
- `block`: 방어도 획득
|
- `block`: 방어도 획득
|
||||||
- `cardPlayedBlock`: 카드를 사용할 때마다 방어도 획득
|
- `cardPlayedBlock`: 카드를 사용할 때마다 방어도 획득
|
||||||
|
- `blockGainMultiplier`: 이번 턴 동안 얻는 방어도 배수
|
||||||
- `hits`: 다단히트 횟수
|
- `hits`: 다단히트 횟수
|
||||||
- `aoe`: 모든 적 대상
|
- `aoe`: 모든 적 대상
|
||||||
- `pierce`: 방어도 무시
|
- `pierce`: 방어도 무시
|
||||||
@@ -65,6 +66,8 @@
|
|||||||
- `nextTurnCopies`: 다음 턴에 손패에서 가져올 복사본 수
|
- `nextTurnCopies`: 다음 턴에 손패에서 가져올 복사본 수
|
||||||
- `nextTurnSelectHandCard`: 현재 손패에서 카드 1장 선택
|
- `nextTurnSelectHandCard`: 현재 손패에서 카드 1장 선택
|
||||||
- `nextTurnSelectPrompt`: 선택 UI 문구
|
- `nextTurnSelectPrompt`: 선택 UI 문구
|
||||||
|
- `nextSkillCostZero`: 다음 스킬 카드 비용을 0으로 만듦
|
||||||
|
- `skillCostReductionThisTurn`: 이번 턴 스킬 카드 비용을 일정량 감소
|
||||||
|
|
||||||
## 기타
|
## 기타
|
||||||
|
|
||||||
|
|||||||
5
docs/card-play-damage.md
Normal file
5
docs/card-play-damage.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# 카드 사용 시 피해
|
||||||
|
|
||||||
|
`cardPlayedDamage`는 카드를 사용할 때마다 현재 대상에게 체력을 직접 깎는 공용 효과입니다. 방어도는 무시하고, 같은 필드를 다른 카드에도 그대로 붙여 재사용할 수 있습니다.
|
||||||
|
|
||||||
|
`cardPlayedRandomDamage`는 같은 시점에 살아 있는 적 하나를 랜덤으로 골라 체력을 직접 깎습니다. `Strangle`과 `SerpentForm` 같은 카드가 이 계열을 씁니다.
|
||||||
8
docs/draw-count.md
Normal file
8
docs/draw-count.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# 전투 드로우 누적
|
||||||
|
|
||||||
|
`damagePerCardDrawnThisCombat`은 이번 전투 동안 실제로 뽑힌 카드 수를 기준으로 공격력을 올리는 공용 필드입니다.
|
||||||
|
|
||||||
|
적용 예시:
|
||||||
|
|
||||||
|
- `Murder`: 이번 전투 동안 뽑은 카드 1장당 피해량이 1 증가
|
||||||
|
|
||||||
22
docs/draw-skill-block.md
Normal file
22
docs/draw-skill-block.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# 드로우 연동 효과
|
||||||
|
|
||||||
|
드로우 결과를 받아 후속 효과를 처리하는 공용 패턴을 정리합니다.
|
||||||
|
|
||||||
|
## 현재 구현
|
||||||
|
|
||||||
|
- `draw`: 카드를 뽑음
|
||||||
|
- `drawUntilHandSize`: 손패가 지정 수치가 될 때까지 뽑음
|
||||||
|
- `drawSkillBlock`: 이번 카드로 뽑힌 카드 중 스킬 카드마다 방어도를 얻음
|
||||||
|
|
||||||
|
## 동작 방식
|
||||||
|
|
||||||
|
- 드로우 함수는 이번에 뽑힌 카드 ID 목록을 반환합니다.
|
||||||
|
- 카드 효과는 그 목록을 보고 조건을 판정합니다.
|
||||||
|
- 그래서 `EscapePlan` 같은 카드뿐 아니라, 나중에 같은 규칙이 필요한 카드에도 같은 필드를 붙이면 됩니다.
|
||||||
|
|
||||||
|
## 예시
|
||||||
|
|
||||||
|
- `EscapePlan`
|
||||||
|
- `draw = 1`
|
||||||
|
- `drawSkillBlock = 3`
|
||||||
|
|
||||||
5
docs/intangible.md
Normal file
5
docs/intangible.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# 불가침
|
||||||
|
|
||||||
|
`intangible`는 카드를 사용할 때 플레이어에게 불가침 수치를 부여하는 공용 필드입니다. 불가침이 남아 있는 동안 받는 피해는 1로 줄어들고, 턴이 끝날 때 1씩 감소합니다.
|
||||||
|
|
||||||
|
`endTurnDexLoss`는 그 카드가 활성화된 동안 매 턴 종료 시 민첩을 잃게 만드는 공용 필드입니다. `WraithForm` 같은 카드가 이 조합을 사용합니다.
|
||||||
14
docs/x-cost.md
Normal file
14
docs/x-cost.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# X 코스트 카드
|
||||||
|
|
||||||
|
`useAllEnergy`는 카드가 사용될 때 남은 에너지를 전부 쓰는 공용 필드입니다.
|
||||||
|
|
||||||
|
연동 필드:
|
||||||
|
|
||||||
|
- `xDamagePerEnergy`: 에너지 1당 피해량
|
||||||
|
- `xWeakPerEnergy`: 에너지 1당 약화량
|
||||||
|
|
||||||
|
적용 예시:
|
||||||
|
|
||||||
|
- `Skewer`: 남은 에너지 전부를 써서 `8 * energy` 피해
|
||||||
|
- `Malaise`: 남은 에너지 전부를 써서 약화 부여
|
||||||
|
|
||||||
@@ -90,12 +90,33 @@ function canPlayCardNow(card, ctx = {}) {
|
|||||||
// 이며, Lua에 대응 AI가 없다(동기화 대상은 데미지/방어/의도/승패 규칙이지 플레이어 선택이 아님).
|
// 이며, Lua에 대응 AI가 없다(동기화 대상은 데미지/방어/의도/승패 규칙이지 플레이어 선택이 아님).
|
||||||
// 손패에서 낼 카드 인덱스(-1=종료). 파워 우선(지속 가치) → 공격 → 스킬.
|
// 손패에서 낼 카드 인덱스(-1=종료). 파워 우선(지속 가치) → 공격 → 스킬.
|
||||||
export function chooseAction(hand, cards, energy, ctx = {}) {
|
export function chooseAction(hand, cards, energy, ctx = {}) {
|
||||||
const entries = hand.map((id, i) => ({ id, i })).filter((x) => cards[x.id] && cards[x.id].cost <= energy && !cards[x.id].unplayable && canPlayCardNow(cards[x.id], ctx));
|
const entries = hand.map((id, i) => ({ id, i })).filter((x) => {
|
||||||
|
const card = cards[x.id];
|
||||||
|
if (!card || card.unplayable || !canPlayCardNow(card, ctx)) return false;
|
||||||
|
let effectiveCost = card.cost || 0;
|
||||||
|
if (ctx.handCostZeroThisTurn === true) effectiveCost = 0;
|
||||||
|
else if (card.useAllEnergy === true) effectiveCost = 1;
|
||||||
|
else if (card.kind === 'Skill') {
|
||||||
|
if (ctx.nextSkillCostZero === true) effectiveCost = 0;
|
||||||
|
else effectiveCost = Math.max(0, effectiveCost - (ctx.skillCostReductionThisTurn || 0));
|
||||||
|
}
|
||||||
|
return card.useAllEnergy === true ? true : effectiveCost <= energy;
|
||||||
|
});
|
||||||
const powers = entries.filter((x) => cards[x.id].kind === 'Power');
|
const powers = entries.filter((x) => cards[x.id].kind === 'Power');
|
||||||
const attacks = entries.filter((x) => cards[x.id].kind === 'Attack');
|
const attacks = entries.filter((x) => cards[x.id].kind === 'Attack');
|
||||||
const skills = entries.filter((x) => cards[x.id].kind === 'Skill');
|
const skills = entries.filter((x) => cards[x.id].kind === 'Skill');
|
||||||
const dmgEff = (x) => (cards[x.id].damage || 0) / Math.max(cards[x.id].cost, 1);
|
const effectiveCost = (card) => {
|
||||||
const blkEff = (x) => (cards[x.id].block || 0) / Math.max(cards[x.id].cost, 1);
|
let cost = card.cost || 0;
|
||||||
|
if (ctx.handCostZeroThisTurn === true) cost = 0;
|
||||||
|
else if (card.useAllEnergy === true) cost = 1;
|
||||||
|
else if (card.kind === 'Skill') {
|
||||||
|
if (ctx.nextSkillCostZero === true) cost = 0;
|
||||||
|
else cost = Math.max(0, cost - (ctx.skillCostReductionThisTurn || 0));
|
||||||
|
}
|
||||||
|
return cost;
|
||||||
|
};
|
||||||
|
const dmgEff = (x) => (cards[x.id].damage || 0) / Math.max(effectiveCost(cards[x.id]), 1);
|
||||||
|
const blkEff = (x) => (cards[x.id].block || 0) / Math.max(effectiveCost(cards[x.id]), 1);
|
||||||
const bestBy = (list, fn) => list.slice().sort((a, b) => fn(b) - fn(a))[0];
|
const bestBy = (list, fn) => list.slice().sort((a, b) => fn(b) - fn(a))[0];
|
||||||
if (powers.length) return powers[0].i;
|
if (powers.length) return powers[0].i;
|
||||||
if (attacks.length) return bestBy(attacks, dmgEff).i;
|
if (attacks.length) return bestBy(attacks, dmgEff).i;
|
||||||
@@ -127,11 +148,17 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
const exhaust = [];
|
const exhaust = [];
|
||||||
let hand = [];
|
let hand = [];
|
||||||
let pHp = PLAYER_HP, pBlock = 0;
|
let pHp = PLAYER_HP, pBlock = 0;
|
||||||
let pStr = 0, pDex = 0, pThorns = 0, pWeak = 0, pVuln = 0;
|
let pStr = 0, pDex = 0, pThorns = 0, pWeak = 0, pVuln = 0, pIntangible = 0;
|
||||||
|
let blockGainMultiplier = 1;
|
||||||
|
let handCostZeroThisTurn = false;
|
||||||
|
let drawDisabledThisTurn = false;
|
||||||
|
let nextSkillCostZero = false;
|
||||||
|
let skillCostReductionThisTurn = 0;
|
||||||
let nextTurnBlock = 0, nextTurnDraw = 0, nextTurnKeepBlock = false;
|
let nextTurnBlock = 0, nextTurnDraw = 0, nextTurnKeepBlock = false;
|
||||||
let nextTurnAttackMultiplier = 1, turnAttackMultiplier = 1;
|
let nextTurnAttackMultiplier = 1, turnAttackMultiplier = 1;
|
||||||
let nextTurnAddCards = [];
|
let nextTurnAddCards = [];
|
||||||
let turnAttackCardsPlayed = 0, turnDiscardedCards = 0;
|
let turnAttackCardsPlayed = 0, turnDiscardedCards = 0;
|
||||||
|
let cardsDrawnThisCombat = 0;
|
||||||
let energy = 0;
|
let energy = 0;
|
||||||
const powers = [];
|
const powers = [];
|
||||||
const mob = monsters.map((m) => ({
|
const mob = monsters.map((m) => ({
|
||||||
@@ -141,16 +168,21 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
let turns = 0;
|
let turns = 0;
|
||||||
|
|
||||||
function draw(n) {
|
function draw(n) {
|
||||||
|
const drawn = [];
|
||||||
|
if (drawDisabledThisTurn === true) return drawn;
|
||||||
for (let k = 0; k < n; k++) {
|
for (let k = 0; k < n; k++) {
|
||||||
if (drawPile.length === 0) { drawPile = shuffle(discard, rng); discard = []; }
|
if (drawPile.length === 0) { drawPile = shuffle(discard, rng); discard = []; }
|
||||||
if (drawPile.length === 0) break;
|
if (drawPile.length === 0) break;
|
||||||
const card = drawPile.pop();
|
const card = drawPile.pop();
|
||||||
|
drawn.push(card);
|
||||||
|
cardsDrawnThisCombat++;
|
||||||
// 손패 10장 상한 — 초과 드로는 자동 버림 (Lua DrawCards 동기화)
|
// 손패 10장 상한 — 초과 드로는 자동 버림 (Lua DrawCards 동기화)
|
||||||
if (hand.length >= 10) {
|
if (hand.length >= 10) {
|
||||||
discard.push(card);
|
discard.push(card);
|
||||||
triggerSly(card);
|
triggerSly(card);
|
||||||
} else hand.push(card);
|
} else hand.push(card);
|
||||||
}
|
}
|
||||||
|
return drawn;
|
||||||
}
|
}
|
||||||
function addCardsToHand(id, n) {
|
function addCardsToHand(id, n) {
|
||||||
for (let k = 0; k < n; k++) {
|
for (let k = 0; k < n; k++) {
|
||||||
@@ -161,6 +193,7 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
function addBlock(base) {
|
function addBlock(base) {
|
||||||
let amount = base || 0;
|
let amount = base || 0;
|
||||||
if (amount > 0) amount += pDex;
|
if (amount > 0) amount += pDex;
|
||||||
|
if (blockGainMultiplier > 1) amount *= blockGainMultiplier;
|
||||||
if (amount < 0) amount = 0;
|
if (amount < 0) amount = 0;
|
||||||
pBlock += amount;
|
pBlock += amount;
|
||||||
return amount;
|
return amount;
|
||||||
@@ -199,6 +232,7 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
if (c.damagePerAttackPlayedThisTurn) base += turnAttackCardsPlayed * c.damagePerAttackPlayedThisTurn;
|
if (c.damagePerAttackPlayedThisTurn) base += turnAttackCardsPlayed * c.damagePerAttackPlayedThisTurn;
|
||||||
if (c.damagePerDiscardedThisTurn) base += turnDiscardedCards * c.damagePerDiscardedThisTurn;
|
if (c.damagePerDiscardedThisTurn) base += turnDiscardedCards * c.damagePerDiscardedThisTurn;
|
||||||
if (c.damagePerSkillInHand) base += countOtherHandSkills(id) * c.damagePerSkillInHand;
|
if (c.damagePerSkillInHand) base += countOtherHandSkills(id) * c.damagePerSkillInHand;
|
||||||
|
if (c.damagePerCardDrawnThisCombat) base += cardsDrawnThisCombat * c.damagePerCardDrawnThisCombat;
|
||||||
if (base < 0) base = 0;
|
if (base < 0) base = 0;
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
@@ -243,9 +277,15 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
const alive = aliveList();
|
const alive = aliveList();
|
||||||
let dmg = 0;
|
let dmg = 0;
|
||||||
let blockGained = 0;
|
let blockGained = 0;
|
||||||
|
if (c.blockGainMultiplier && c.blockGainMultiplier > 0) blockGainMultiplier *= c.blockGainMultiplier;
|
||||||
|
if (c.nextSkillCostZero === true) nextSkillCostZero = true;
|
||||||
|
if (c.skillCostReductionThisTurn && c.skillCostReductionThisTurn > 0) skillCostReductionThisTurn += c.skillCostReductionThisTurn;
|
||||||
|
if (c.handCostZeroThisTurn === true) handCostZeroThisTurn = true;
|
||||||
|
if (c.drawDisabledThisTurn === true) drawDisabledThisTurn = true;
|
||||||
|
const xEnergy = costSpent || 0;
|
||||||
if (c.kind === 'Attack') {
|
if (c.kind === 'Attack') {
|
||||||
if (alive.length && c.damage) {
|
if (alive.length && (c.damage || c.xDamagePerEnergy)) {
|
||||||
const baseDamage = attackBaseForCard(id, c);
|
const baseDamage = c.xDamagePerEnergy ? xEnergy * c.xDamagePerEnergy : attackBaseForCard(id, c);
|
||||||
const bonusHits = (c.otherHandAtLeast && c.bonusHitsWhenOtherHandAtLeast && Math.max(0, hand.length - 1) >= c.otherHandAtLeast)
|
const bonusHits = (c.otherHandAtLeast && c.bonusHitsWhenOtherHandAtLeast && Math.max(0, hand.length - 1) >= c.otherHandAtLeast)
|
||||||
? c.bonusHitsWhenOtherHandAtLeast : 0;
|
? c.bonusHitsWhenOtherHandAtLeast : 0;
|
||||||
const hitN = (c.hits || 1) + bonusHits;
|
const hitN = (c.hits || 1) + bonusHits;
|
||||||
@@ -261,6 +301,8 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
const d2 = m2.vuln > 0 ? Math.floor(totalNv * 1.5) : totalNv;
|
const d2 = m2.vuln > 0 ? Math.floor(totalNv * 1.5) : totalNv;
|
||||||
const r2 = applyDamage(m2.hp, m2.block, d2);
|
const r2 = applyDamage(m2.hp, m2.block, d2);
|
||||||
m2.hp = r2.hp; m2.block = r2.block;
|
m2.hp = r2.hp; m2.block = r2.block;
|
||||||
|
const attackPoison = powerFieldTotal('attackPoison');
|
||||||
|
if (d2 > 0 && attackPoison > 0) m2.poison += attackPoison;
|
||||||
if (m2.hp <= 0) m2.alive = false;
|
if (m2.hp <= 0) m2.alive = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -272,6 +314,8 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
const r = applyDamage(target.hp, target.block, dmg);
|
const r = applyDamage(target.hp, target.block, dmg);
|
||||||
target.hp = r.hp; target.block = r.block;
|
target.hp = r.hp; target.block = r.block;
|
||||||
}
|
}
|
||||||
|
const attackPoison = powerFieldTotal('attackPoison');
|
||||||
|
if (dmg > 0 && attackPoison > 0) target.poison += attackPoison;
|
||||||
if (target.hp <= 0) target.alive = false;
|
if (target.hp <= 0) target.alive = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,9 +324,10 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
if (recordStats) powers.push(id);
|
if (recordStats) powers.push(id);
|
||||||
} else {
|
} else {
|
||||||
if (c.block) blockGained = addBlock(c.block);
|
if (c.block) blockGained = addBlock(c.block);
|
||||||
if ((c.weak || c.vuln || c.poison) && alive.length) {
|
const weakAmount = (c.weak || 0) + (c.xWeakPerEnergy || 0) * xEnergy;
|
||||||
|
if ((weakAmount || c.vuln || c.poison) && alive.length) {
|
||||||
const target = chooseTarget(alive, 0);
|
const target = chooseTarget(alive, 0);
|
||||||
if (c.weak) target.weak += c.weak;
|
if (weakAmount) target.weak += weakAmount;
|
||||||
if (c.vuln) target.vuln += c.vuln;
|
if (c.vuln) target.vuln += c.vuln;
|
||||||
if (c.poison) target.poison += c.poison;
|
if (c.poison) target.poison += c.poison;
|
||||||
}
|
}
|
||||||
@@ -293,13 +338,39 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
if (c.selfVuln) pVuln += c.selfVuln;
|
if (c.selfVuln) pVuln += c.selfVuln;
|
||||||
if (c.heal) pHp = Math.min(pHp + c.heal, PLAYER_HP);
|
if (c.heal) pHp = Math.min(pHp + c.heal, PLAYER_HP);
|
||||||
if (c.gainEnergy) energy += c.gainEnergy;
|
if (c.gainEnergy) energy += c.gainEnergy;
|
||||||
|
if (c.intangible) pIntangible += c.intangible;
|
||||||
queueNextTurnEffects(c);
|
queueNextTurnEffects(c);
|
||||||
if (c.draw) draw(c.draw);
|
let drawnCards = [];
|
||||||
|
if (c.draw) drawnCards = drawnCards.concat(draw(c.draw));
|
||||||
if (c.drawUntilHandSize) {
|
if (c.drawUntilHandSize) {
|
||||||
const need = c.drawUntilHandSize - Math.max(0, hand.length - 1);
|
const need = c.drawUntilHandSize - Math.max(0, hand.length - 1);
|
||||||
if (need > 0) draw(need);
|
if (need > 0) drawnCards = drawnCards.concat(draw(need));
|
||||||
|
}
|
||||||
|
if (c.drawSkillBlock && c.drawSkillBlock > 0) {
|
||||||
|
for (const drawnId of drawnCards) {
|
||||||
|
if (cards[drawnId]?.kind === 'Skill') blockGained += addBlock(c.drawSkillBlock);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (c.addShiv && !c.discard && c.discardAll !== true) addCardsToHand('Shiv', c.addShiv);
|
if (c.addShiv && !c.discard && c.discardAll !== true) addCardsToHand('Shiv', c.addShiv);
|
||||||
|
if (c.cardPlayedDamage && alive.length) {
|
||||||
|
const target = chooseTarget(aliveList(), 0);
|
||||||
|
if (target && target.alive) {
|
||||||
|
target.hp -= c.cardPlayedDamage;
|
||||||
|
dmg += c.cardPlayedDamage;
|
||||||
|
if (target.hp <= 0) target.alive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (c.cardPlayedRandomDamage && alive.length) {
|
||||||
|
const pool = aliveList();
|
||||||
|
if (pool.length) {
|
||||||
|
const target = pool[Math.floor(rng() * pool.length)];
|
||||||
|
if (target) {
|
||||||
|
target.hp -= c.cardPlayedRandomDamage;
|
||||||
|
dmg += c.cardPlayedRandomDamage;
|
||||||
|
if (target.hp <= 0) target.alive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (recordStats && stats) stats[id] = bump(stats[id], costSpent, dmg, blockGained);
|
if (recordStats && stats) stats[id] = bump(stats[id], costSpent, dmg, blockGained);
|
||||||
}
|
}
|
||||||
function triggerSly(id) {
|
function triggerSly(id) {
|
||||||
@@ -331,6 +402,10 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
turns++;
|
turns++;
|
||||||
turnAttackCardsPlayed = 0;
|
turnAttackCardsPlayed = 0;
|
||||||
turnDiscardedCards = 0;
|
turnDiscardedCards = 0;
|
||||||
|
blockGainMultiplier = 1;
|
||||||
|
handCostZeroThisTurn = false;
|
||||||
|
drawDisabledThisTurn = false;
|
||||||
|
skillCostReductionThisTurn = 0;
|
||||||
// 파워 발동 — Lua StartPlayerTurn 동기화 (블록 리셋 후 strength/energy/block 파워)
|
// 파워 발동 — Lua StartPlayerTurn 동기화 (블록 리셋 후 strength/energy/block 파워)
|
||||||
if (nextTurnKeepBlock === true) nextTurnKeepBlock = false;
|
if (nextTurnKeepBlock === true) nextTurnKeepBlock = false;
|
||||||
else pBlock = 0;
|
else pBlock = 0;
|
||||||
@@ -345,6 +420,16 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
if (pc.powerEffect === 'strengthPerTurn') pStr += pc.value;
|
if (pc.powerEffect === 'strengthPerTurn') pStr += pc.value;
|
||||||
else if (pc.powerEffect === 'energyPerTurn') energyBonus += pc.value;
|
else if (pc.powerEffect === 'energyPerTurn') energyBonus += pc.value;
|
||||||
else if (pc.powerEffect === 'blockPerTurn') pBlock += pc.value;
|
else if (pc.powerEffect === 'blockPerTurn') pBlock += pc.value;
|
||||||
|
else if (pc.powerEffect === 'poisonPerTurn') {
|
||||||
|
for (const m of mob) if (m.alive) m.poison += pc.value;
|
||||||
|
} else if (pc.powerEffect === 'damagePerTurn') {
|
||||||
|
for (const m of mob) {
|
||||||
|
if (!m.alive) continue;
|
||||||
|
const r = applyDamage(m.hp, m.block, pc.value || 0);
|
||||||
|
m.hp = r.hp; m.block = r.block;
|
||||||
|
if (m.hp <= 0) m.alive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (pc.turnStartShiv) addCardsToHand('Shiv', pc.turnStartShiv);
|
if (pc.turnStartShiv) addCardsToHand('Shiv', pc.turnStartShiv);
|
||||||
if (pc.turnStartDraw) powerTurnDraw += pc.turnStartDraw;
|
if (pc.turnStartDraw) powerTurnDraw += pc.turnStartDraw;
|
||||||
if (pc.turnStartDiscard) powerTurnDiscard += pc.turnStartDiscard;
|
if (pc.turnStartDiscard) powerTurnDiscard += pc.turnStartDiscard;
|
||||||
@@ -362,12 +447,16 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
while (true) {
|
while (true) {
|
||||||
const alive = aliveList();
|
const alive = aliveList();
|
||||||
if (alive.length === 0) break;
|
if (alive.length === 0) break;
|
||||||
const idx = chooseAction(hand, cards, energy, { drawPileCount: drawPile.length });
|
const idx = chooseAction(hand, cards, energy, { drawPileCount: drawPile.length, nextSkillCostZero, skillCostReductionThisTurn, handCostZeroThisTurn });
|
||||||
if (idx < 0) break;
|
if (idx < 0) break;
|
||||||
const id = hand[idx], c = cards[id];
|
const id = hand[idx], c = cards[id];
|
||||||
energy -= c.cost;
|
const skillFree = c.kind === 'Skill' && nextSkillCostZero === true;
|
||||||
resolveCardEffects(id, c, c.cost);
|
const baseCost = c.cost || 0;
|
||||||
|
const cost = handCostZeroThisTurn === true ? 0 : (c.useAllEnergy === true ? energy : (skillFree ? 0 : (c.kind === 'Skill' ? Math.max(0, baseCost - skillCostReductionThisTurn) : baseCost)));
|
||||||
|
energy -= cost;
|
||||||
|
resolveCardEffects(id, c, cost);
|
||||||
if (c.kind === 'Attack') turnAttackCardsPlayed++;
|
if (c.kind === 'Attack') turnAttackCardsPlayed++;
|
||||||
|
if (skillFree === true && c.nextSkillCostZero !== true) nextSkillCostZero = false;
|
||||||
const playedBlock = powerFieldTotal('cardPlayedBlock');
|
const playedBlock = powerFieldTotal('cardPlayedBlock');
|
||||||
if (playedBlock > 0) addBlock(playedBlock);
|
if (playedBlock > 0) addBlock(playedBlock);
|
||||||
hand.splice(idx, 1);
|
hand.splice(idx, 1);
|
||||||
@@ -388,6 +477,14 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
else discard.push(hid);
|
else discard.push(hid);
|
||||||
}
|
}
|
||||||
hand = kept;
|
hand = kept;
|
||||||
|
for (const pid of powers) {
|
||||||
|
const pc = cards[pid];
|
||||||
|
if (pc?.endTurnDexLoss) {
|
||||||
|
pDex -= pc.endTurnDexLoss;
|
||||||
|
if (pDex < 0) pDex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pIntangible > 0) pIntangible--;
|
||||||
if (pHp <= 0) return { win: false, turns, playerHpRemaining: 0 };
|
if (pHp <= 0) return { win: false, turns, playerHpRemaining: 0 };
|
||||||
// 플레이어 디버프 감소 — Lua EndPlayerTurn 동기화 (적 행동 전)
|
// 플레이어 디버프 감소 — Lua EndPlayerTurn 동기화 (적 행동 전)
|
||||||
if (pWeak > 0) pWeak--;
|
if (pWeak > 0) pWeak--;
|
||||||
@@ -407,7 +504,9 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
if (it.kind === 'Attack') {
|
if (it.kind === 'Attack') {
|
||||||
const atk = calcAttack(it.value, m.str, m.weak, pVuln);
|
const atk = calcAttack(it.value, m.str, m.weak, pVuln);
|
||||||
const beforeHp = pHp;
|
const beforeHp = pHp;
|
||||||
const r = applyDamage(pHp, pBlock, atk); pHp = r.hp; pBlock = r.block;
|
let incoming = atk;
|
||||||
|
if (pIntangible > 0 && incoming > 1) incoming = 1;
|
||||||
|
const r = applyDamage(pHp, pBlock, incoming); pHp = r.hp; pBlock = r.block;
|
||||||
if (beforeHp > pHp && pThorns > 0) {
|
if (beforeHp > pHp && pThorns > 0) {
|
||||||
m.hp -= pThorns;
|
m.hp -= pThorns;
|
||||||
if (m.hp <= 0) m.alive = false;
|
if (m.hp <= 0) m.alive = false;
|
||||||
|
|||||||
@@ -684,3 +684,198 @@ test("simulateCombat: cardPlayedBlock grants block whenever a card is played", (
|
|||||||
assert.equal(r.draw, true);
|
assert.equal(r.draw, true);
|
||||||
assert.equal(r.playerHpRemaining, 80);
|
assert.equal(r.playerHpRemaining, 80);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("simulateCombat: blockGainMultiplier doubles block gain for the turn", () => {
|
||||||
|
const data = {
|
||||||
|
cards: {
|
||||||
|
Shadow: { name: "Shadowmeld", cost: 1, kind: "Skill", block: 5, blockGainMultiplier: 2 },
|
||||||
|
Shield: { name: "Shield", cost: 1, kind: "Skill", block: 2 },
|
||||||
|
},
|
||||||
|
starterDeck: ["Shadow", "Shield"],
|
||||||
|
monsters: [{ name: "Dummy", maxHp: 9999, intents: [{ kind: "Attack", value: 8 }] }],
|
||||||
|
};
|
||||||
|
const r = simulateCombat(data, () => 0.999999);
|
||||||
|
assert.equal(r.draw, true);
|
||||||
|
assert.equal(r.playerHpRemaining, 80);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("simulateCombat: nextSkillCostZero makes the next skill free", () => {
|
||||||
|
const data = {
|
||||||
|
cards: {
|
||||||
|
Pounce: { name: "Pounce", cost: 2, kind: "Attack", damage: 12, nextSkillCostZero: true },
|
||||||
|
Guard: { name: "Guard", cost: 2, kind: "Skill", block: 8 },
|
||||||
|
},
|
||||||
|
starterDeck: ["Pounce", "Guard"],
|
||||||
|
monsters: [{ name: "Dummy", maxHp: 9999, intents: [{ kind: "Attack", value: 8 }] }],
|
||||||
|
};
|
||||||
|
const r = simulateCombat(data, () => 0.999999);
|
||||||
|
assert.equal(r.draw, true);
|
||||||
|
assert.equal(r.playerHpRemaining, 80);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("chooseAction: skillCostReductionThisTurn allows discounted skills", () => {
|
||||||
|
const cards = {
|
||||||
|
Guard: { name: "Guard", cost: 2, kind: "Skill", block: 8 },
|
||||||
|
};
|
||||||
|
assert.equal(chooseAction(["Guard"], cards, 1, { skillCostReductionThisTurn: 1 }), 0);
|
||||||
|
assert.equal(chooseAction(["Guard"], cards, 1, {}), -1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("chooseAction: handCostZeroThisTurn lets expensive cards be played", () => {
|
||||||
|
const cards = {
|
||||||
|
Burst: { name: "Burst", cost: 3, kind: "Skill", block: 8 },
|
||||||
|
};
|
||||||
|
assert.equal(chooseAction(["Burst"], cards, 0, { handCostZeroThisTurn: true }), 0);
|
||||||
|
assert.equal(chooseAction(["Burst"], cards, 0, {}), -1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("chooseAction: useAllEnergy cards remain playable at zero energy", () => {
|
||||||
|
const cards = {
|
||||||
|
Skewer: { name: "Skewer", cost: 2, kind: "Attack", useAllEnergy: true, xDamagePerEnergy: 8 },
|
||||||
|
};
|
||||||
|
assert.equal(chooseAction(["Skewer"], cards, 0, {}), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("simulateCombat: drawSkillBlock grants block for each drawn skill", () => {
|
||||||
|
const data = {
|
||||||
|
cards: {
|
||||||
|
Escape: { name: "EscapePlan", cost: 0, kind: "Skill", draw: 1, drawSkillBlock: 3, innate: true, exhaust: true },
|
||||||
|
Filler1: { name: "Filler1", cost: 99, kind: "Skill", block: 0 },
|
||||||
|
Filler2: { name: "Filler2", cost: 99, kind: "Skill", block: 0 },
|
||||||
|
Filler3: { name: "Filler3", cost: 99, kind: "Skill", block: 0 },
|
||||||
|
Filler4: { name: "Filler4", cost: 99, kind: "Skill", block: 0 },
|
||||||
|
Filler5: { name: "Filler5", cost: 99, kind: "Skill", block: 0 },
|
||||||
|
},
|
||||||
|
starterDeck: ["Escape", "Filler1", "Filler2", "Filler3", "Filler4", "Filler5"],
|
||||||
|
monsters: [{ name: "Dummy", maxHp: 9999, intents: [{ kind: "Attack", value: 0 }] }],
|
||||||
|
};
|
||||||
|
const stats = {};
|
||||||
|
const r = simulateCombat(data, () => 0.999999, stats);
|
||||||
|
assert.equal(r.draw, true);
|
||||||
|
assert.equal(stats.Escape.block, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("simulateCombat: poisonPerTurn powers poison all enemies at turn start", () => {
|
||||||
|
const data = {
|
||||||
|
cards: {
|
||||||
|
Fumes: { name: "NoxiousFumes", cost: 1, kind: "Power", powerEffect: "poisonPerTurn", value: 2 },
|
||||||
|
},
|
||||||
|
starterDeck: ["Fumes"],
|
||||||
|
monsters: [
|
||||||
|
{ name: "DummyA", maxHp: 2, intents: [{ kind: "Attack", value: 0 }] },
|
||||||
|
{ name: "DummyB", maxHp: 2, intents: [{ kind: "Attack", value: 0 }] },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const r = simulateCombat(data, () => 0.999999);
|
||||||
|
assert.equal(r.win, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("simulateCombat: damagePerTurn powers damage all enemies at turn start", () => {
|
||||||
|
const data = {
|
||||||
|
cards: {
|
||||||
|
Speed: { name: "Speedster", cost: 2, kind: "Power", powerEffect: "damagePerTurn", value: 2 },
|
||||||
|
},
|
||||||
|
starterDeck: ["Speed"],
|
||||||
|
monsters: [
|
||||||
|
{ name: "DummyA", maxHp: 2, intents: [{ kind: "Attack", value: 0 }] },
|
||||||
|
{ name: "DummyB", maxHp: 2, intents: [{ kind: "Attack", value: 0 }] },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const r = simulateCombat(data, () => 0.999999);
|
||||||
|
assert.equal(r.win, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("simulateCombat: attackPoison power applies poison on attack damage", () => {
|
||||||
|
const data = {
|
||||||
|
cards: {
|
||||||
|
Venom: { name: "Envenom", cost: 2, kind: "Power", attackPoison: 2 },
|
||||||
|
Strike: { name: "Strike", cost: 1, kind: "Attack", damage: 1 },
|
||||||
|
},
|
||||||
|
starterDeck: ["Venom", "Strike"],
|
||||||
|
monsters: [{ name: "Dummy", maxHp: 3, intents: [{ kind: "Attack", value: 0 }] }],
|
||||||
|
};
|
||||||
|
const r = simulateCombat(data, () => 0.999999);
|
||||||
|
assert.equal(r.win, true);
|
||||||
|
assert.equal(r.turns, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("simulateCombat: cardPlayedDamage hits the target whenever a card is played", () => {
|
||||||
|
const data = {
|
||||||
|
cards: {
|
||||||
|
Strangle: { name: "Strangle", cost: 1, kind: "Attack", damage: 8, cardPlayedDamage: 2 },
|
||||||
|
},
|
||||||
|
starterDeck: ["Strangle"],
|
||||||
|
monsters: [{ name: "Dummy", maxHp: 10, intents: [{ kind: "Attack", value: 0 }] }],
|
||||||
|
};
|
||||||
|
const r = simulateCombat(data, () => 0.999999);
|
||||||
|
assert.equal(r.win, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("simulateCombat: cardPlayedRandomDamage hits a random enemy on card play", () => {
|
||||||
|
const data = {
|
||||||
|
cards: {
|
||||||
|
SerpentForm: { name: "SerpentForm", cost: 3, kind: "Power", cardPlayedRandomDamage: 4 },
|
||||||
|
},
|
||||||
|
starterDeck: ["SerpentForm"],
|
||||||
|
monsters: [{ name: "Dummy", maxHp: 4, intents: [{ kind: "Attack", value: 0 }] }],
|
||||||
|
};
|
||||||
|
const r = simulateCombat(data, () => 0.999999);
|
||||||
|
assert.equal(r.win, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("simulateCombat: intangible cards reduce incoming damage and persist across turns", () => {
|
||||||
|
const data = {
|
||||||
|
cards: {
|
||||||
|
Wraith: { name: "WraithForm", cost: 3, kind: "Power", intangible: 2, endTurnDexLoss: 1, innate: true },
|
||||||
|
Strike: { name: "Strike", cost: 1, kind: "Attack", damage: 1 },
|
||||||
|
},
|
||||||
|
starterDeck: ["Wraith", "Strike"],
|
||||||
|
monsters: [{ name: "Dummy", maxHp: 1, intents: [{ kind: "Attack", value: 10 }] }],
|
||||||
|
};
|
||||||
|
const r = simulateCombat(data, () => 0.999999);
|
||||||
|
assert.equal(r.win, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("simulateCombat: useAllEnergy skewer consumes all energy for damage", () => {
|
||||||
|
const data = {
|
||||||
|
cards: {
|
||||||
|
Skewer: { name: "Skewer", cost: 2, kind: "Attack", useAllEnergy: true, xDamagePerEnergy: 8 },
|
||||||
|
},
|
||||||
|
starterDeck: ["Skewer"],
|
||||||
|
monsters: [{ name: "Dummy", maxHp: 24, intents: [{ kind: "Attack", value: 0 }] }],
|
||||||
|
};
|
||||||
|
const r = simulateCombat(data, () => 0.999999);
|
||||||
|
assert.equal(r.win, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("simulateCombat: useAllEnergy malaise scales weak with energy spent", () => {
|
||||||
|
const data = {
|
||||||
|
cards: {
|
||||||
|
Malaise: { name: "Malaise", cost: 2, kind: "Skill", useAllEnergy: true, xWeakPerEnergy: 1 },
|
||||||
|
Strike: { name: "Strike", cost: 1, kind: "Attack", damage: 1 },
|
||||||
|
},
|
||||||
|
starterDeck: ["Malaise", "Strike"],
|
||||||
|
monsters: [{ name: "Dummy", maxHp: 1, intents: [{ kind: "Attack", value: 10 }] }],
|
||||||
|
};
|
||||||
|
const r = simulateCombat(data, () => 0.999999);
|
||||||
|
assert.equal(r.win, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("simulateCombat: damagePerCardDrawnThisCombat scales murder", () => {
|
||||||
|
const data = {
|
||||||
|
cards: {
|
||||||
|
Murder: { name: "Murder", cost: 3, kind: "Attack", damage: 1, damagePerCardDrawnThisCombat: 1 },
|
||||||
|
Filler1: { name: "Filler1", cost: 99, kind: "Skill" },
|
||||||
|
Filler2: { name: "Filler2", cost: 99, kind: "Skill" },
|
||||||
|
Filler3: { name: "Filler3", cost: 99, kind: "Skill" },
|
||||||
|
Filler4: { name: "Filler4", cost: 99, kind: "Skill" },
|
||||||
|
Filler5: { name: "Filler5", cost: 99, kind: "Skill" },
|
||||||
|
},
|
||||||
|
starterDeck: ["Murder", "Filler1", "Filler2", "Filler3", "Filler4", "Filler5"],
|
||||||
|
monsters: [{ name: "Dummy", maxHp: 6, intents: [{ kind: "Attack", value: 0 }] }],
|
||||||
|
};
|
||||||
|
const stats = {};
|
||||||
|
const r = simulateCombat(data, () => 0.999999, stats);
|
||||||
|
assert.equal(r.win, true);
|
||||||
|
assert.ok(stats.Murder.damage > 1);
|
||||||
|
});
|
||||||
|
|||||||
@@ -44,18 +44,43 @@ end
|
|||||||
if self:CanPlayCardNow(c) ~= true then
|
if self:CanPlayCardNow(c) ~= true then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if self.Energy < c.cost then
|
local cost = c.cost or 0
|
||||||
|
local skillFree = false
|
||||||
|
if self.HandCostZeroThisTurn == true then
|
||||||
|
cost = 0
|
||||||
|
elseif c.useAllEnergy == true then
|
||||||
|
cost = self.Energy
|
||||||
|
end
|
||||||
|
if c.kind == "Skill" and self.NextSkillCostZero == true then
|
||||||
|
cost = 0
|
||||||
|
skillFree = true
|
||||||
|
end
|
||||||
|
if c.kind == "Skill" and self.SkillCostReductionThisTurn ~= nil and self.SkillCostReductionThisTurn > 0 then
|
||||||
|
cost = math.max(0, cost - self.SkillCostReductionThisTurn)
|
||||||
|
end
|
||||||
|
if self.Energy < cost then
|
||||||
self:Toast("에너지가 부족합니다")
|
self:Toast("에너지가 부족합니다")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
self.Energy = self.Energy - c.cost
|
self.Energy = self.Energy - cost
|
||||||
self:ResolveCardEffects(cardId, slot, c, false)
|
self:ResolveCardEffects(cardId, slot, c, false, cost)
|
||||||
if c.kind == "Attack" then
|
if c.kind == "Attack" then
|
||||||
self.TurnAttackCardsPlayed = (self.TurnAttackCardsPlayed or 0) + 1
|
self.TurnAttackCardsPlayed = (self.TurnAttackCardsPlayed or 0) + 1
|
||||||
end
|
end
|
||||||
|
if skillFree == true then
|
||||||
|
if c.nextSkillCostZero ~= true then
|
||||||
|
self.NextSkillCostZero = false
|
||||||
|
end
|
||||||
|
end
|
||||||
if self:HasPowerField("cardPlayedBlock") == true then
|
if self:HasPowerField("cardPlayedBlock") == true then
|
||||||
self:AddCardBlock(self:AddPowerFieldTotal("cardPlayedBlock"))
|
self:AddCardBlock(self:AddPowerFieldTotal("cardPlayedBlock"))
|
||||||
end
|
end
|
||||||
|
if c.cardPlayedDamage ~= nil and c.cardPlayedDamage > 0 then
|
||||||
|
self:DealDirectDamageToTarget(c.cardPlayedDamage)
|
||||||
|
end
|
||||||
|
if c.cardPlayedRandomDamage ~= nil and c.cardPlayedRandomDamage > 0 then
|
||||||
|
self:DealDirectDamageToRandomMonster(c.cardPlayedRandomDamage)
|
||||||
|
end
|
||||||
table.remove(self.Hand, slot)
|
table.remove(self.Hand, slot)
|
||||||
if c.exhaust == true then
|
if c.exhaust == true then
|
||||||
if self.ExhaustPile == nil then self.ExhaustPile = {} end
|
if self.ExhaustPile == nil then self.ExhaustPile = {} end
|
||||||
@@ -230,6 +255,12 @@ if m.block > 0 and pierce ~= true then
|
|||||||
dmg = dmg - absorbed
|
dmg = dmg - absorbed
|
||||||
end
|
end
|
||||||
m.hp = m.hp - dmg
|
m.hp = m.hp - dmg
|
||||||
|
if dmg > 0 then
|
||||||
|
local poison = self:AddPowerFieldTotal("attackPoison")
|
||||||
|
if poison ~= nil and poison > 0 then
|
||||||
|
m.poison = (m.poison or 0) + poison
|
||||||
|
end
|
||||||
|
end
|
||||||
self:MonsterHitMotion(m.slot)
|
self:MonsterHitMotion(m.slot)
|
||||||
if m.hp <= 0 then
|
if m.hp <= 0 then
|
||||||
m.hp = 0
|
m.hp = 0
|
||||||
@@ -238,6 +269,48 @@ end`, [
|
|||||||
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
||||||
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'pierce' },
|
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'pierce' },
|
||||||
]),
|
]),
|
||||||
|
method('DealDirectDamageToTarget', `local m = self.Monsters[self.TargetIndex]
|
||||||
|
if m == nil or m.alive ~= true then
|
||||||
|
m = nil
|
||||||
|
for i = 1, #self.Monsters do
|
||||||
|
if self.Monsters[i].alive == true then m = self.Monsters[i]; self.TargetIndex = i; break end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if m == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
m.hp = m.hp - amount
|
||||||
|
self:ShowDmgPop(m.slot, amount)
|
||||||
|
self:MonsterHitMotion(m.slot)
|
||||||
|
if m.hp <= 0 then
|
||||||
|
m.hp = 0
|
||||||
|
self:KillMonster(m.slot)
|
||||||
|
end`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
||||||
|
]),
|
||||||
|
method('DealDirectDamageToRandomMonster', `local alive = {}
|
||||||
|
for i = 1, #self.Monsters do
|
||||||
|
local m = self.Monsters[i]
|
||||||
|
if m ~= nil and m.alive == true then
|
||||||
|
table.insert(alive, m)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #alive <= 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local m = alive[math.random(1, #alive)]
|
||||||
|
if m == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
m.hp = m.hp - amount
|
||||||
|
self:ShowDmgPop(m.slot, amount)
|
||||||
|
self:MonsterHitMotion(m.slot)
|
||||||
|
if m.hp <= 0 then
|
||||||
|
m.hp = 0
|
||||||
|
self:KillMonster(m.slot)
|
||||||
|
end`, [
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
||||||
|
]),
|
||||||
method('PlayAttackFx', `local m = self.Monsters[targetIndex]
|
method('PlayAttackFx', `local m = self.Monsters[targetIndex]
|
||||||
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
|
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
|
||||||
self:DealDamageToTarget(damage, pierce)
|
self:DealDamageToTarget(damage, pierce)
|
||||||
@@ -303,6 +376,12 @@ _TimerService:SetTimerOnce(function()
|
|||||||
dmg = dmg - absorbed
|
dmg = dmg - absorbed
|
||||||
end
|
end
|
||||||
m.hp = m.hp - dmg
|
m.hp = m.hp - dmg
|
||||||
|
if dmg > 0 then
|
||||||
|
local poison = self:AddPowerFieldTotal("attackPoison")
|
||||||
|
if poison ~= nil and poison > 0 then
|
||||||
|
m.poison = (m.poison or 0) + poison
|
||||||
|
end
|
||||||
|
end
|
||||||
self:ShowDmgPop(i, dmg)
|
self:ShowDmgPop(i, dmg)
|
||||||
self:MonsterHitMotion(i)
|
self:MonsterHitMotion(i)
|
||||||
if m.hp <= 0 then
|
if m.hp <= 0 then
|
||||||
@@ -336,6 +415,9 @@ if self.PlayerBlock > 0 then
|
|||||||
self.PlayerBlock = self.PlayerBlock - absorbed
|
self.PlayerBlock = self.PlayerBlock - absorbed
|
||||||
dmg = dmg - absorbed
|
dmg = dmg - absorbed
|
||||||
end
|
end
|
||||||
|
if dmg > 0 and self.PlayerIntangible ~= nil and self.PlayerIntangible > 0 and dmg > 1 then
|
||||||
|
dmg = 1
|
||||||
|
end
|
||||||
if dmg > 0 then
|
if dmg > 0 then
|
||||||
self.PlayerHp = self.PlayerHp - dmg
|
self.PlayerHp = self.PlayerHp - dmg
|
||||||
local reflect = self.PlayerThorns or 0
|
local reflect = self.PlayerThorns or 0
|
||||||
|
|||||||
@@ -230,8 +230,10 @@ self.TurnAttackCardsPlayed = 0
|
|||||||
self.TurnDiscardedCards = 0
|
self.TurnDiscardedCards = 0
|
||||||
self.NextTurnSelectCopies = 0
|
self.NextTurnSelectCopies = 0
|
||||||
self.NextTurnSelectPrompt = ""
|
self.NextTurnSelectPrompt = ""
|
||||||
|
self.SkillCostReductionThisTurn = 0
|
||||||
self:UpdateDiscardPrompt()
|
self:UpdateDiscardPrompt()
|
||||||
self.Energy = self.MaxEnergy
|
self.Energy = self.MaxEnergy
|
||||||
|
self.BlockGainMultiplier = 1
|
||||||
self:ApplyRelics("turnStart")
|
self:ApplyRelics("turnStart")
|
||||||
if self.NextTurnKeepBlock == true then
|
if self.NextTurnKeepBlock == true then
|
||||||
self.NextTurnKeepBlock = false
|
self.NextTurnKeepBlock = false
|
||||||
@@ -244,6 +246,9 @@ if self.ClayBlockNext > 0 then
|
|||||||
end
|
end
|
||||||
self.TurnAttackMultiplier = self.NextTurnAttackMultiplier or 1
|
self.TurnAttackMultiplier = self.NextTurnAttackMultiplier or 1
|
||||||
self.NextTurnAttackMultiplier = 1
|
self.NextTurnAttackMultiplier = 1
|
||||||
|
self.CardsDrawnThisCombat = self.CardsDrawnThisCombat or 0
|
||||||
|
self.HandCostZeroThisTurn = false
|
||||||
|
self.DrawDisabledThisTurn = false
|
||||||
local powerTurnDraw = 0
|
local powerTurnDraw = 0
|
||||||
local powerTurnDiscard = 0
|
local powerTurnDiscard = 0
|
||||||
if self.PlayerPowers ~= nil then
|
if self.PlayerPowers ~= nil then
|
||||||
@@ -256,6 +261,19 @@ if self.PlayerPowers ~= nil then
|
|||||||
self.Energy = self.Energy + pc.value
|
self.Energy = self.Energy + pc.value
|
||||||
elseif pc.powerEffect == "blockPerTurn" then
|
elseif pc.powerEffect == "blockPerTurn" then
|
||||||
self.PlayerBlock = self.PlayerBlock + pc.value
|
self.PlayerBlock = self.PlayerBlock + pc.value
|
||||||
|
elseif pc.powerEffect == "poisonPerTurn" then
|
||||||
|
if self.Monsters ~= nil then
|
||||||
|
for j = 1, #self.Monsters do
|
||||||
|
local tm = self.Monsters[j]
|
||||||
|
if tm ~= nil and tm.alive == true then
|
||||||
|
tm.poison = (tm.poison or 0) + pc.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif pc.powerEffect == "damagePerTurn" then
|
||||||
|
if self.Monsters ~= nil then
|
||||||
|
self:PlayAoeFx(pc.fx or pc.image, pc.value or 0)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if pc.turnStartShiv ~= nil then
|
if pc.turnStartShiv ~= nil then
|
||||||
self:AddCardsToHand("Shiv", pc.turnStartShiv)
|
self:AddCardsToHand("Shiv", pc.turnStartShiv)
|
||||||
@@ -408,13 +426,30 @@ for i = 1, #self.Hand do
|
|||||||
\tend
|
\tend
|
||||||
end
|
end
|
||||||
self.Hand = kept
|
self.Hand = kept
|
||||||
|
if self.PlayerPowers ~= nil then
|
||||||
|
for i = 1, #self.PlayerPowers do
|
||||||
|
local pc = self.Cards[self.PlayerPowers[i]]
|
||||||
|
if pc ~= nil and pc.endTurnDexLoss ~= nil and pc.endTurnDexLoss > 0 then
|
||||||
|
self.PlayerDex = self.PlayerDex - pc.endTurnDexLoss
|
||||||
|
if self.PlayerDex < 0 then self.PlayerDex = 0 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if self.PlayerIntangible ~= nil and self.PlayerIntangible > 0 then
|
||||||
|
self.PlayerIntangible = self.PlayerIntangible - 1
|
||||||
|
if self.PlayerIntangible < 0 then self.PlayerIntangible = 0 end
|
||||||
|
end
|
||||||
if self.PlayerWeak > 0 then self.PlayerWeak = self.PlayerWeak - 1 end
|
if self.PlayerWeak > 0 then self.PlayerWeak = self.PlayerWeak - 1 end
|
||||||
if self.PlayerVuln > 0 then self.PlayerVuln = self.PlayerVuln - 1 end
|
if self.PlayerVuln > 0 then self.PlayerVuln = self.PlayerVuln - 1 end
|
||||||
self:RenderHand(false)
|
self:RenderHand(false)
|
||||||
self:RenderPiles()
|
self:RenderPiles()
|
||||||
self:EnemyTurn()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'retainSlot' }]),
|
self:EnemyTurn()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'retainSlot' }]),
|
||||||
method('DrawCards', `local drawnSlots = {}
|
method('DrawCards', `local drawnSlots = {}
|
||||||
|
local drawnCards = {}
|
||||||
local drewAny = false
|
local drewAny = false
|
||||||
|
if self.DrawDisabledThisTurn == true then
|
||||||
|
\treturn drawnCards
|
||||||
|
end
|
||||||
for i = 1, amount do
|
for i = 1, amount do
|
||||||
\tif #self.DrawPile <= 0 then
|
\tif #self.DrawPile <= 0 then
|
||||||
\t\tself:RecycleDiscardIntoDraw()
|
\t\tself:RecycleDiscardIntoDraw()
|
||||||
@@ -423,6 +458,8 @@ for i = 1, amount do
|
|||||||
\t\tbreak
|
\t\tbreak
|
||||||
\tend
|
\tend
|
||||||
\tlocal cardId = table.remove(self.DrawPile)
|
\tlocal cardId = table.remove(self.DrawPile)
|
||||||
|
\ttable.insert(drawnCards, cardId)
|
||||||
|
\tself.CardsDrawnThisCombat = (self.CardsDrawnThisCombat or 0) + 1
|
||||||
\tif #self.Hand >= 10 then
|
\tif #self.Hand >= 10 then
|
||||||
\t\ttable.insert(self.DiscardPile, cardId)
|
\t\ttable.insert(self.DiscardPile, cardId)
|
||||||
\t\tself:TriggerSly(cardId)
|
\t\tself:TriggerSly(cardId)
|
||||||
@@ -442,10 +479,11 @@ if animate == true and #drawnSlots > 0 then
|
|||||||
\t\tlocal slot = drawnSlots[i]
|
\t\tlocal slot = drawnSlots[i]
|
||||||
\t\tself:AnimateCardFrom(slot, drawStart, Vector2(self:GetHandSlotX(slot), 0), 0.08 + i * 0.045)
|
\t\tself:AnimateCardFrom(slot, drawStart, Vector2(self:GetHandSlotX(slot), 0), 0.08 + i * 0.045)
|
||||||
\tend
|
\tend
|
||||||
|
return drawnCards
|
||||||
end`, [
|
end`, [
|
||||||
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
||||||
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'animate' },
|
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'animate' },
|
||||||
]),
|
], 0, 'any'),
|
||||||
method('AddCardsToHand', `if self.Hand == nil then
|
method('AddCardsToHand', `if self.Hand == nil then
|
||||||
self.Hand = {}
|
self.Hand = {}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ if e ~= nil and e.SpriteGUIRendererComponent ~= nil then
|
|||||||
end
|
end
|
||||||
self:SetText(base .. "/Cost", string.format("%d", c.cost))
|
self:SetText(base .. "/Cost", string.format("%d", c.cost))
|
||||||
self:SetText(base .. "/Name", c.name)
|
self:SetText(base .. "/Name", c.name)
|
||||||
self:SetText(base .. "/Desc", c.desc)
|
self:SetText(base .. "/Desc", self:FormatCardDescription(c.desc))
|
||||||
local art = _EntityService:GetEntityByPath(base .. "/Art")
|
local art = _EntityService:GetEntityByPath(base .. "/Art")
|
||||||
if art ~= nil then
|
if art ~= nil then
|
||||||
if c.image ~= nil and c.image ~= "" then
|
if c.image ~= nil and c.image ~= "" then
|
||||||
@@ -269,6 +269,9 @@ end, 1 / 60)`, [
|
|||||||
if amount > 0 and self.PlayerDex ~= nil then
|
if amount > 0 and self.PlayerDex ~= nil then
|
||||||
amount = amount + self.PlayerDex
|
amount = amount + self.PlayerDex
|
||||||
end
|
end
|
||||||
|
if self.BlockGainMultiplier ~= nil and self.BlockGainMultiplier > 1 then
|
||||||
|
amount = amount * self.BlockGainMultiplier
|
||||||
|
end
|
||||||
if amount < 0 then
|
if amount < 0 then
|
||||||
amount = 0
|
amount = 0
|
||||||
end
|
end
|
||||||
@@ -305,6 +308,9 @@ end
|
|||||||
if c.damagePerSkillInHand ~= nil then
|
if c.damagePerSkillInHand ~= nil then
|
||||||
base2 = base2 + self:CountOtherHandSkills(slot) * c.damagePerSkillInHand
|
base2 = base2 + self:CountOtherHandSkills(slot) * c.damagePerSkillInHand
|
||||||
end
|
end
|
||||||
|
if c.damagePerCardDrawnThisCombat ~= nil then
|
||||||
|
base2 = base2 + (self.CardsDrawnThisCombat or 0) * c.damagePerCardDrawnThisCombat
|
||||||
|
end
|
||||||
if base2 < 0 then
|
if base2 < 0 then
|
||||||
base2 = 0
|
base2 = 0
|
||||||
end
|
end
|
||||||
@@ -367,13 +373,38 @@ if c.nextTurnAttackMultiplier ~= nil and c.nextTurnAttackMultiplier > 0 then
|
|||||||
local cur = self.NextTurnAttackMultiplier or 1
|
local cur = self.NextTurnAttackMultiplier or 1
|
||||||
self.NextTurnAttackMultiplier = cur * c.nextTurnAttackMultiplier
|
self.NextTurnAttackMultiplier = cur * c.nextTurnAttackMultiplier
|
||||||
end`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' }]),
|
end`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' }]),
|
||||||
method('ResolveCardEffects', `if c == nil then
|
method('ResolveCardEffects', `if c == nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
if c.blockGainMultiplier ~= nil and c.blockGainMultiplier > 0 then
|
||||||
|
self.BlockGainMultiplier = (self.BlockGainMultiplier or 1) * c.blockGainMultiplier
|
||||||
|
end
|
||||||
|
if c.nextSkillCostZero == true then
|
||||||
|
self.NextSkillCostZero = true
|
||||||
|
end
|
||||||
|
if c.skillCostReductionThisTurn ~= nil and c.skillCostReductionThisTurn > 0 then
|
||||||
|
self.SkillCostReductionThisTurn = (self.SkillCostReductionThisTurn or 0) + c.skillCostReductionThisTurn
|
||||||
|
end
|
||||||
|
if c.handCostZeroThisTurn == true then
|
||||||
|
self.HandCostZeroThisTurn = true
|
||||||
|
end
|
||||||
|
if c.drawDisabledThisTurn == true then
|
||||||
|
self.DrawDisabledThisTurn = true
|
||||||
|
end
|
||||||
|
local xEnergy = energySpent or 0
|
||||||
|
local weakAmount = c.weak or 0
|
||||||
|
local vulnAmount = c.vuln or 0
|
||||||
|
local poisonAmount = c.poison or 0
|
||||||
|
if c.xWeakPerEnergy ~= nil and c.xWeakPerEnergy > 0 then
|
||||||
|
weakAmount = weakAmount + xEnergy * c.xWeakPerEnergy
|
||||||
|
end
|
||||||
if c.kind == "Attack" then
|
if c.kind == "Attack" then
|
||||||
if c.damage ~= nil then
|
if c.damage ~= nil or c.xDamagePerEnergy ~= nil then
|
||||||
self:PlayerAttackMotion()
|
self:PlayerAttackMotion()
|
||||||
local baseDmg = self:AttackBaseForCard(slot, c)
|
local baseDmg = self:AttackBaseForCard(slot, c)
|
||||||
|
if c.xDamagePerEnergy ~= nil and c.xDamagePerEnergy > 0 then
|
||||||
|
baseDmg = xEnergy * c.xDamagePerEnergy
|
||||||
|
end
|
||||||
local total = 0
|
local total = 0
|
||||||
local hitN = c.hits or 1
|
local hitN = c.hits or 1
|
||||||
if c.otherHandAtLeast ~= nil and c.bonusHitsWhenOtherHandAtLeast ~= nil then
|
if c.otherHandAtLeast ~= nil and c.bonusHitsWhenOtherHandAtLeast ~= nil then
|
||||||
@@ -428,8 +459,11 @@ end
|
|||||||
if c.gainEnergy ~= nil and c.gainEnergy ~= 0 then
|
if c.gainEnergy ~= nil and c.gainEnergy ~= 0 then
|
||||||
self.Energy = self.Energy + c.gainEnergy
|
self.Energy = self.Energy + c.gainEnergy
|
||||||
end
|
end
|
||||||
|
if c.intangible ~= nil and c.intangible > 0 then
|
||||||
|
self.PlayerIntangible = (self.PlayerIntangible or 0) + c.intangible
|
||||||
|
end
|
||||||
self:QueueNextTurnEffects(c)
|
self:QueueNextTurnEffects(c)
|
||||||
if c.weak ~= nil or c.vuln ~= nil or c.poison ~= nil then
|
if c.weak ~= nil or c.vuln ~= nil or c.poison ~= nil or c.xWeakPerEnergy ~= nil then
|
||||||
local tm = self.Monsters[self.TargetIndex]
|
local tm = self.Monsters[self.TargetIndex]
|
||||||
if tm == nil or tm.alive ~= true then
|
if tm == nil or tm.alive ~= true then
|
||||||
for i = 1, #self.Monsters do
|
for i = 1, #self.Monsters do
|
||||||
@@ -437,18 +471,19 @@ if c.weak ~= nil or c.vuln ~= nil or c.poison ~= nil then
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
if tm ~= nil and tm.alive == true then
|
if tm ~= nil and tm.alive == true then
|
||||||
if c.weak ~= nil then tm.weak = tm.weak + c.weak end
|
if weakAmount ~= nil and weakAmount > 0 then tm.weak = tm.weak + weakAmount end
|
||||||
if c.poison ~= nil then tm.poison = (tm.poison or 0) + c.poison end
|
if poisonAmount ~= nil and poisonAmount > 0 then tm.poison = (tm.poison or 0) + poisonAmount end
|
||||||
if c.vuln ~= nil then
|
if vulnAmount ~= nil and vulnAmount > 0 then
|
||||||
tm.vuln = tm.vuln + c.vuln
|
tm.vuln = tm.vuln + vulnAmount
|
||||||
if self:HasRelic("championBelt") then
|
if self:HasRelic("championBelt") then
|
||||||
tm.weak = tm.weak + 1
|
tm.weak = tm.weak + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
local drawnCards = {}
|
||||||
if c.draw ~= nil then
|
if c.draw ~= nil then
|
||||||
self:DrawCards(c.draw, true)
|
drawnCards = self:DrawCards(c.draw, true) or {}
|
||||||
end
|
end
|
||||||
if c.drawUntilHandSize ~= nil and c.drawUntilHandSize > 0 then
|
if c.drawUntilHandSize ~= nil and c.drawUntilHandSize > 0 then
|
||||||
local currentHand = 0
|
local currentHand = 0
|
||||||
@@ -460,7 +495,18 @@ if c.drawUntilHandSize ~= nil and c.drawUntilHandSize > 0 then
|
|||||||
end
|
end
|
||||||
local need = c.drawUntilHandSize - currentHand
|
local need = c.drawUntilHandSize - currentHand
|
||||||
if need > 0 then
|
if need > 0 then
|
||||||
self:DrawCards(need, true)
|
local moreDrawnCards = self:DrawCards(need, true) or {}
|
||||||
|
for i = 1, #moreDrawnCards do
|
||||||
|
table.insert(drawnCards, moreDrawnCards[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if c.drawSkillBlock ~= nil and c.drawSkillBlock > 0 then
|
||||||
|
for i = 1, #drawnCards do
|
||||||
|
local drawnCard = self.Cards[drawnCards[i]]
|
||||||
|
if drawnCard ~= nil and drawnCard.kind == "Skill" then
|
||||||
|
self:AddCardBlock(c.drawSkillBlock)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if c.addShiv ~= nil and c.discard == nil and c.discardAll ~= true then
|
if c.addShiv ~= nil and c.discard == nil and c.discardAll ~= true then
|
||||||
@@ -470,13 +516,14 @@ end`, [
|
|||||||
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
||||||
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' },
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' },
|
||||||
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'free' },
|
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'free' },
|
||||||
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'energySpent' },
|
||||||
]),
|
]),
|
||||||
method('TriggerSly', `local c = self.Cards[cardId]
|
method('TriggerSly', `local c = self.Cards[cardId]
|
||||||
if c == nil or c.sly ~= true then
|
if c == nil or c.sly ~= true then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
self:Toast("교활 발동: " .. c.name)
|
self:Toast("교활 발동: " .. c.name)
|
||||||
self:ResolveCardEffects(cardId, 0, c, true)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' }]),
|
self:ResolveCardEffects(cardId, 0, c, true, 0)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' }]),
|
||||||
method('DiscardHandCard', `if self.Hand == nil then
|
method('DiscardHandCard', `if self.Hand == nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ self:SetHpBar("/ui/RunUIGroup/CombatHud/PlayerPanel/HpBarFill", self.PlayerHp, s
|
|||||||
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0)
|
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0)
|
||||||
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock))
|
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock))
|
||||||
local pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln, 0)
|
local pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln, 0)
|
||||||
|
if self.PlayerIntangible ~= nil and self.PlayerIntangible > 0 then
|
||||||
|
if pb ~= "" then pb = pb .. " " end
|
||||||
|
pb = pb .. "불가침" .. tostring(self.PlayerIntangible)
|
||||||
|
end
|
||||||
if self.PlayerDex ~= nil and self.PlayerDex > 0 then
|
if self.PlayerDex ~= nil and self.PlayerDex > 0 then
|
||||||
if pb ~= "" then pb = pb .. " " end
|
if pb ~= "" then pb = pb .. " " end
|
||||||
pb = pb .. "민첩+" .. tostring(self.PlayerDex)
|
pb = pb .. "민첩+" .. tostring(self.PlayerDex)
|
||||||
|
|||||||
@@ -67,11 +67,18 @@ self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/Name", self:JobLabel())
|
|||||||
self.MaxEnergy = 3
|
self.MaxEnergy = 3
|
||||||
self.Turn = 0
|
self.Turn = 0
|
||||||
self.PlayerBlock = 0
|
self.PlayerBlock = 0
|
||||||
|
self.BlockGainMultiplier = 1
|
||||||
|
self.CardsDrawnThisCombat = 0
|
||||||
|
self.HandCostZeroThisTurn = false
|
||||||
|
self.DrawDisabledThisTurn = false
|
||||||
|
self.NextSkillCostZero = false
|
||||||
|
self.SkillCostReductionThisTurn = 0
|
||||||
self.PlayerStr = 0
|
self.PlayerStr = 0
|
||||||
self.PlayerDex = 0
|
self.PlayerDex = 0
|
||||||
self.PlayerThorns = 0
|
self.PlayerThorns = 0
|
||||||
self.PlayerWeak = 0
|
self.PlayerWeak = 0
|
||||||
self.PlayerVuln = 0
|
self.PlayerVuln = 0
|
||||||
|
self.PlayerIntangible = 0
|
||||||
self.PlayerPowers = {}
|
self.PlayerPowers = {}
|
||||||
self.FightAttackCount = 0
|
self.FightAttackCount = 0
|
||||||
self.TurnAttackCardsPlayed = 0
|
self.TurnAttackCardsPlayed = 0
|
||||||
|
|||||||
@@ -3,6 +3,47 @@ import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_
|
|||||||
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
|
||||||
|
|
||||||
export const tooltipMethods = [
|
export const tooltipMethods = [
|
||||||
|
method('FormatCardDescription', `if desc == nil or desc == "" then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
local function replacePlain(text, needle, replacement)
|
||||||
|
local out = ""
|
||||||
|
local pos = 1
|
||||||
|
while true do
|
||||||
|
local s, e = string.find(text, needle, pos, true)
|
||||||
|
if s == nil then
|
||||||
|
out = out .. string.sub(text, pos)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
out = out .. string.sub(text, pos, s - 1) .. replacement
|
||||||
|
pos = e + 1
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
local terms = {
|
||||||
|
"교활",
|
||||||
|
"보존",
|
||||||
|
"민첩",
|
||||||
|
"가시",
|
||||||
|
"소멸",
|
||||||
|
"선천성",
|
||||||
|
"취약",
|
||||||
|
"약화",
|
||||||
|
"독",
|
||||||
|
"광역",
|
||||||
|
"관통",
|
||||||
|
"방어도",
|
||||||
|
"힘",
|
||||||
|
"스킬",
|
||||||
|
"공격",
|
||||||
|
"파워",
|
||||||
|
}
|
||||||
|
local out = desc
|
||||||
|
for i = 1, #terms do
|
||||||
|
local term = terms[i]
|
||||||
|
out = replacePlain(out, term, "<color=#70D6FF>" .. term .. "</color>")
|
||||||
|
end
|
||||||
|
return out`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'desc' }], 0, 'string'),
|
||||||
method('BuildCardKeywordTooltip', `if c == nil then
|
method('BuildCardKeywordTooltip', `if c == nil then
|
||||||
return ""
|
return ""
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ function writeCodeblocks() {
|
|||||||
prop('number', 'PlayerHp', '0'),
|
prop('number', 'PlayerHp', '0'),
|
||||||
prop('number', 'PlayerMaxHp', '80'),
|
prop('number', 'PlayerMaxHp', '80'),
|
||||||
prop('number', 'PlayerBlock', '0'),
|
prop('number', 'PlayerBlock', '0'),
|
||||||
|
prop('number', 'BlockGainMultiplier', '1'),
|
||||||
prop('number', 'PlayerDex', '0'),
|
prop('number', 'PlayerDex', '0'),
|
||||||
prop('number', 'PlayerThorns', '0'),
|
prop('number', 'PlayerThorns', '0'),
|
||||||
prop('boolean', 'CombatOver', 'false'),
|
prop('boolean', 'CombatOver', 'false'),
|
||||||
@@ -141,6 +142,8 @@ function writeCodeblocks() {
|
|||||||
prop('number', 'TurnAttackMultiplier', '1'),
|
prop('number', 'TurnAttackMultiplier', '1'),
|
||||||
prop('string', 'NextTurnSelectPrompt', '""'),
|
prop('string', 'NextTurnSelectPrompt', '""'),
|
||||||
prop('number', 'NextTurnSelectCopies', '0'),
|
prop('number', 'NextTurnSelectCopies', '0'),
|
||||||
|
prop('boolean', 'NextSkillCostZero', 'false'),
|
||||||
|
prop('number', 'SkillCostReductionThisTurn', '0'),
|
||||||
prop('any', 'NextTurnAddCards'),
|
prop('any', 'NextTurnAddCards'),
|
||||||
], [
|
], [
|
||||||
...bootMethods,
|
...bootMethods,
|
||||||
|
|||||||
@@ -162,9 +162,18 @@ function luaCardsTable(cards) {
|
|||||||
if (c.damagePerAttackPlayedThisTurn != null) fields.push(`damagePerAttackPlayedThisTurn = ${c.damagePerAttackPlayedThisTurn}`);
|
if (c.damagePerAttackPlayedThisTurn != null) fields.push(`damagePerAttackPlayedThisTurn = ${c.damagePerAttackPlayedThisTurn}`);
|
||||||
if (c.damagePerDiscardedThisTurn != null) fields.push(`damagePerDiscardedThisTurn = ${c.damagePerDiscardedThisTurn}`);
|
if (c.damagePerDiscardedThisTurn != null) fields.push(`damagePerDiscardedThisTurn = ${c.damagePerDiscardedThisTurn}`);
|
||||||
if (c.damagePerSkillInHand != null) fields.push(`damagePerSkillInHand = ${c.damagePerSkillInHand}`);
|
if (c.damagePerSkillInHand != null) fields.push(`damagePerSkillInHand = ${c.damagePerSkillInHand}`);
|
||||||
|
if (c.damagePerCardDrawnThisCombat != null) fields.push(`damagePerCardDrawnThisCombat = ${c.damagePerCardDrawnThisCombat}`);
|
||||||
|
if (c.damagePerTurn != null) fields.push(`damagePerTurn = ${c.damagePerTurn}`);
|
||||||
|
if (c.cardPlayedDamage != null) fields.push(`cardPlayedDamage = ${c.cardPlayedDamage}`);
|
||||||
|
if (c.cardPlayedRandomDamage != null) fields.push(`cardPlayedRandomDamage = ${c.cardPlayedRandomDamage}`);
|
||||||
|
if (c.intangible != null) fields.push(`intangible = ${c.intangible}`);
|
||||||
|
if (c.endTurnDexLoss != null) fields.push(`endTurnDexLoss = ${c.endTurnDexLoss}`);
|
||||||
|
if (c.poisonPerTurn != null) fields.push(`poisonPerTurn = ${c.poisonPerTurn}`);
|
||||||
|
if (c.attackPoison != null) fields.push(`attackPoison = ${c.attackPoison}`);
|
||||||
if (c.otherHandAtLeast != null) fields.push(`otherHandAtLeast = ${c.otherHandAtLeast}`);
|
if (c.otherHandAtLeast != null) fields.push(`otherHandAtLeast = ${c.otherHandAtLeast}`);
|
||||||
if (c.bonusHitsWhenOtherHandAtLeast != null) fields.push(`bonusHitsWhenOtherHandAtLeast = ${c.bonusHitsWhenOtherHandAtLeast}`);
|
if (c.bonusHitsWhenOtherHandAtLeast != null) fields.push(`bonusHitsWhenOtherHandAtLeast = ${c.bonusHitsWhenOtherHandAtLeast}`);
|
||||||
if (c.block != null) fields.push(`block = ${c.block}`);
|
if (c.block != null) fields.push(`block = ${c.block}`);
|
||||||
|
if (c.blockGainMultiplier != null) fields.push(`blockGainMultiplier = ${c.blockGainMultiplier}`);
|
||||||
if (c.strength != null) fields.push(`strength = ${c.strength}`);
|
if (c.strength != null) fields.push(`strength = ${c.strength}`);
|
||||||
if (c.dex != null) fields.push(`dex = ${c.dex}`);
|
if (c.dex != null) fields.push(`dex = ${c.dex}`);
|
||||||
if (c.thorns != null) fields.push(`thorns = ${c.thorns}`);
|
if (c.thorns != null) fields.push(`thorns = ${c.thorns}`);
|
||||||
@@ -181,6 +190,7 @@ function luaCardsTable(cards) {
|
|||||||
if (c.selfVuln != null) fields.push(`selfVuln = ${c.selfVuln}`);
|
if (c.selfVuln != null) fields.push(`selfVuln = ${c.selfVuln}`);
|
||||||
if (c.draw != null) fields.push(`draw = ${c.draw}`);
|
if (c.draw != null) fields.push(`draw = ${c.draw}`);
|
||||||
if (c.drawUntilHandSize != null) fields.push(`drawUntilHandSize = ${c.drawUntilHandSize}`);
|
if (c.drawUntilHandSize != null) fields.push(`drawUntilHandSize = ${c.drawUntilHandSize}`);
|
||||||
|
if (c.drawSkillBlock != null) fields.push(`drawSkillBlock = ${c.drawSkillBlock}`);
|
||||||
if (c.heal != null) fields.push(`heal = ${c.heal}`);
|
if (c.heal != null) fields.push(`heal = ${c.heal}`);
|
||||||
if (c.gainEnergy != null) fields.push(`gainEnergy = ${c.gainEnergy}`);
|
if (c.gainEnergy != null) fields.push(`gainEnergy = ${c.gainEnergy}`);
|
||||||
if (c.poison != null) fields.push(`poison = ${c.poison}`);
|
if (c.poison != null) fields.push(`poison = ${c.poison}`);
|
||||||
@@ -191,7 +201,12 @@ function luaCardsTable(cards) {
|
|||||||
if (c.turnStartShiv != null) fields.push(`turnStartShiv = ${c.turnStartShiv}`);
|
if (c.turnStartShiv != null) fields.push(`turnStartShiv = ${c.turnStartShiv}`);
|
||||||
if (c.turnStartDraw != null) fields.push(`turnStartDraw = ${c.turnStartDraw}`);
|
if (c.turnStartDraw != null) fields.push(`turnStartDraw = ${c.turnStartDraw}`);
|
||||||
if (c.turnStartDiscard != null) fields.push(`turnStartDiscard = ${c.turnStartDiscard}`);
|
if (c.turnStartDiscard != null) fields.push(`turnStartDiscard = ${c.turnStartDiscard}`);
|
||||||
|
if (c.handCostZeroThisTurn === true) fields.push('handCostZeroThisTurn = true');
|
||||||
|
if (c.drawDisabledThisTurn === true) fields.push('drawDisabledThisTurn = true');
|
||||||
if (c.addShivPerDiscard === true) fields.push('addShivPerDiscard = true');
|
if (c.addShivPerDiscard === true) fields.push('addShivPerDiscard = true');
|
||||||
|
if (c.useAllEnergy === true) fields.push('useAllEnergy = true');
|
||||||
|
if (c.xDamagePerEnergy != null) fields.push(`xDamagePerEnergy = ${c.xDamagePerEnergy}`);
|
||||||
|
if (c.xWeakPerEnergy != null) fields.push(`xWeakPerEnergy = ${c.xWeakPerEnergy}`);
|
||||||
if (c.nextTurnBlock != null) fields.push(`nextTurnBlock = ${c.nextTurnBlock}`);
|
if (c.nextTurnBlock != null) fields.push(`nextTurnBlock = ${c.nextTurnBlock}`);
|
||||||
if (c.nextTurnDraw != null) fields.push(`nextTurnDraw = ${c.nextTurnDraw}`);
|
if (c.nextTurnDraw != null) fields.push(`nextTurnDraw = ${c.nextTurnDraw}`);
|
||||||
if (c.nextTurnKeepBlock === true) fields.push('nextTurnKeepBlock = true');
|
if (c.nextTurnKeepBlock === true) fields.push('nextTurnKeepBlock = true');
|
||||||
@@ -199,6 +214,8 @@ function luaCardsTable(cards) {
|
|||||||
if (c.nextTurnCopies != null) fields.push(`nextTurnCopies = ${c.nextTurnCopies}`);
|
if (c.nextTurnCopies != null) fields.push(`nextTurnCopies = ${c.nextTurnCopies}`);
|
||||||
if (c.nextTurnSelectHandCard === true) fields.push('nextTurnSelectHandCard = true');
|
if (c.nextTurnSelectHandCard === true) fields.push('nextTurnSelectHandCard = true');
|
||||||
if (c.nextTurnSelectPrompt != null) fields.push(`nextTurnSelectPrompt = ${luaStr(c.nextTurnSelectPrompt)}`);
|
if (c.nextTurnSelectPrompt != null) fields.push(`nextTurnSelectPrompt = ${luaStr(c.nextTurnSelectPrompt)}`);
|
||||||
|
if (c.nextSkillCostZero === true) fields.push('nextSkillCostZero = true');
|
||||||
|
if (c.skillCostReductionThisTurn != null) fields.push(`skillCostReductionThisTurn = ${c.skillCostReductionThisTurn}`);
|
||||||
if (c.innate === true) fields.push('innate = true');
|
if (c.innate === true) fields.push('innate = true');
|
||||||
if (c.playableWhenDrawPileEmpty === true) fields.push('playableWhenDrawPileEmpty = true');
|
if (c.playableWhenDrawPileEmpty === true) fields.push('playableWhenDrawPileEmpty = true');
|
||||||
if (c.sly === true) fields.push('sly = true');
|
if (c.sly === true) fields.push('sly = true');
|
||||||
|
|||||||
Reference in New Issue
Block a user