Campaign 3
|
|
@ -7,7 +7,10 @@
|
|||
"Bash(ls:*)",
|
||||
"Bash(cd:*)",
|
||||
"Bash(python3:*)",
|
||||
"Bash(head:*)"
|
||||
"Bash(head:*)",
|
||||
"Bash(jq:*)",
|
||||
"Bash(identify /home/brianfertig/git/tyrants-edge/assets/images/cards/imp_cmd_1.png)",
|
||||
"Bash(identify /home/brianfertig/git/tyrants-edge/assets/images/ui/imperial_01.png)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 424 B |
|
After Width: | Height: | Size: 424 B |
|
After Width: | Height: | Size: 424 B |
|
|
@ -38,9 +38,13 @@
|
|||
"level": 3,
|
||||
"requiredLevel": 3,
|
||||
"unlockCondition": "campaign_bloodthirsty",
|
||||
"missions": [],
|
||||
"missions": ["mission_014", "mission_015", "mission_016", "mission_017", "mission_018", "mission_019", "mission_020", "mission_021"],
|
||||
"starterReward": {
|
||||
"cards": []
|
||||
"cards": [
|
||||
"xeno_drone_1", "xeno_drone_1", "xeno_drone_1",
|
||||
"xeno_spitter_1", "xeno_spitter_1", "xeno_spitter_1",
|
||||
"xeno_beetle_1", "xeno_beetle_1", "xeno_beetle_1"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
325
data/cards.json
|
|
@ -2841,5 +2841,330 @@
|
|||
],
|
||||
"flavorText": "The tide rises. The tide claims all.",
|
||||
"artKey": "bt_archon_1"
|
||||
},
|
||||
|
||||
{
|
||||
"id": "xeno_cmd_1",
|
||||
"name": "Hatchery Keeper",
|
||||
"faction": "xeno",
|
||||
"type": "commander",
|
||||
"rarity": "legendary",
|
||||
"levels": [
|
||||
{ "attack": 4, "health": 55, "armor": 2, "delay": 0, "skills": [{ "name": "spawn", "value": 1, "trigger": "on_turn_start" }] },
|
||||
{ "attack": 6, "health": 83, "armor": 3, "delay": 0, "skills": [{ "name": "spawn", "value": 1, "trigger": "on_turn_start" }] },
|
||||
{ "attack": 9, "health": 124, "armor": 4, "delay": 0, "skills": [{ "name": "spawn", "value": 1, "trigger": "on_turn_start" }] },
|
||||
{ "attack": 14, "health": 186, "armor": 6, "delay": 0, "skills": [{ "name": "spawn", "value": 1, "trigger": "on_turn_start" }] },
|
||||
{ "attack": 20, "health": 279, "armor": 9, "delay": 0, "skills": [{ "name": "spawn", "value": 1, "trigger": "on_turn_start" }] }
|
||||
],
|
||||
"flavorText": "Every egg she lays is another mouth to feed on your flesh.",
|
||||
"artKey": "xeno_cmd_1"
|
||||
},
|
||||
{
|
||||
"id": "xeno_cmd_2",
|
||||
"name": "War Drone Alpha",
|
||||
"faction": "xeno",
|
||||
"type": "commander",
|
||||
"rarity": "legendary",
|
||||
"levels": [
|
||||
{ "attack": 6, "health": 50, "armor": 2, "delay": 0, "skills": [{ "name": "rally", "value": 1, "trigger": "preBattle", "all": true }] },
|
||||
{ "attack": 9, "health": 75, "armor": 3, "delay": 0, "skills": [{ "name": "rally", "value": 2, "trigger": "preBattle", "all": true }] },
|
||||
{ "attack": 14, "health": 113, "armor": 5, "delay": 0, "skills": [{ "name": "rally", "value": 3, "trigger": "preBattle", "all": true }] },
|
||||
{ "attack": 20, "health": 169, "armor": 7, "delay": 0, "skills": [{ "name": "rally", "value": 5, "trigger": "preBattle", "all": true }] },
|
||||
{ "attack": 30, "health": 253, "armor": 10, "delay": 0, "skills": [{ "name": "rally", "value": 7, "trigger": "preBattle", "all": true }] }
|
||||
],
|
||||
"flavorText": "The Alpha does not lead. It commands through shared fury.",
|
||||
"artKey": "xeno_cmd_2"
|
||||
},
|
||||
{
|
||||
"id": "xeno_cmd_3",
|
||||
"name": "The Hive Mind",
|
||||
"faction": "xeno",
|
||||
"type": "commander",
|
||||
"rarity": "legendary",
|
||||
"levels": [
|
||||
{ "attack": 5, "health": 60, "armor": 3, "delay": 0, "skills": [{ "name": "spawn", "value": 1, "trigger": "on_turn_start" }, { "name": "enfeeble", "value": 1, "trigger": "preBattle", "all": true }] },
|
||||
{ "attack": 8, "health": 90, "armor": 5, "delay": 0, "skills": [{ "name": "spawn", "value": 1, "trigger": "on_turn_start" }, { "name": "enfeeble", "value": 2, "trigger": "preBattle", "all": true }] },
|
||||
{ "attack": 11, "health": 135, "armor": 7, "delay": 0, "skills": [{ "name": "spawn", "value": 1, "trigger": "on_turn_start" }, { "name": "enfeeble", "value": 3, "trigger": "preBattle", "all": true }] },
|
||||
{ "attack": 17, "health": 203, "armor": 10, "delay": 0, "skills": [{ "name": "spawn", "value": 1, "trigger": "on_turn_start" }, { "name": "enfeeble", "value": 5, "trigger": "preBattle", "all": true }] },
|
||||
{ "attack": 25, "health": 304, "armor": 15, "delay": 0, "skills": [{ "name": "spawn", "value": 1, "trigger": "on_turn_start" }, { "name": "enfeeble", "value": 7, "trigger": "preBattle", "all": true }] }
|
||||
],
|
||||
"flavorText": "One mind. A billion claws. It has already seen what you will do next.",
|
||||
"artKey": "xeno_cmd_3"
|
||||
},
|
||||
{
|
||||
"id": "xeno_drone_token",
|
||||
"name": "Xeno Drone",
|
||||
"faction": "xeno",
|
||||
"type": "token",
|
||||
"rarity": "common",
|
||||
"levels": [
|
||||
{ "attack": 2, "health": 5, "armor": 0, "delay": 0, "skills": [] },
|
||||
{ "attack": 3, "health": 8, "armor": 0, "delay": 0, "skills": [] },
|
||||
{ "attack": 4, "health": 12, "armor": 0, "delay": 0, "skills": [] },
|
||||
{ "attack": 6, "health": 18, "armor": 0, "delay": 0, "skills": [] },
|
||||
{ "attack": 9, "health": 27, "armor": 0, "delay": 0, "skills": [] }
|
||||
],
|
||||
"flavorText": "Born to die. Born to swarm.",
|
||||
"artKey": "xeno_drone_token"
|
||||
},
|
||||
|
||||
{
|
||||
"id": "xeno_drone_1",
|
||||
"name": "Chitin Drone",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "common",
|
||||
"levels": [
|
||||
{ "attack": 3, "health": 14, "armor": 1, "delay": 1, "skills": [{ "name": "carapace", "value": 1, "trigger": "on_defend" }] },
|
||||
{ "attack": 5, "health": 21, "armor": 2, "delay": 1, "skills": [{ "name": "carapace", "value": 1, "trigger": "on_defend" }] },
|
||||
{ "attack": 7, "health": 32, "armor": 2, "delay": 1, "skills": [{ "name": "carapace", "value": 1, "trigger": "on_defend" }] },
|
||||
{ "attack": 10, "health": 48, "armor": 3, "delay": 1, "skills": [{ "name": "carapace", "value": 2, "trigger": "on_defend" }] },
|
||||
{ "attack": 15, "health": 71, "armor": 5, "delay": 1, "skills": [{ "name": "carapace", "value": 2, "trigger": "on_defend" }] }
|
||||
],
|
||||
"flavorText": "Each wound seals with a harder shell.",
|
||||
"artKey": "xeno_drone_1"
|
||||
},
|
||||
{
|
||||
"id": "xeno_spitter_1",
|
||||
"name": "Spitter Larva",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "common",
|
||||
"levels": [
|
||||
{ "attack": 2, "health": 12, "armor": 0, "delay": 1, "skills": [{ "name": "venom", "value": 2, "trigger": "on_attack" }] },
|
||||
{ "attack": 3, "health": 18, "armor": 0, "delay": 1, "skills": [{ "name": "venom", "value": 3, "trigger": "on_attack" }] },
|
||||
{ "attack": 5, "health": 27, "armor": 1, "delay": 1, "skills": [{ "name": "venom", "value": 4, "trigger": "on_attack" }] },
|
||||
{ "attack": 7, "health": 41, "armor": 1, "delay": 1, "skills": [{ "name": "venom", "value": 6, "trigger": "on_attack" }] },
|
||||
{ "attack": 10, "health": 61, "armor": 2, "delay": 1, "skills": [{ "name": "venom", "value": 8, "trigger": "on_attack" }] }
|
||||
],
|
||||
"flavorText": "The burning starts slow. It never stops.",
|
||||
"artKey": "xeno_spitter_1"
|
||||
},
|
||||
{
|
||||
"id": "xeno_beetle_1",
|
||||
"name": "Tunnel Beetle",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "common",
|
||||
"levels": [
|
||||
{ "attack": 4, "health": 11, "armor": 0, "delay": 1, "skills": [{ "name": "burrow", "value": 2, "trigger": "passive" }] },
|
||||
{ "attack": 6, "health": 17, "armor": 0, "delay": 1, "skills": [{ "name": "burrow", "value": 2, "trigger": "passive" }] },
|
||||
{ "attack": 9, "health": 25, "armor": 1, "delay": 1, "skills": [{ "name": "burrow", "value": 2, "trigger": "passive" }] },
|
||||
{ "attack": 14, "health": 38, "armor": 1, "delay": 1, "skills": [{ "name": "burrow", "value": 3, "trigger": "passive" }] },
|
||||
{ "attack": 20, "health": 56, "armor": 2, "delay": 1, "skills": [{ "name": "burrow", "value": 3, "trigger": "passive" }] }
|
||||
],
|
||||
"flavorText": "You can't target what you can't see.",
|
||||
"artKey": "xeno_beetle_1"
|
||||
},
|
||||
{
|
||||
"id": "xeno_worker_1",
|
||||
"name": "Hive Worker",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "common",
|
||||
"levels": [
|
||||
{ "attack": 3, "health": 13, "armor": 2, "delay": 1, "skills": [{ "name": "molt", "trigger": "on_turn_start" }] },
|
||||
{ "attack": 5, "health": 20, "armor": 3, "delay": 1, "skills": [{ "name": "molt", "trigger": "on_turn_start" }] },
|
||||
{ "attack": 7, "health": 29, "armor": 4, "delay": 1, "skills": [{ "name": "molt", "trigger": "on_turn_start" }] },
|
||||
{ "attack": 10, "health": 44, "armor": 6, "delay": 1, "skills": [{ "name": "molt", "trigger": "on_turn_start" }] },
|
||||
{ "attack": 15, "health": 66, "armor": 9, "delay": 1, "skills": [{ "name": "molt", "trigger": "on_turn_start" }] }
|
||||
],
|
||||
"flavorText": "Shed the old. Grow something meaner.",
|
||||
"artKey": "xeno_worker_1"
|
||||
},
|
||||
|
||||
{
|
||||
"id": "xeno_guard_1",
|
||||
"name": "Swarm Guard",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "rare",
|
||||
"levels": [
|
||||
{ "attack": 3, "health": 18, "armor": 3, "delay": 1, "skills": [{ "name": "counter", "value": 2, "trigger": "on_defend" }, { "name": "carapace", "value": 1, "trigger": "on_defend" }] },
|
||||
{ "attack": 5, "health": 27, "armor": 5, "delay": 1, "skills": [{ "name": "counter", "value": 3, "trigger": "on_defend" }, { "name": "carapace", "value": 1, "trigger": "on_defend" }] },
|
||||
{ "attack": 7, "health": 41, "armor": 7, "delay": 1, "skills": [{ "name": "counter", "value": 5, "trigger": "on_defend" }, { "name": "carapace", "value": 1, "trigger": "on_defend" }] },
|
||||
{ "attack": 10, "health": 61, "armor": 10, "delay": 1, "skills": [{ "name": "counter", "value": 7, "trigger": "on_defend" }, { "name": "carapace", "value": 2, "trigger": "on_defend" }] },
|
||||
{ "attack": 15, "health": 91, "armor": 15, "delay": 1, "skills": [{ "name": "counter", "value": 10, "trigger": "on_defend" }, { "name": "carapace", "value": 2, "trigger": "on_defend" }] }
|
||||
],
|
||||
"flavorText": "Strike the hive, and the hive strikes back harder.",
|
||||
"artKey": "xeno_guard_1"
|
||||
},
|
||||
{
|
||||
"id": "xeno_mantis_1",
|
||||
"name": "Acid Mantis",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "rare",
|
||||
"levels": [
|
||||
{ "attack": 5, "health": 13, "armor": 1, "delay": 1, "skills": [{ "name": "venom", "value": 2, "trigger": "on_attack" }, { "name": "strike", "value": 1, "trigger": "preAttack" }] },
|
||||
{ "attack": 8, "health": 20, "armor": 2, "delay": 1, "skills": [{ "name": "venom", "value": 3, "trigger": "on_attack" }, { "name": "strike", "value": 2, "trigger": "preAttack" }] },
|
||||
{ "attack": 11, "health": 29, "armor": 2, "delay": 1, "skills": [{ "name": "venom", "value": 4, "trigger": "on_attack" }, { "name": "strike", "value": 2, "trigger": "preAttack" }] },
|
||||
{ "attack": 17, "health": 44, "armor": 3, "delay": 1, "skills": [{ "name": "venom", "value": 6, "trigger": "on_attack" }, { "name": "strike", "value": 3, "trigger": "preAttack" }] },
|
||||
{ "attack": 25, "health": 66, "armor": 5, "delay": 1, "skills": [{ "name": "venom", "value": 8, "trigger": "on_attack" }, { "name": "strike", "value": 5, "trigger": "preAttack" }] }
|
||||
],
|
||||
"flavorText": "Its spit dissolves armor. Its claws dissolve everything else.",
|
||||
"artKey": "xeno_mantis_1"
|
||||
},
|
||||
{
|
||||
"id": "xeno_horror_1",
|
||||
"name": "Burrowing Horror",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "rare",
|
||||
"levels": [
|
||||
{ "attack": 6, "health": 14, "armor": 2, "delay": 2, "skills": [{ "name": "burrow", "value": 3, "trigger": "passive" }, { "name": "swarm", "value": 1, "trigger": "preBattle" }] },
|
||||
{ "attack": 9, "health": 21, "armor": 3, "delay": 2, "skills": [{ "name": "burrow", "value": 3, "trigger": "passive" }, { "name": "swarm", "value": 2, "trigger": "preBattle" }] },
|
||||
{ "attack": 14, "health": 32, "armor": 5, "delay": 2, "skills": [{ "name": "burrow", "value": 3, "trigger": "passive" }, { "name": "swarm", "value": 3, "trigger": "preBattle" }] },
|
||||
{ "attack": 20, "health": 47, "armor": 7, "delay": 2, "skills": [{ "name": "burrow", "value": 4, "trigger": "passive" }, { "name": "swarm", "value": 5, "trigger": "preBattle" }] },
|
||||
{ "attack": 30, "health": 71, "armor": 10, "delay": 2, "skills": [{ "name": "burrow", "value": 4, "trigger": "passive" }, { "name": "swarm", "value": 7, "trigger": "preBattle" }] }
|
||||
],
|
||||
"flavorText": "The ground shook three times. Then nothing moved above it.",
|
||||
"artKey": "xeno_horror_1"
|
||||
},
|
||||
{
|
||||
"id": "xeno_msentinel_1",
|
||||
"name": "Molt Sentinel",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "rare",
|
||||
"levels": [
|
||||
{ "attack": 2, "health": 20, "armor": 3, "delay": 1, "skills": [{ "name": "molt", "trigger": "on_turn_start" }, { "name": "protect", "value": 2, "trigger": "preBattle" }] },
|
||||
{ "attack": 3, "health": 30, "armor": 5, "delay": 1, "skills": [{ "name": "molt", "trigger": "on_turn_start" }, { "name": "protect", "value": 3, "trigger": "preBattle" }] },
|
||||
{ "attack": 5, "health": 45, "armor": 7, "delay": 1, "skills": [{ "name": "molt", "trigger": "on_turn_start" }, { "name": "protect", "value": 5, "trigger": "preBattle" }] },
|
||||
{ "attack": 7, "health": 68, "armor": 10, "delay": 1, "skills": [{ "name": "molt", "trigger": "on_turn_start" }, { "name": "protect", "value": 7, "trigger": "preBattle" }] },
|
||||
{ "attack": 10, "health": 101, "armor": 15, "delay": 1, "skills": [{ "name": "molt", "trigger": "on_turn_start" }, { "name": "protect", "value": 10, "trigger": "preBattle" }] }
|
||||
],
|
||||
"flavorText": "It sheds its skin to shield the swarm.",
|
||||
"artKey": "xeno_msentinel_1"
|
||||
},
|
||||
{
|
||||
"id": "xeno_parasite_1",
|
||||
"name": "Brood Parasite",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "rare",
|
||||
"levels": [
|
||||
{ "attack": 4, "health": 12, "armor": 1, "delay": 2, "skills": [{ "name": "enfeeble", "value": 2, "trigger": "preBattle" }, { "name": "venom", "value": 1, "trigger": "on_attack" }] },
|
||||
{ "attack": 6, "health": 18, "armor": 2, "delay": 2, "skills": [{ "name": "enfeeble", "value": 3, "trigger": "preBattle" }, { "name": "venom", "value": 2, "trigger": "on_attack" }] },
|
||||
{ "attack": 9, "health": 27, "armor": 3, "delay": 2, "skills": [{ "name": "enfeeble", "value": 5, "trigger": "preBattle" }, { "name": "venom", "value": 2, "trigger": "on_attack" }] },
|
||||
{ "attack": 14, "health": 41, "armor": 5, "delay": 2, "skills": [{ "name": "enfeeble", "value": 7, "trigger": "preBattle" }, { "name": "venom", "value": 3, "trigger": "on_attack" }] },
|
||||
{ "attack": 20, "health": 61, "armor": 7, "delay": 2, "skills": [{ "name": "enfeeble", "value": 10, "trigger": "preBattle" }, { "name": "venom", "value": 5, "trigger": "on_attack" }] }
|
||||
],
|
||||
"flavorText": "It crawls inside. You stop fighting. Then you start fighting for them.",
|
||||
"artKey": "xeno_parasite_1"
|
||||
},
|
||||
|
||||
{
|
||||
"id": "xeno_hiveguard_1",
|
||||
"name": "Hive Guardian",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "epic",
|
||||
"levels": [
|
||||
{ "attack": 6, "health": 18, "armor": 3, "delay": 2, "skills": [{ "name": "carapace", "value": 2, "trigger": "on_defend" }, { "name": "armored", "value": 2, "trigger": "passive" }] },
|
||||
{ "attack": 9, "health": 27, "armor": 5, "delay": 2, "skills": [{ "name": "carapace", "value": 2, "trigger": "on_defend" }, { "name": "armored", "value": 3, "trigger": "passive" }] },
|
||||
{ "attack": 14, "health": 41, "armor": 7, "delay": 2, "skills": [{ "name": "carapace", "value": 2, "trigger": "on_defend" }, { "name": "armored", "value": 5, "trigger": "passive" }] },
|
||||
{ "attack": 20, "health": 61, "armor": 10, "delay": 2, "skills": [{ "name": "carapace", "value": 3, "trigger": "on_defend" }, { "name": "armored", "value": 7, "trigger": "passive" }] },
|
||||
{ "attack": 30, "health": 91, "armor": 15, "delay": 2, "skills": [{ "name": "carapace", "value": 3, "trigger": "on_defend" }, { "name": "armored", "value": 10, "trigger": "passive" }] }
|
||||
],
|
||||
"flavorText": "Blades shatter. Shells harden. The hive remembers every wound.",
|
||||
"artKey": "xeno_hiveguard_1"
|
||||
},
|
||||
{
|
||||
"id": "xeno_wyrm_1",
|
||||
"name": "Venom Wyrm",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "epic",
|
||||
"levels": [
|
||||
{ "attack": 6, "health": 16, "armor": 2, "delay": 2, "skills": [{ "name": "venom", "value": 3, "trigger": "on_attack", "all": true }] },
|
||||
{ "attack": 9, "health": 24, "armor": 3, "delay": 2, "skills": [{ "name": "venom", "value": 4, "trigger": "on_attack", "all": true }] },
|
||||
{ "attack": 14, "health": 36, "armor": 5, "delay": 2, "skills": [{ "name": "venom", "value": 6, "trigger": "on_attack", "all": true }] },
|
||||
{ "attack": 20, "health": 54, "armor": 7, "delay": 2, "skills": [{ "name": "venom", "value": 8, "trigger": "on_attack", "all": true }] },
|
||||
{ "attack": 30, "health": 81, "armor": 10, "delay": 2, "skills": [{ "name": "venom", "value": 12, "trigger": "on_attack", "all": true }] }
|
||||
],
|
||||
"flavorText": "A single drop paralyzes. A stream dissolves.",
|
||||
"artKey": "xeno_wyrm_1"
|
||||
},
|
||||
{
|
||||
"id": "xeno_tyrant_1",
|
||||
"name": "Swarm Tyrant",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "epic",
|
||||
"levels": [
|
||||
{ "attack": 7, "health": 16, "armor": 2, "delay": 2, "skills": [{ "name": "swarm", "value": 2, "trigger": "preBattle" }, { "name": "rally", "value": 2, "trigger": "preBattle" }] },
|
||||
{ "attack": 11, "health": 24, "armor": 3, "delay": 2, "skills": [{ "name": "swarm", "value": 3, "trigger": "preBattle" }, { "name": "rally", "value": 3, "trigger": "preBattle" }] },
|
||||
{ "attack": 16, "health": 36, "armor": 5, "delay": 2, "skills": [{ "name": "swarm", "value": 5, "trigger": "preBattle" }, { "name": "rally", "value": 5, "trigger": "preBattle" }] },
|
||||
{ "attack": 24, "health": 54, "armor": 7, "delay": 2, "skills": [{ "name": "swarm", "value": 7, "trigger": "preBattle" }, { "name": "rally", "value": 7, "trigger": "preBattle" }] },
|
||||
{ "attack": 35, "health": 81, "armor": 10, "delay": 2, "skills": [{ "name": "swarm", "value": 10, "trigger": "preBattle" }, { "name": "rally", "value": 10, "trigger": "preBattle" }] }
|
||||
],
|
||||
"flavorText": "Where one xeno stands, a thousand follow.",
|
||||
"artKey": "xeno_tyrant_1"
|
||||
},
|
||||
{
|
||||
"id": "xeno_queen_1",
|
||||
"name": "Burrow Queen",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "epic",
|
||||
"levels": [
|
||||
{ "attack": 6, "health": 20, "armor": 3, "delay": 3, "skills": [{ "name": "burrow", "value": 3, "trigger": "passive" }, { "name": "hive_link", "value": 2, "trigger": "on_death" }] },
|
||||
{ "attack": 9, "health": 30, "armor": 5, "delay": 3, "skills": [{ "name": "burrow", "value": 3, "trigger": "passive" }, { "name": "hive_link", "value": 3, "trigger": "on_death" }] },
|
||||
{ "attack": 14, "health": 45, "armor": 7, "delay": 3, "skills": [{ "name": "burrow", "value": 3, "trigger": "passive" }, { "name": "hive_link", "value": 5, "trigger": "on_death" }] },
|
||||
{ "attack": 20, "health": 68, "armor": 10, "delay": 3, "skills": [{ "name": "burrow", "value": 4, "trigger": "passive" }, { "name": "hive_link", "value": 7, "trigger": "on_death" }] },
|
||||
{ "attack": 30, "health": 101, "armor": 15, "delay": 3, "skills": [{ "name": "burrow", "value": 4, "trigger": "passive" }, { "name": "hive_link", "value": 10, "trigger": "on_death" }] }
|
||||
],
|
||||
"flavorText": "Kill the queen and the swarm grows angrier.",
|
||||
"artKey": "xeno_queen_1"
|
||||
},
|
||||
{
|
||||
"id": "xeno_colossus_1",
|
||||
"name": "Chitin Colossus",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "epic",
|
||||
"levels": [
|
||||
{ "attack": 7, "health": 18, "armor": 3, "delay": 3, "skills": [{ "name": "molt", "trigger": "on_turn_start" }, { "name": "swipe", "value": 2, "trigger": "preAttack" }] },
|
||||
{ "attack": 11, "health": 27, "armor": 5, "delay": 3, "skills": [{ "name": "molt", "trigger": "on_turn_start" }, { "name": "swipe", "value": 3, "trigger": "preAttack" }] },
|
||||
{ "attack": 16, "health": 41, "armor": 7, "delay": 3, "skills": [{ "name": "molt", "trigger": "on_turn_start" }, { "name": "swipe", "value": 5, "trigger": "preAttack" }] },
|
||||
{ "attack": 24, "health": 61, "armor": 10, "delay": 3, "skills": [{ "name": "molt", "trigger": "on_turn_start" }, { "name": "swipe", "value": 7, "trigger": "preAttack" }] },
|
||||
{ "attack": 35, "health": 91, "armor": 15, "delay": 3, "skills": [{ "name": "molt", "trigger": "on_turn_start" }, { "name": "swipe", "value": 10, "trigger": "preAttack" }] }
|
||||
],
|
||||
"flavorText": "It sheds its skin, and the ground splits beneath its weight.",
|
||||
"artKey": "xeno_colossus_1"
|
||||
},
|
||||
|
||||
{
|
||||
"id": "xeno_apex_1",
|
||||
"name": "Apex Predator",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "legendary",
|
||||
"levels": [
|
||||
{ "attack": 8, "health": 22, "armor": 3, "delay": 3, "skills": [{ "name": "venom", "value": 3, "trigger": "on_attack" }, { "name": "flurry", "value": 1, "trigger": "passive" }, { "name": "hive_link", "value": 3, "trigger": "on_death" }] },
|
||||
{ "attack": 12, "health": 33, "armor": 5, "delay": 3, "skills": [{ "name": "venom", "value": 4, "trigger": "on_attack" }, { "name": "flurry", "value": 1, "trigger": "passive" }, { "name": "hive_link", "value": 5, "trigger": "on_death" }] },
|
||||
{ "attack": 18, "health": 50, "armor": 7, "delay": 3, "skills": [{ "name": "venom", "value": 6, "trigger": "on_attack" }, { "name": "flurry", "value": 1, "trigger": "passive" }, { "name": "hive_link", "value": 7, "trigger": "on_death" }] },
|
||||
{ "attack": 27, "health": 74, "armor": 10, "delay": 3, "skills": [{ "name": "venom", "value": 8, "trigger": "on_attack" }, { "name": "flurry", "value": 1, "trigger": "passive" }, { "name": "hive_link", "value": 10, "trigger": "on_death" }] },
|
||||
{ "attack": 41, "health": 111, "armor": 15, "delay": 3, "skills": [{ "name": "venom", "value": 12, "trigger": "on_attack" }, { "name": "flurry", "value": 1, "trigger": "passive" }, { "name": "hive_link", "value": 15, "trigger": "on_death" }] }
|
||||
],
|
||||
"flavorText": "It was here long before us. It will be here long after.",
|
||||
"artKey": "xeno_apex_1"
|
||||
},
|
||||
{
|
||||
"id": "xeno_leviathan_1",
|
||||
"name": "Hive Leviathan",
|
||||
"faction": "xeno",
|
||||
"type": "assault",
|
||||
"rarity": "legendary",
|
||||
"levels": [
|
||||
{ "attack": 8, "health": 20, "armor": 4, "delay": 3, "skills": [{ "name": "carapace", "value": 2, "trigger": "on_defend" }, { "name": "swarm", "value": 2, "trigger": "preBattle" }, { "name": "molt", "trigger": "on_turn_start" }] },
|
||||
{ "attack": 12, "health": 30, "armor": 6, "delay": 3, "skills": [{ "name": "carapace", "value": 2, "trigger": "on_defend" }, { "name": "swarm", "value": 3, "trigger": "preBattle" }, { "name": "molt", "trigger": "on_turn_start" }] },
|
||||
{ "attack": 18, "health": 45, "armor": 9, "delay": 3, "skills": [{ "name": "carapace", "value": 2, "trigger": "on_defend" }, { "name": "swarm", "value": 5, "trigger": "preBattle" }, { "name": "molt", "trigger": "on_turn_start" }] },
|
||||
{ "attack": 27, "health": 68, "armor": 14, "delay": 3, "skills": [{ "name": "carapace", "value": 3, "trigger": "on_defend" }, { "name": "swarm", "value": 7, "trigger": "preBattle" }, { "name": "molt", "trigger": "on_turn_start" }] },
|
||||
{ "attack": 41, "health": 101, "armor": 20, "delay": 3, "skills": [{ "name": "carapace", "value": 3, "trigger": "on_defend" }, { "name": "swarm", "value": 10, "trigger": "preBattle" }, { "name": "molt", "trigger": "on_turn_start" }] }
|
||||
],
|
||||
"flavorText": "It does not sleep. It does not forget. It only grows.",
|
||||
"artKey": "xeno_leviathan_1"
|
||||
}
|
||||
]
|
||||
|
|
@ -361,5 +361,126 @@
|
|||
]
|
||||
},
|
||||
"lore": "The tide rises. Every drop of blood spilled feeds the Countess. She is ancient, she is patient, and she has had enough of slumber."
|
||||
},
|
||||
|
||||
{
|
||||
"id": "mission_014",
|
||||
"name": "Emergence",
|
||||
"chapter": 3,
|
||||
"campaignId": "campaign_xeno",
|
||||
"enemyLevel": 1,
|
||||
"description": "Strange tunnels have appeared near the eastern settlements. Something crawls within.",
|
||||
"unlockCondition": null,
|
||||
"opponent": {
|
||||
"commander": "xeno_cmd_1",
|
||||
"cards": ["xeno_drone_1", "xeno_drone_1", "xeno_drone_1", "xeno_spitter_1", "xeno_spitter_1"]
|
||||
},
|
||||
"rewards": { "gold": 150, "cards": ["xeno_drone_1"] },
|
||||
"lore": "The first chitin fragments were dismissed as natural formations. Then the formations moved."
|
||||
},
|
||||
{
|
||||
"id": "mission_015",
|
||||
"name": "Hive Scouts",
|
||||
"chapter": 3,
|
||||
"campaignId": "campaign_xeno",
|
||||
"enemyLevel": 1,
|
||||
"description": "Advance scouts test the perimeter. The hive is probing our defenses.",
|
||||
"unlockCondition": "mission_014",
|
||||
"opponent": {
|
||||
"commander": "xeno_cmd_1",
|
||||
"cards": ["xeno_drone_1", "xeno_drone_1", "xeno_spitter_1", "xeno_beetle_1", "xeno_beetle_1", "xeno_worker_1"]
|
||||
},
|
||||
"rewards": { "gold": 200, "cards": ["xeno_spitter_1"] },
|
||||
"lore": "They move in patterns too precise for instinct. Something is directing them."
|
||||
},
|
||||
{
|
||||
"id": "mission_016",
|
||||
"name": "Tunnel War",
|
||||
"chapter": 3,
|
||||
"campaignId": "campaign_xeno",
|
||||
"enemyLevel": 1,
|
||||
"description": "They attack from below. Our formations mean nothing underground.",
|
||||
"unlockCondition": "mission_015",
|
||||
"opponent": {
|
||||
"commander": "xeno_cmd_1",
|
||||
"cards": ["xeno_drone_1", "xeno_beetle_1", "xeno_worker_1", "xeno_guard_1", "xeno_mantis_1", "xeno_horror_1"]
|
||||
},
|
||||
"rewards": { "gold": 250, "cards": ["xeno_beetle_1"] },
|
||||
"lore": "The tunnels go deeper than any mine. Deeper than any root. Something vast breathes in the dark."
|
||||
},
|
||||
{
|
||||
"id": "mission_017",
|
||||
"name": "The Swarm Rises",
|
||||
"chapter": 3,
|
||||
"campaignId": "campaign_xeno",
|
||||
"enemyLevel": 1,
|
||||
"description": "War Drone Alpha takes the field. The swarm has found its general.",
|
||||
"unlockCondition": "mission_016",
|
||||
"opponent": {
|
||||
"commander": "xeno_cmd_2",
|
||||
"cards": ["xeno_drone_1", "xeno_spitter_1", "xeno_guard_1", "xeno_mantis_1", "xeno_horror_1", "xeno_msentinel_1", "xeno_parasite_1"]
|
||||
},
|
||||
"rewards": { "gold": 300, "cards": ["xeno_worker_1"] },
|
||||
"lore": "The Alpha moved differently from the others. With purpose. With malice."
|
||||
},
|
||||
{
|
||||
"id": "mission_018",
|
||||
"name": "Acid Rain",
|
||||
"chapter": 3,
|
||||
"campaignId": "campaign_xeno",
|
||||
"enemyLevel": 1,
|
||||
"description": "The venom wyrms have arrived. The very air burns.",
|
||||
"unlockCondition": "mission_017",
|
||||
"opponent": {
|
||||
"commander": "xeno_cmd_2",
|
||||
"cards": ["xeno_spitter_1", "xeno_mantis_1", "xeno_guard_1", "xeno_horror_1", "xeno_parasite_1", "xeno_wyrm_1", "xeno_hiveguard_1"]
|
||||
},
|
||||
"rewards": { "gold": 350, "cards": ["xeno_guard_1"] },
|
||||
"lore": "Our shields corroded in minutes. Our armor lasted seconds. Only retreat lasted."
|
||||
},
|
||||
{
|
||||
"id": "mission_019",
|
||||
"name": "Queen's Nest",
|
||||
"chapter": 3,
|
||||
"campaignId": "campaign_xeno",
|
||||
"enemyLevel": 1,
|
||||
"description": "The Burrow Queens breed beneath our feet. Destroy the nests before they multiply.",
|
||||
"unlockCondition": "mission_018",
|
||||
"opponent": {
|
||||
"commander": "xeno_cmd_2",
|
||||
"cards": ["xeno_beetle_1", "xeno_guard_1", "xeno_msentinel_1", "xeno_hiveguard_1", "xeno_tyrant_1", "xeno_queen_1", "xeno_colossus_1"]
|
||||
},
|
||||
"rewards": { "gold": 400, "cards": ["xeno_mantis_1"] },
|
||||
"lore": "Every queen killed made the swarm fight harder. We began to understand the hive link."
|
||||
},
|
||||
{
|
||||
"id": "mission_020",
|
||||
"name": "Apex Hunt",
|
||||
"chapter": 3,
|
||||
"campaignId": "campaign_xeno",
|
||||
"enemyLevel": 1,
|
||||
"description": "The Apex Predator has emerged. Our best warriors have gone to meet it. Few will return.",
|
||||
"unlockCondition": "mission_019",
|
||||
"opponent": {
|
||||
"commander": "xeno_cmd_2",
|
||||
"cards": ["xeno_mantis_1", "xeno_horror_1", "xeno_hiveguard_1", "xeno_wyrm_1", "xeno_tyrant_1", "xeno_queen_1", "xeno_colossus_1", "xeno_apex_1"]
|
||||
},
|
||||
"rewards": { "gold": 450, "cards": ["xeno_horror_1"] },
|
||||
"lore": "It moved faster than anything that size should. The venom hit before we even saw it."
|
||||
},
|
||||
{
|
||||
"id": "mission_021",
|
||||
"name": "The Hive Mind",
|
||||
"chapter": 3,
|
||||
"campaignId": "campaign_xeno",
|
||||
"enemyLevel": 1,
|
||||
"description": "One mind controls them all. Sever it, or humanity falls.",
|
||||
"unlockCondition": "mission_020",
|
||||
"opponent": {
|
||||
"commander": "xeno_cmd_3",
|
||||
"cards": ["xeno_guard_1", "xeno_hiveguard_1", "xeno_wyrm_1", "xeno_tyrant_1", "xeno_queen_1", "xeno_colossus_1", "xeno_apex_1", "xeno_leviathan_1"]
|
||||
},
|
||||
"rewards": { "gold": 500, "cards": ["xeno_hiveguard_1"] },
|
||||
"lore": "It spoke through the swarm. Not words. Something older. The feeling of being consumed."
|
||||
}
|
||||
]
|
||||
|
|
@ -164,5 +164,49 @@
|
|||
"description": "When below 50% HP, gain +value ATK (recalculated each attack).",
|
||||
"trigger": "passive",
|
||||
"category": "offense"
|
||||
},
|
||||
{
|
||||
"name": "spawn",
|
||||
"description": "Summon a Xeno Drone into an empty lane.",
|
||||
"trigger": "on_turn_start",
|
||||
"category": "special"
|
||||
},
|
||||
{
|
||||
"name": "swarm",
|
||||
"description": "Gain +value ATK for each allied xeno card on the field.",
|
||||
"trigger": "preBattle",
|
||||
"category": "offense"
|
||||
},
|
||||
{
|
||||
"name": "carapace",
|
||||
"description": "When damaged, permanently gain +value armor.",
|
||||
"trigger": "on_defend",
|
||||
"category": "defense"
|
||||
},
|
||||
{
|
||||
"name": "venom",
|
||||
"description": "Apply value venom stacks to target. Each turn, deal damage equal to stacks then reduce by 1. Only targets cards without existing venom.",
|
||||
"allDescription": "Apply value venom stacks to ALL enemy cards without existing venom.",
|
||||
"trigger": "on_attack",
|
||||
"category": "offense",
|
||||
"supportsAll": true
|
||||
},
|
||||
{
|
||||
"name": "burrow",
|
||||
"description": "Cannot be targeted by enemy skills for value turns. Can still attack normally.",
|
||||
"trigger": "passive",
|
||||
"category": "defense"
|
||||
},
|
||||
{
|
||||
"name": "molt",
|
||||
"description": "Convert all current armor into HP healing, then armor resets to 0.",
|
||||
"trigger": "on_turn_start",
|
||||
"category": "defense"
|
||||
},
|
||||
{
|
||||
"name": "hive_link",
|
||||
"description": "On death, all allied xeno cards gain +value ATK permanently.",
|
||||
"trigger": "on_death",
|
||||
"category": "offense"
|
||||
}
|
||||
]
|
||||
|
|
@ -65,6 +65,7 @@ export class CombatEngine {
|
|||
const tick = cards => {
|
||||
for (const c of cards) {
|
||||
if (c.currentDelay > 0) c.currentDelay--;
|
||||
if (c.burrowTurns > 0) c.burrowTurns--;
|
||||
}
|
||||
};
|
||||
tick(this.playerLanes);
|
||||
|
|
@ -85,6 +86,22 @@ export class CombatEngine {
|
|||
applyRupture(this.opponentLanes);
|
||||
}
|
||||
|
||||
// Apply venom damage to all cards
|
||||
_venomPhase() {
|
||||
const applyVenom = cards => {
|
||||
for (const c of cards) {
|
||||
if (c.venomStacks > 0) {
|
||||
c.currentHP -= c.venomStacks;
|
||||
this._log(`${c.name} takes ${c.venomStacks} venom damage`);
|
||||
this.events.push({ type: 'venomTick', card: c, damage: c.venomStacks });
|
||||
c.venomStacks--;
|
||||
}
|
||||
}
|
||||
};
|
||||
applyVenom(this.playerLanes);
|
||||
applyVenom(this.opponentLanes);
|
||||
}
|
||||
|
||||
// Tick jam timers
|
||||
_jamPhase() {
|
||||
const tickJam = cards => {
|
||||
|
|
@ -98,28 +115,44 @@ export class CombatEngine {
|
|||
|
||||
// Fire on_turn_start skills for commanders
|
||||
_commanderSkillPhase() {
|
||||
if (this.playerCommander.currentHP > 0) {
|
||||
for (const s of this.playerCommander.skills) {
|
||||
if (s.trigger === 'on_turn_start') {
|
||||
this.skillProcessor.process(s, this.playerCommander, null,
|
||||
[this.playerCommander, ...this.playerLanes],
|
||||
[this.opponentCommander, ...this.opponentLanes],
|
||||
{ enemyCommander: this.opponentCommander, cardManager: this.cardManager }
|
||||
);
|
||||
const processCommander = (commander, lanes, enemyCommander, enemyLanes) => {
|
||||
if (commander.currentHP <= 0) return;
|
||||
for (const s of commander.skills) {
|
||||
if (s.trigger !== 'on_turn_start') continue;
|
||||
const ctx = { enemyCommander, cardManager: this.cardManager, laneCards: lanes };
|
||||
this.skillProcessor.process(s, commander, null,
|
||||
[commander, ...lanes], [enemyCommander, ...enemyLanes], ctx);
|
||||
if (ctx.spawnedDrone && lanes.length < MAX_LANES) {
|
||||
lanes.push(ctx.spawnedDrone);
|
||||
const side = (commander === this.playerCommander) ? 'player' : 'opponent';
|
||||
this._log(`${commander.name} spawns a ${ctx.spawnedDrone.name}`);
|
||||
this.events.push({ type: 'spawn', side, card: ctx.spawnedDrone });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.opponentCommander.currentHP > 0) {
|
||||
for (const s of this.opponentCommander.skills) {
|
||||
if (s.trigger === 'on_turn_start') {
|
||||
this.skillProcessor.process(s, this.opponentCommander, null,
|
||||
[this.opponentCommander, ...this.opponentLanes],
|
||||
[this.playerCommander, ...this.playerLanes],
|
||||
{ enemyCommander: this.playerCommander, cardManager: this.cardManager }
|
||||
);
|
||||
};
|
||||
processCommander(this.playerCommander, this.playerLanes, this.opponentCommander, this.opponentLanes);
|
||||
processCommander(this.opponentCommander, this.opponentLanes, this.playerCommander, this.playerLanes);
|
||||
}
|
||||
|
||||
// Fire on_turn_start skills for lane cards (e.g. molt)
|
||||
_cardOnTurnStartPhase() {
|
||||
const processCards = (cards, commander, enemyCommander, enemyLanes) => {
|
||||
for (const card of cards) {
|
||||
if (card.currentHP <= 0 || card.currentDelay > 0) continue;
|
||||
for (const s of card.skills) {
|
||||
if (s.trigger !== 'on_turn_start') continue;
|
||||
const ctx = { enemyCommander, cardManager: this.cardManager, laneCards: cards };
|
||||
this.skillProcessor.process(s, card, null,
|
||||
[commander, ...cards], [enemyCommander, ...enemyLanes], ctx);
|
||||
if (s.name === 'molt' && ctx.moltHeal > 0) {
|
||||
this._log(`${card.name} molts: heals ${ctx.moltHeal}, loses ${ctx.moltArmorLost} armor`);
|
||||
this.events.push({ type: 'molt', card, heal: ctx.moltHeal, armorLost: ctx.moltArmorLost });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
processCards(this.playerLanes, this.playerCommander, this.opponentCommander, this.opponentLanes);
|
||||
processCards(this.opponentLanes, this.opponentCommander, this.playerCommander, this.playerLanes);
|
||||
}
|
||||
|
||||
// Cards attack across lanes
|
||||
|
|
@ -196,6 +229,12 @@ export class CombatEngine {
|
|||
if (onAtkCtx.weakenAllTargets) {
|
||||
this.events.push({ type: 'weakenAll', attacker, targets: onAtkCtx.weakenAllTargets, side });
|
||||
}
|
||||
if (onAtkCtx.venomTarget) {
|
||||
this.events.push({ type: 'venomApply', attacker, target: onAtkCtx.venomTarget, stacks: onAtkCtx.venomStacks, side });
|
||||
}
|
||||
if (onAtkCtx.venomAllTargets) {
|
||||
this.events.push({ type: 'venomApplyAll', attacker, targets: onAtkCtx.venomAllTargets, side });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -208,7 +247,7 @@ export class CombatEngine {
|
|||
}
|
||||
}
|
||||
|
||||
// counter skill on defender — only fires if defender survived the main attack
|
||||
// counter and carapace skills on defender — only fire if defender survived the main attack
|
||||
if (currentTarget.currentHP > 0) {
|
||||
for (const s of currentTarget.skills) {
|
||||
if (s.name === 'counter') {
|
||||
|
|
@ -223,16 +262,30 @@ export class CombatEngine {
|
|||
if (attacker.currentHP <= 0) {
|
||||
this._log(`${attacker.name} is destroyed by counter`);
|
||||
this.events.push({ type: 'death', card: attacker, side });
|
||||
this._processOnDeath(attacker, alliedLanes, side);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.name === 'carapace' && dmg > 0) {
|
||||
const carapaceCtx = { trigger: 'on_defend' };
|
||||
this.skillProcessor.process(s, currentTarget, attacker,
|
||||
enemyLanes.filter(c => c.currentHP > 0),
|
||||
alliedLanes.filter(c => c.currentHP > 0),
|
||||
carapaceCtx);
|
||||
if (carapaceCtx.carapaceGain > 0) {
|
||||
this._log(`${currentTarget.name} carapace: +${carapaceCtx.carapaceGain} ARM`);
|
||||
this.events.push({ type: 'carapace', card: currentTarget, gain: carapaceCtx.carapaceGain });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if defender died
|
||||
if (currentTarget.currentHP <= 0) {
|
||||
this._log(`${currentTarget.name} is destroyed`);
|
||||
this.events.push({ type: 'death', card: currentTarget, side: side === 'player' ? 'opponent' : 'player' });
|
||||
const defenderSide = side === 'player' ? 'opponent' : 'player';
|
||||
this.events.push({ type: 'death', card: currentTarget, side: defenderSide });
|
||||
this._processOnDeath(currentTarget, enemyLanes, defenderSide);
|
||||
}
|
||||
} else {
|
||||
// Attack commander directly (pierce has already reduced currentArmor if applicable)
|
||||
|
|
@ -253,6 +306,20 @@ export class CombatEngine {
|
|||
}
|
||||
}
|
||||
|
||||
// Process on_death skills for a card that just died
|
||||
_processOnDeath(deadCard, alliedLanes, side) {
|
||||
for (const s of deadCard.skills) {
|
||||
if (s.trigger !== 'on_death') continue;
|
||||
const allies = alliedLanes.filter(a => a.currentHP > 0);
|
||||
const ctx = {};
|
||||
this.skillProcessor.process(s, deadCard, null, allies, [], ctx);
|
||||
if (s.name === 'hive_link' && ctx.hiveLinkTargets?.length > 0) {
|
||||
this._log(`${deadCard.name} hive_link: +${s.value} ATK to ${ctx.hiveLinkTargets.length} allies`);
|
||||
this.events.push({ type: 'hive_link', source: deadCard, targets: ctx.hiveLinkTargets, gain: s.value, side });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove dead cards, compact lanes
|
||||
_deathCheck() {
|
||||
this.playerLanes = this.playerLanes.filter(c => c.currentHP > 0);
|
||||
|
|
@ -315,7 +382,9 @@ export class CombatEngine {
|
|||
for (const c of this.playerHand) { if (c.currentDelay > 0) c.currentDelay--; }
|
||||
for (const c of this.ai.hand) { if (c.currentDelay > 0) c.currentDelay--; }
|
||||
this._commanderSkillPhase();
|
||||
this._cardOnTurnStartPhase();
|
||||
this._rupturePhase();
|
||||
this._venomPhase();
|
||||
this._jamPhase();
|
||||
this._attackPhase();
|
||||
this._deathCheck();
|
||||
|
|
@ -356,7 +425,10 @@ export class CombatEngine {
|
|||
// Tick AI hand cards that were not deployed this turn
|
||||
for (const c of this.ai.hand) { if (c.currentDelay > 0) c.currentDelay--; }
|
||||
this._commanderSkillPhase();
|
||||
this._cardOnTurnStartPhase();
|
||||
this._rupturePhase();
|
||||
this._venomPhase();
|
||||
this._deathCheck(); // remove cards killed by rupture/venom before combat
|
||||
this._jamPhase();
|
||||
this._preBattlePhase();
|
||||
// NOTE: No _deathCheck() here — dead cards stay in lanes so the scene can
|
||||
|
|
@ -401,6 +473,10 @@ export class CombatEngine {
|
|||
buffs.push({ skill: 'legion', source: card, target: card, amount: ctx.legionGain });
|
||||
this._log(`${card.name} legion: +${ctx.legionGain} ATK`);
|
||||
}
|
||||
if (s.name === 'swarm' && ctx.swarmCard) {
|
||||
buffs.push({ skill: 'swarm', source: card, target: card, amount: ctx.swarmGain });
|
||||
this._log(`${card.name} swarm: +${ctx.swarmGain} ATK`);
|
||||
}
|
||||
if (s.name === 'bloodrage' && ctx.bloodrageGain) {
|
||||
buffs.push({ skill: 'bloodrage', source: card, target: card, amount: ctx.bloodrageGain });
|
||||
this._log(`${card.name} bloodrage: +${ctx.bloodrageGain} ATK`);
|
||||
|
|
@ -723,7 +799,9 @@ export class CombatEngine {
|
|||
this._deployPhase();
|
||||
this._activationPhase();
|
||||
this._commanderSkillPhase();
|
||||
this._cardOnTurnStartPhase();
|
||||
this._rupturePhase();
|
||||
this._venomPhase();
|
||||
this._jamPhase();
|
||||
this._attackPhase();
|
||||
this._deathCheck();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export class SkillProcessor {
|
|||
}
|
||||
|
||||
strike(skill, attacker, defender, allies, enemies, context) {
|
||||
if (!skill.all && defender?.burrowTurns > 0) return;
|
||||
if (skill.all) {
|
||||
const targets = enemies.length > 0 ? enemies.filter(e => e.currentHP > 0) : (context?.enemyCommander ? [context.enemyCommander] : []);
|
||||
if (targets.length === 0) return;
|
||||
|
|
@ -73,6 +74,7 @@ export class SkillProcessor {
|
|||
}
|
||||
|
||||
rupture(skill, attacker, defender, allies, enemies, context) {
|
||||
if (!skill.all && defender?.burrowTurns > 0) return;
|
||||
if (skill.all) {
|
||||
const targets = enemies.filter(e => e.currentHP > 0);
|
||||
if (targets.length === 0) return;
|
||||
|
|
@ -189,7 +191,7 @@ export class SkillProcessor {
|
|||
|
||||
// Primary: directly opposite card
|
||||
const primary = enemyLaneCards[idx];
|
||||
if (primary?.currentHP > 0) {
|
||||
if (primary?.currentHP > 0 && !(primary.burrowTurns > 0)) {
|
||||
const reductions = this._applyJamReductions(primary, primaryAmount);
|
||||
if (reductions.length > 0) {
|
||||
primary._tempBuffs = primary._tempBuffs || [];
|
||||
|
|
@ -252,6 +254,7 @@ export class SkillProcessor {
|
|||
if (idx < 0 || idx >= enemyLaneCards.length) return;
|
||||
const target = enemyLaneCards[idx];
|
||||
if (!target || target.currentHP <= 0) return;
|
||||
if (target.burrowTurns > 0) return;
|
||||
const amount = Math.min(skill.value, target.currentAttack);
|
||||
target.currentAttack = Math.max(0, target.currentAttack - amount);
|
||||
// Store negative amount so _removeAndCollectDebuffs reverses it by adding back
|
||||
|
|
@ -264,6 +267,7 @@ export class SkillProcessor {
|
|||
}
|
||||
|
||||
weaken(skill, attacker, defender, allies, enemies, context) {
|
||||
if (!skill.all && defender?.burrowTurns > 0) return;
|
||||
if (skill.all) {
|
||||
const targets = enemies.filter(e => e.currentHP > 0);
|
||||
if (targets.length === 0) return;
|
||||
|
|
@ -319,9 +323,11 @@ export class SkillProcessor {
|
|||
if (context) context.mortarAllTargets = allTargets;
|
||||
return;
|
||||
}
|
||||
const validEnemies = enemies.filter(e => e.currentHP > 0 && !(e.burrowTurns > 0));
|
||||
if (validEnemies.length === 0) return;
|
||||
const rng = context?.rng;
|
||||
const idx = rng ? rng.nextInt(0, enemies.length - 1) : Math.floor(Math.random() * enemies.length);
|
||||
const target = enemies[idx];
|
||||
const idx = rng ? rng.nextInt(0, validEnemies.length - 1) : Math.floor(Math.random() * validEnemies.length);
|
||||
const target = validEnemies[idx];
|
||||
const dmg = Math.max(0, skill.value - Math.max(0, target.currentArmor));
|
||||
target.currentHP -= dmg;
|
||||
if (context) {
|
||||
|
|
@ -432,4 +438,77 @@ export class SkillProcessor {
|
|||
if (context) { context.bloodrageCard = attacker; context.bloodrageGain = skill.value; }
|
||||
}
|
||||
}
|
||||
|
||||
// ── Xeno skills ─────────────────────────────────────────────────────────────
|
||||
|
||||
spawn(skill, attacker, defender, allies, enemies, context) {
|
||||
const laneCards = context?.laneCards || [];
|
||||
if (laneCards.length >= 4) return; // no empty lane
|
||||
const drone = context.cardManager.createInstance('xeno_drone_token', attacker.level || 1);
|
||||
drone.currentDelay = 0;
|
||||
context.spawnedDrone = drone;
|
||||
}
|
||||
|
||||
swarm(skill, attacker, defender, allies, enemies, context) {
|
||||
const xenoAllies = allies.filter(a =>
|
||||
a.faction === 'xeno' && a !== attacker && a.currentHP > 0 && a.type !== 'commander'
|
||||
);
|
||||
const gain = skill.value * xenoAllies.length;
|
||||
if (gain <= 0) return;
|
||||
attacker.currentAttack += gain;
|
||||
attacker._tempBuffs = attacker._tempBuffs || [];
|
||||
attacker._tempBuffs.push({ stat: 'currentAttack', amount: gain, source: 'swarm' });
|
||||
if (context) { context.swarmCard = attacker; context.swarmGain = gain; }
|
||||
}
|
||||
|
||||
carapace(skill, attacker, defender, allies, enemies, context) {
|
||||
// attacker is the defending card (the one with carapace)
|
||||
attacker.currentArmor += skill.value;
|
||||
attacker.armor += skill.value; // permanent base increase
|
||||
if (context) context.carapaceGain = skill.value;
|
||||
}
|
||||
|
||||
venom(skill, attacker, defender, allies, enemies, context) {
|
||||
if (skill.all) {
|
||||
const targets = enemies.filter(e => e.currentHP > 0 && !(e.venomStacks > 0));
|
||||
if (targets.length === 0) return;
|
||||
const allTargets = [];
|
||||
for (const t of targets) {
|
||||
if (t.burrowTurns > 0) continue; // burrowed cards immune to targeted venom
|
||||
t.venomStacks = skill.value;
|
||||
allTargets.push({ target: t, stacks: skill.value });
|
||||
}
|
||||
if (context && allTargets.length > 0) context.venomAllTargets = allTargets;
|
||||
return;
|
||||
}
|
||||
if (!defender || defender.currentHP <= 0) return;
|
||||
if (defender.venomStacks > 0) return;
|
||||
if (defender.burrowTurns > 0) return;
|
||||
defender.venomStacks = skill.value;
|
||||
if (context) { context.venomTarget = defender; context.venomStacks = skill.value; }
|
||||
}
|
||||
|
||||
molt(skill, attacker, defender, allies, enemies, context) {
|
||||
const armorToConvert = attacker.currentArmor;
|
||||
if (armorToConvert <= 0) return;
|
||||
const healAmount = Math.min(armorToConvert, attacker.health - attacker.currentHP);
|
||||
attacker.currentHP += healAmount;
|
||||
attacker.currentArmor = 0;
|
||||
if (context) { context.moltHeal = healAmount; context.moltArmorLost = armorToConvert; }
|
||||
}
|
||||
|
||||
hive_link(skill, attacker, defender, allies, enemies, context) {
|
||||
// attacker is the dying card
|
||||
const xenoAllies = allies.filter(a =>
|
||||
a.faction === 'xeno' && a.currentHP > 0 && a !== attacker
|
||||
);
|
||||
if (xenoAllies.length === 0) return;
|
||||
const targets = [];
|
||||
for (const ally of xenoAllies) {
|
||||
ally.currentAttack += skill.value;
|
||||
ally.attack += skill.value; // permanent
|
||||
targets.push({ target: ally, gain: skill.value });
|
||||
}
|
||||
if (context) context.hiveLinkTargets = targets;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ export class CardManager {
|
|||
currentArmor: stats.armor,
|
||||
currentDelay: stats.delay,
|
||||
ruptureStacks: 0,
|
||||
venomStacks: 0,
|
||||
burrowTurns: (() => { const bs = stats.skills.find(s => s.name === 'burrow'); return bs ? bs.value : 0; })(),
|
||||
jamTurns: 0,
|
||||
berserkBonus: 0,
|
||||
instanceId: Math.random().toString(36).slice(2)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export class PackManager {
|
|||
const pool = this.cardManager.getAllCards().filter(c =>
|
||||
c.rarity === rarity &&
|
||||
c.type !== 'commander' &&
|
||||
c.type !== 'token' &&
|
||||
slot.cardPool.includes(c.faction)
|
||||
);
|
||||
if (pool.length === 0) continue;
|
||||
|
|
|
|||
|
|
@ -106,6 +106,27 @@ export class CardObject extends Phaser.GameObjects.Container {
|
|||
this.add(this.delayOverlay);
|
||||
}
|
||||
|
||||
// Burrow overlay (brown tint when burrowed)
|
||||
this.burrowOverlay = this.scene.add.rectangle(0, imageCY, w, imageH, 0x553311, 0.45);
|
||||
this.burrowOverlay.setVisible(this.cardData.burrowTurns > 0);
|
||||
this.add(this.burrowOverlay);
|
||||
|
||||
// Venom indicator (top-right of image area)
|
||||
this.venomBadge = this.scene.add.text(
|
||||
w / 2 - Math.round(4 * scale),
|
||||
-h / 2 + bannerH + Math.round(3 * scale),
|
||||
'',
|
||||
{
|
||||
fontSize: fs(7), color: '#44ff44', fontStyle: 'bold',
|
||||
backgroundColor: '#000000aa',
|
||||
padding: { x: Math.round(2 * scale), y: Math.round(1 * scale) },
|
||||
fontFamily: 'Audiowide'
|
||||
}
|
||||
).setOrigin(1, 0);
|
||||
this.venomBadge.setVisible(false);
|
||||
this.add(this.venomBadge);
|
||||
this._updateVenomBadge();
|
||||
|
||||
// Flash overlay — transparent red rect; .bg is targeted by _animateAttack
|
||||
this.bg = this.scene.add.rectangle(0, imageCY, w, imageH, 0xff2200, 0);
|
||||
this.add(this.bg);
|
||||
|
|
@ -191,6 +212,17 @@ export class CardObject extends Phaser.GameObjects.Container {
|
|||
}
|
||||
}
|
||||
|
||||
_updateVenomBadge() {
|
||||
if (!this.venomBadge) return;
|
||||
const stacks = this.cardData.venomStacks || 0;
|
||||
if (stacks > 0) {
|
||||
this.venomBadge.setText(`☠${stacks}`);
|
||||
this.venomBadge.setVisible(true);
|
||||
} else {
|
||||
this.venomBadge.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
refresh() {
|
||||
const w = this.options.width || 80;
|
||||
const scale = w / 80;
|
||||
|
|
@ -210,6 +242,10 @@ export class CardObject extends Phaser.GameObjects.Container {
|
|||
if (this.delayOverlay) {
|
||||
this.delayOverlay.setVisible(this.cardData.currentDelay > 0);
|
||||
}
|
||||
if (this.burrowOverlay) {
|
||||
this.burrowOverlay.setVisible(this.cardData.burrowTurns > 0);
|
||||
}
|
||||
this._updateVenomBadge();
|
||||
if (this.skillText && this.cardData.skills?.length) {
|
||||
const skillStr = this.cardData.skills
|
||||
.map(s => {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ import { SaveManager } from '../managers/SaveManager.js';
|
|||
const BATTLE_BACKGROUNDS = {
|
||||
imperial: ['imperial_01', 'imperial_02', 'imperial_03'],
|
||||
raider: ['raider_01', 'raider_02', 'raider_03'],
|
||||
bloodthirsty: ['bloodthirsty_01', 'bloodthirsty_02', 'bloodthirsty_03']
|
||||
bloodthirsty: ['bloodthirsty_01', 'bloodthirsty_02', 'bloodthirsty_03'],
|
||||
xeno: ['xeno_01', 'xeno_02', 'xeno_03']
|
||||
};
|
||||
|
||||
// ── Battle music playlist ─────────────────────────────────────────────────────
|
||||
|
|
@ -338,8 +339,15 @@ export class BattleScene extends Phaser.Scene {
|
|||
? this.engine.getState().opponent.lanes.find(c => !oldOpponentIds.has(c.instanceId))
|
||||
: null;
|
||||
|
||||
const startAttacks = () => this._processPreBattle(preBattleEvents, () => {
|
||||
this.time.delayedCall(200, () => this._processNextAttackStep());
|
||||
// Collect new-phase events (spawn, molt, venomTick) emitted between deploy and preBattle
|
||||
const phaseEvents = commitEvents.filter(e =>
|
||||
['spawn', 'molt', 'venomTick', 'carapace', 'hive_link'].includes(e.type)
|
||||
);
|
||||
|
||||
const startAttacks = () => this._processPhaseEvents(phaseEvents, () => {
|
||||
this._processPreBattle(preBattleEvents, () => {
|
||||
this.time.delayedCall(200, () => this._processNextAttackStep());
|
||||
});
|
||||
});
|
||||
|
||||
const animateOpponentThenAttack = () => {
|
||||
|
|
@ -401,7 +409,7 @@ export class BattleScene extends Phaser.Scene {
|
|||
} else if (currentRound) {
|
||||
if (e.type === 'berserk') currentRound.berserkEvent = e;
|
||||
else if (e.type === 'counter') currentRound.counterEvent = e;
|
||||
else if (e.type === 'ruptureAll' || e.type === 'healAll' || e.type === 'weakenAll')
|
||||
else if (['ruptureAll', 'healAll', 'weakenAll', 'venomApply', 'venomApplyAll', 'carapace', 'hive_link'].includes(e.type))
|
||||
currentRound.onAttackAllEvents.push(e);
|
||||
}
|
||||
}
|
||||
|
|
@ -476,6 +484,8 @@ export class BattleScene extends Phaser.Scene {
|
|||
if (event.type === 'ruptureAll') this._animateRuptureAll(event, cb);
|
||||
else if (event.type === 'healAll') this._animateHealAll(event, cb);
|
||||
else if (event.type === 'weakenAll') this._animateWeakenAll(event, cb);
|
||||
else if (event.type === 'venomApply') this._animateVenomApply(event, cb);
|
||||
else if (event.type === 'venomApplyAll') this._animateVenomApplyAll(event, cb);
|
||||
else cb();
|
||||
};
|
||||
next(0);
|
||||
|
|
@ -537,6 +547,123 @@ export class BattleScene extends Phaser.Scene {
|
|||
}
|
||||
}
|
||||
|
||||
// ── Xeno skill animations ──────────────────────────────────────────────────
|
||||
|
||||
_processPhaseEvents(events, onComplete) {
|
||||
if (!events || events.length === 0) { onComplete(); return; }
|
||||
const next = (idx) => {
|
||||
if (idx >= events.length) { onComplete(); return; }
|
||||
const event = events[idx];
|
||||
const cb = () => next(idx + 1);
|
||||
if (event.type === 'spawn') this._animateSpawn(event, cb);
|
||||
else if (event.type === 'molt') this._animateMolt(event, cb);
|
||||
else if (event.type === 'venomTick') this._animateVenomTick(event, cb);
|
||||
else if (event.type === 'carapace') this._animateCarapace(event, cb);
|
||||
else if (event.type === 'hive_link') this._animateHiveLink(event, cb);
|
||||
else cb();
|
||||
};
|
||||
next(0);
|
||||
}
|
||||
|
||||
_animateSpawn(event, onComplete) {
|
||||
// Re-render state so the new drone card object exists
|
||||
this._renderState();
|
||||
const cardObj = this.cardObjects.get(event.card.instanceId);
|
||||
if (!cardObj?.scene) { onComplete(); return; }
|
||||
this.statusText.setText(`${event.side === 'player' ? 'Your' : 'Enemy'} commander spawns a drone!`);
|
||||
cardObj.setAlpha(0).setScale(0.5);
|
||||
this.tweens.add({
|
||||
targets: cardObj,
|
||||
alpha: 1, scaleX: 1, scaleY: 1,
|
||||
duration: 400,
|
||||
ease: 'Back.Out',
|
||||
onComplete: () => { this.time.delayedCall(200, onComplete); }
|
||||
});
|
||||
}
|
||||
|
||||
_animateMolt(event, onComplete) {
|
||||
const obj = this.cardObjects.get(event.card.instanceId);
|
||||
if (!obj?.scene) { onComplete(); return; }
|
||||
this.statusText.setText(`${event.card.name} molts: converts ${event.armorLost} armor to ${event.heal} HP`);
|
||||
obj.flash(0x22aa44);
|
||||
this.time.delayedCall(400, () => {
|
||||
if (obj.scene) obj.refresh();
|
||||
onComplete();
|
||||
});
|
||||
}
|
||||
|
||||
_animateVenomTick(event, onComplete) {
|
||||
const obj = this.cardObjects.get(event.card.instanceId) || this.commanderObjects?.get(event.card.instanceId);
|
||||
if (!obj?.scene) { onComplete(); return; }
|
||||
obj.flash(0x44ff44);
|
||||
this.time.delayedCall(300, () => {
|
||||
if (obj.scene) obj.refresh();
|
||||
onComplete();
|
||||
});
|
||||
}
|
||||
|
||||
_animateCarapace(event, onComplete) {
|
||||
const obj = this.cardObjects.get(event.card.instanceId);
|
||||
if (!obj?.scene) { onComplete(); return; }
|
||||
obj.animateArmorGain(event.gain, onComplete);
|
||||
}
|
||||
|
||||
_animateHiveLink(event, onComplete) {
|
||||
if (!event.targets?.length) { onComplete(); return; }
|
||||
this.statusText.setText(`${event.source.name} dies — hive link grants +${event.gain} ATK!`);
|
||||
let remaining = event.targets.length;
|
||||
const done = () => {
|
||||
if (--remaining <= 0) onComplete();
|
||||
};
|
||||
for (const t of event.targets) {
|
||||
const obj = this.cardObjects.get(t.target.instanceId);
|
||||
if (!obj?.scene) { done(); continue; }
|
||||
obj.flash(0xff4400);
|
||||
this.time.delayedCall(400, () => {
|
||||
if (obj.scene) obj.refresh();
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_animateVenomApply(event, onComplete) {
|
||||
const obj = this.cardObjects.get(event.target.instanceId) || this.commanderObjects?.get(event.target.instanceId);
|
||||
if (!obj?.scene) { onComplete(); return; }
|
||||
this.statusText.setText(`${event.attacker.name} applies ${event.stacks} venom to ${event.target.name}!`);
|
||||
obj.flash(0x44ff44);
|
||||
this.time.delayedCall(400, () => {
|
||||
if (obj.scene) obj.refresh();
|
||||
onComplete();
|
||||
});
|
||||
}
|
||||
|
||||
_animateVenomApplyAll(event, onComplete) {
|
||||
if (!event.targets?.length) { onComplete(); return; }
|
||||
this.statusText.setText(`${event.attacker.name} venoms all enemies!`);
|
||||
let remaining = event.targets.length;
|
||||
const done = () => {
|
||||
if (--remaining <= 0) onComplete();
|
||||
};
|
||||
for (const t of event.targets) {
|
||||
const obj = this.cardObjects.get(t.target.instanceId) || this.commanderObjects?.get(t.target.instanceId);
|
||||
if (!obj?.scene) { done(); continue; }
|
||||
obj.flash(0x44ff44);
|
||||
this.time.delayedCall(400, () => {
|
||||
if (obj.scene) obj.refresh();
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_animateSwarmBuff(buff, onComplete) {
|
||||
// Reuse the legion buff animation pattern
|
||||
const _lookup = id => id && (this.cardObjects.get(id) || this.commanderObjects?.get(id));
|
||||
const targetObj = _lookup(buff.target.instanceId);
|
||||
if (!targetObj?.scene) { onComplete(); return; }
|
||||
this.statusText.setText(`${buff.target.name} swarm: +${buff.amount} ATK`);
|
||||
targetObj.animateBerserkGain(buff.amount, onComplete);
|
||||
}
|
||||
|
||||
// Explosion sprite + fade-out for a defeated lane card.
|
||||
_playCardDeath(obj, cardData, onComplete = null) {
|
||||
this.cardObjects.delete(cardData.instanceId);
|
||||
|
|
@ -2042,6 +2169,8 @@ export class BattleScene extends Phaser.Scene {
|
|||
this._animateRallyBuff(group, () => next(idx + 1));
|
||||
} else if (group.skill === 'legion') {
|
||||
this._animateLegionBuff(group, () => next(idx + 1));
|
||||
} else if (group.skill === 'swarm') {
|
||||
this._animateSwarmBuff(group, () => next(idx + 1));
|
||||
} else if (group.skill === 'bloodrage') {
|
||||
this._animateBloodrageBuff(group, () => next(idx + 1));
|
||||
} else {
|
||||
|
|
|
|||