Compare commits
4 Commits
aee3352efe
...
d6d3bafc7f
| Author | SHA1 | Date |
|---|---|---|
|
|
d6d3bafc7f | |
|
|
8b5aa6064a | |
|
|
02b14b88c8 | |
|
|
b214f6b8bd |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -108,52 +108,52 @@
|
|||
{ "id": "caravan", "name": "Caravan", "domain": "land", "attack": 0, "defense": 1, "move": 1, "hp": 10, "fp": 1, "cost": 50, "prereq": "trade", "obsoletedBy": "freight", "flags": ["caravan", "noncombat"], "abbr": "CV", "frame": 3 },
|
||||
{ "id": "freight", "name": "Freight", "domain": "land", "attack": 0, "defense": 1, "move": 2, "hp": 10, "fp": 1, "cost": 50, "prereq": "thecorporation", "obsoletedBy": null, "flags": ["caravan", "noncombat"], "abbr": "FR", "frame": 4 },
|
||||
|
||||
{ "id": "warriors", "name": "Warriors", "domain": "land", "attack": 1, "defense": 1, "move": 1, "hp": 10, "fp": 1, "cost": 10, "prereq": null, "obsoletedBy": "pikemen", "flags": [], "abbr": "WA", "frame": 5 },
|
||||
{ "id": "phalanx", "name": "Phalanx", "domain": "land", "attack": 1, "defense": 2, "move": 1, "hp": 10, "fp": 1, "cost": 20, "prereq": "bronzeworking", "obsoletedBy": "pikemen", "flags": [], "abbr": "PH", "frame": 6 },
|
||||
{ "id": "archers", "name": "Archers", "domain": "land", "attack": 3, "defense": 2, "move": 1, "hp": 10, "fp": 1, "cost": 30, "prereq": "warriorcode", "obsoletedBy": "musketeers", "flags": [], "abbr": "AR", "frame": 7 },
|
||||
{ "id": "legion", "name": "Legion", "domain": "land", "attack": 4, "defense": 2, "move": 1, "hp": 10, "fp": 1, "cost": 40, "prereq": "ironworking", "obsoletedBy": "musketeers", "flags": [], "abbr": "LG", "frame": 8 },
|
||||
{ "id": "pikemen", "name": "Pikemen", "domain": "land", "attack": 1, "defense": 2, "move": 1, "hp": 10, "fp": 1, "cost": 20, "prereq": "feudalism", "obsoletedBy": "musketeers", "flags": ["antimounted"], "abbr": "PK", "frame": 9 },
|
||||
{ "id": "musketeers", "name": "Musketeers", "domain": "land", "attack": 3, "defense": 3, "move": 1, "hp": 20, "fp": 1, "cost": 30, "prereq": "gunpowder", "obsoletedBy": "riflemen", "flags": [], "abbr": "MK", "frame": 10 },
|
||||
{ "id": "riflemen", "name": "Riflemen", "domain": "land", "attack": 5, "defense": 4, "move": 1, "hp": 20, "fp": 1, "cost": 40, "prereq": "conscription", "obsoletedBy": null, "flags": [], "abbr": "RF", "frame": 11 },
|
||||
{ "id": "alpinetroops", "name": "Alpine Troops", "domain": "land", "attack": 5, "defense": 5, "move": 1, "hp": 20, "fp": 1, "cost": 50, "prereq": "tactics", "obsoletedBy": null, "flags": ["ignoreterrain"], "abbr": "AL", "frame": 12 },
|
||||
{ "id": "partisans", "name": "Partisans", "domain": "land", "attack": 4, "defense": 4, "move": 1, "hp": 20, "fp": 1, "cost": 50, "prereq": "guerrillawarfare", "obsoletedBy": null, "flags": ["ignoreterrain"], "abbr": "PS", "frame": 13 },
|
||||
{ "id": "marines", "name": "Marines", "domain": "land", "attack": 8, "defense": 5, "move": 1, "hp": 20, "fp": 1, "cost": 60, "prereq": "amphibiouswarfare", "obsoletedBy": null, "flags": ["amphibious"], "abbr": "MN", "frame": 14 },
|
||||
{ "id": "paratroopers", "name": "Paratroopers", "domain": "land", "attack": 6, "defense": 4, "move": 1, "hp": 20, "fp": 1, "cost": 60, "prereq": "combinedarms", "obsoletedBy": null, "flags": ["paradrop"], "abbr": "PT", "frame": 15 },
|
||||
{ "id": "mechinf", "name": "Mech. Inf.", "domain": "land", "attack": 6, "defense": 6, "move": 3, "hp": 30, "fp": 1, "cost": 50, "prereq": "laborunion", "obsoletedBy": null, "flags": [], "abbr": "MI", "frame": 16 },
|
||||
{ "id": "warriors", "name": "Warriors", "domain": "land", "attack": 1, "defense": 1, "move": 1, "hp": 10, "fp": 1, "cost": 10, "prereq": null, "obsoletedBy": "pikemen", "flags": [], "battleSound": "sword", "abbr": "WA", "frame": 5 },
|
||||
{ "id": "phalanx", "name": "Phalanx", "domain": "land", "attack": 1, "defense": 2, "move": 1, "hp": 10, "fp": 1, "cost": 20, "prereq": "bronzeworking", "obsoletedBy": "pikemen", "flags": [], "battleSound": "sword", "abbr": "PH", "frame": 6 },
|
||||
{ "id": "archers", "name": "Archers", "domain": "land", "attack": 3, "defense": 2, "move": 1, "hp": 10, "fp": 1, "cost": 30, "prereq": "warriorcode", "obsoletedBy": "musketeers", "flags": [], "battleSound": "arrows", "abbr": "AR", "frame": 7 },
|
||||
{ "id": "legion", "name": "Legion", "domain": "land", "attack": 4, "defense": 2, "move": 1, "hp": 10, "fp": 1, "cost": 40, "prereq": "ironworking", "obsoletedBy": "musketeers", "flags": [], "battleSound": "sword", "abbr": "LG", "frame": 8 },
|
||||
{ "id": "pikemen", "name": "Pikemen", "domain": "land", "attack": 1, "defense": 2, "move": 1, "hp": 10, "fp": 1, "cost": 20, "prereq": "feudalism", "obsoletedBy": "musketeers", "flags": ["antimounted"], "battleSound": "sword", "abbr": "PK", "frame": 9 },
|
||||
{ "id": "musketeers", "name": "Musketeers", "domain": "land", "attack": 3, "defense": 3, "move": 1, "hp": 20, "fp": 1, "cost": 30, "prereq": "gunpowder", "obsoletedBy": "riflemen", "flags": [], "battleSound": "gunfire-classic", "abbr": "MK", "frame": 10 },
|
||||
{ "id": "riflemen", "name": "Riflemen", "domain": "land", "attack": 5, "defense": 4, "move": 1, "hp": 20, "fp": 1, "cost": 40, "prereq": "conscription", "obsoletedBy": null, "flags": [], "battleSound": "gunfire-classic", "abbr": "RF", "frame": 11 },
|
||||
{ "id": "alpinetroops", "name": "Alpine Troops", "domain": "land", "attack": 5, "defense": 5, "move": 1, "hp": 20, "fp": 1, "cost": 50, "prereq": "tactics", "obsoletedBy": null, "flags": ["ignoreterrain"], "battleSound": "gunfire-modern", "abbr": "AL", "frame": 12 },
|
||||
{ "id": "partisans", "name": "Partisans", "domain": "land", "attack": 4, "defense": 4, "move": 1, "hp": 20, "fp": 1, "cost": 50, "prereq": "guerrillawarfare", "obsoletedBy": null, "flags": ["ignoreterrain"], "battleSound": "gunfire-modern", "abbr": "PS", "frame": 13 },
|
||||
{ "id": "marines", "name": "Marines", "domain": "land", "attack": 8, "defense": 5, "move": 1, "hp": 20, "fp": 1, "cost": 60, "prereq": "amphibiouswarfare", "obsoletedBy": null, "flags": ["amphibious"], "battleSound": "gunfire-modern", "abbr": "MN", "frame": 14 },
|
||||
{ "id": "paratroopers", "name": "Paratroopers", "domain": "land", "attack": 6, "defense": 4, "move": 1, "hp": 20, "fp": 1, "cost": 60, "prereq": "combinedarms", "obsoletedBy": null, "flags": ["paradrop"], "battleSound": "gunfire-modern", "abbr": "PT", "frame": 15 },
|
||||
{ "id": "mechinf", "name": "Mech. Inf.", "domain": "land", "attack": 6, "defense": 6, "move": 3, "hp": 30, "fp": 1, "cost": 50, "prereq": "laborunion", "obsoletedBy": null, "flags": [], "battleSound": "gunfire-modern", "abbr": "MI", "frame": 16 },
|
||||
|
||||
{ "id": "horsemen", "name": "Horsemen", "domain": "land", "attack": 2, "defense": 1, "move": 2, "hp": 10, "fp": 1, "cost": 20, "prereq": "horsebackriding", "obsoletedBy": "knights", "flags": ["mounted"], "abbr": "HS", "frame": 17 },
|
||||
{ "id": "chariot", "name": "Chariot", "domain": "land", "attack": 3, "defense": 1, "move": 2, "hp": 10, "fp": 1, "cost": 30, "prereq": "thewheel", "obsoletedBy": "knights", "flags": ["mounted"], "abbr": "CH", "frame": 18 },
|
||||
{ "id": "elephant", "name": "Elephant", "domain": "land", "attack": 4, "defense": 1, "move": 2, "hp": 10, "fp": 1, "cost": 40, "prereq": "polytheism", "obsoletedBy": "crusaders", "flags": ["mounted"], "abbr": "EL", "frame": 19 },
|
||||
{ "id": "knights", "name": "Knights", "domain": "land", "attack": 4, "defense": 2, "move": 2, "hp": 10, "fp": 1, "cost": 40, "prereq": "chivalry", "obsoletedBy": "dragoons", "flags": ["mounted"], "abbr": "KN", "frame": 20 },
|
||||
{ "id": "crusaders", "name": "Crusaders", "domain": "land", "attack": 5, "defense": 1, "move": 2, "hp": 10, "fp": 1, "cost": 40, "prereq": "monotheism", "obsoletedBy": "dragoons", "flags": ["mounted"], "abbr": "CS", "frame": 21 },
|
||||
{ "id": "dragoons", "name": "Dragoons", "domain": "land", "attack": 5, "defense": 2, "move": 2, "hp": 20, "fp": 1, "cost": 50, "prereq": "leadership", "obsoletedBy": "cavalry", "flags": ["mounted"], "abbr": "DG", "frame": 22 },
|
||||
{ "id": "cavalry", "name": "Cavalry", "domain": "land", "attack": 8, "defense": 3, "move": 2, "hp": 20, "fp": 1, "cost": 60, "prereq": "tactics", "obsoletedBy": "armor", "flags": ["mounted"], "abbr": "CY", "frame": 23 },
|
||||
{ "id": "armor", "name": "Armor", "domain": "land", "attack": 10, "defense": 5, "move": 3, "hp": 30, "fp": 1, "cost": 80, "prereq": "mobilewarfare", "obsoletedBy": null, "flags": ["mounted"], "abbr": "AM", "frame": 24 },
|
||||
{ "id": "horsemen", "name": "Horsemen", "domain": "land", "attack": 2, "defense": 1, "move": 2, "hp": 10, "fp": 1, "cost": 20, "prereq": "horsebackriding", "obsoletedBy": "knights", "flags": ["mounted"], "battleSound": "sword", "abbr": "HS", "frame": 17 },
|
||||
{ "id": "chariot", "name": "Chariot", "domain": "land", "attack": 3, "defense": 1, "move": 2, "hp": 10, "fp": 1, "cost": 30, "prereq": "thewheel", "obsoletedBy": "knights", "flags": ["mounted"], "battleSound": "sword", "abbr": "CH", "frame": 18 },
|
||||
{ "id": "elephant", "name": "Elephant", "domain": "land", "attack": 4, "defense": 1, "move": 2, "hp": 10, "fp": 1, "cost": 40, "prereq": "polytheism", "obsoletedBy": "crusaders", "flags": ["mounted"], "battleSound": "sword", "abbr": "EL", "frame": 19 },
|
||||
{ "id": "knights", "name": "Knights", "domain": "land", "attack": 4, "defense": 2, "move": 2, "hp": 10, "fp": 1, "cost": 40, "prereq": "chivalry", "obsoletedBy": "dragoons", "flags": ["mounted"], "battleSound": "sword", "abbr": "KN", "frame": 20 },
|
||||
{ "id": "crusaders", "name": "Crusaders", "domain": "land", "attack": 5, "defense": 1, "move": 2, "hp": 10, "fp": 1, "cost": 40, "prereq": "monotheism", "obsoletedBy": "dragoons", "flags": ["mounted"], "battleSound": "sword", "abbr": "CS", "frame": 21 },
|
||||
{ "id": "dragoons", "name": "Dragoons", "domain": "land", "attack": 5, "defense": 2, "move": 2, "hp": 20, "fp": 1, "cost": 50, "prereq": "leadership", "obsoletedBy": "cavalry", "flags": ["mounted"], "battleSound": "gunfire-classic", "abbr": "DG", "frame": 22 },
|
||||
{ "id": "cavalry", "name": "Cavalry", "domain": "land", "attack": 8, "defense": 3, "move": 2, "hp": 20, "fp": 1, "cost": 60, "prereq": "tactics", "obsoletedBy": "armor", "flags": ["mounted"], "battleSound": "gunfire-classic", "abbr": "CY", "frame": 23 },
|
||||
{ "id": "armor", "name": "Armor", "domain": "land", "attack": 10, "defense": 5, "move": 3, "hp": 30, "fp": 1, "cost": 80, "prereq": "mobilewarfare", "obsoletedBy": null, "flags": ["mounted"], "battleSound": "tank", "abbr": "AM", "frame": 24 },
|
||||
|
||||
{ "id": "catapult", "name": "Catapult", "domain": "land", "attack": 6, "defense": 1, "move": 1, "hp": 10, "fp": 1, "cost": 40, "prereq": "mathematics", "obsoletedBy": "cannon", "flags": [], "abbr": "CP", "frame": 25 },
|
||||
{ "id": "cannon", "name": "Cannon", "domain": "land", "attack": 8, "defense": 1, "move": 1, "hp": 20, "fp": 1, "cost": 40, "prereq": "metallurgy", "obsoletedBy": "artillery", "flags": [], "abbr": "CN", "frame": 26 },
|
||||
{ "id": "artillery", "name": "Artillery", "domain": "land", "attack": 10, "defense": 1, "move": 1, "hp": 20, "fp": 2, "cost": 50, "prereq": "machinetools", "obsoletedBy": "howitzer", "flags": [], "abbr": "AT", "frame": 27 },
|
||||
{ "id": "howitzer", "name": "Howitzer", "domain": "land", "attack": 12, "defense": 2, "move": 2, "hp": 30, "fp": 2, "cost": 70, "prereq": "robotics", "obsoletedBy": null, "flags": ["ignorewalls"], "abbr": "HW", "frame": 28 },
|
||||
{ "id": "catapult", "name": "Catapult", "domain": "land", "attack": 6, "defense": 1, "move": 1, "hp": 10, "fp": 1, "cost": 40, "prereq": "mathematics", "obsoletedBy": "cannon", "flags": [], "battleSound": "catapult", "abbr": "CP", "frame": 25 },
|
||||
{ "id": "cannon", "name": "Cannon", "domain": "land", "attack": 8, "defense": 1, "move": 1, "hp": 20, "fp": 1, "cost": 40, "prereq": "metallurgy", "obsoletedBy": "artillery", "flags": [], "battleSound": "cannon", "abbr": "CN", "frame": 26 },
|
||||
{ "id": "artillery", "name": "Artillery", "domain": "land", "attack": 10, "defense": 1, "move": 1, "hp": 20, "fp": 2, "cost": 50, "prereq": "machinetools", "obsoletedBy": "howitzer", "flags": [], "battleSound": "cannon", "abbr": "AT", "frame": 27 },
|
||||
{ "id": "howitzer", "name": "Howitzer", "domain": "land", "attack": 12, "defense": 2, "move": 2, "hp": 30, "fp": 2, "cost": 70, "prereq": "robotics", "obsoletedBy": null, "flags": ["ignorewalls"], "battleSound": "cannon", "abbr": "HW", "frame": 28 },
|
||||
|
||||
{ "id": "fighter", "name": "Fighter", "domain": "air", "attack": 4, "defense": 3, "move": 10, "hp": 20, "fp": 2, "cost": 60, "prereq": "flight", "obsoletedBy": "stealthfighter", "flags": ["fighter"], "abbr": "FT", "frame": 29 },
|
||||
{ "id": "bomber", "name": "Bomber", "domain": "air", "attack": 12, "defense": 1, "move": 8, "hp": 20, "fp": 2, "cost": 120, "prereq": "advancedflight", "obsoletedBy": "stealthbomber", "flags": [], "abbr": "BM", "frame": 30 },
|
||||
{ "id": "helicopter", "name": "Helicopter", "domain": "air", "attack": 10, "defense": 3, "move": 6, "hp": 20, "fp": 2, "cost": 100, "prereq": "combinedarms", "obsoletedBy": null, "flags": [], "abbr": "HC", "frame": 31 },
|
||||
{ "id": "stealthfighter", "name": "Stealth Ftr.", "domain": "air", "attack": 8, "defense": 4, "move": 14, "hp": 20, "fp": 2, "cost": 80, "prereq": "stealth", "obsoletedBy": null, "flags": ["fighter"], "abbr": "SF", "frame": 32 },
|
||||
{ "id": "stealthbomber", "name": "Stealth Bmb.", "domain": "air", "attack": 14, "defense": 5, "move": 12, "hp": 20, "fp": 2, "cost": 160, "prereq": "stealth", "obsoletedBy": null, "flags": [], "abbr": "SB", "frame": 33 },
|
||||
{ "id": "cruisemsl", "name": "Cruise Msl.", "domain": "air", "attack": 18, "defense": 0, "move": 12, "hp": 10, "fp": 3, "cost": 60, "prereq": "rocketry", "obsoletedBy": null, "flags": ["missile"], "abbr": "CM", "frame": 34 },
|
||||
{ "id": "nuclearmsl", "name": "Nuclear Msl.", "domain": "air", "attack": 99, "defense": 0, "move": 16, "hp": 10, "fp": 1, "cost": 160, "prereq": "nuclearfission", "obsoletedBy": null, "flags": ["missile", "nuke"], "abbr": "NM", "frame": 35 },
|
||||
{ "id": "fighter", "name": "Fighter", "domain": "air", "attack": 4, "defense": 3, "move": 10, "hp": 20, "fp": 2, "cost": 60, "prereq": "flight", "obsoletedBy": "stealthfighter", "flags": ["fighter"], "battleSound": "gunfire-modern", "abbr": "FT", "frame": 29 },
|
||||
{ "id": "bomber", "name": "Bomber", "domain": "air", "attack": 12, "defense": 1, "move": 8, "hp": 20, "fp": 2, "cost": 120, "prereq": "advancedflight", "obsoletedBy": "stealthbomber", "flags": [], "battleSound": "missle", "abbr": "BM", "frame": 30 },
|
||||
{ "id": "helicopter", "name": "Helicopter", "domain": "air", "attack": 10, "defense": 3, "move": 6, "hp": 20, "fp": 2, "cost": 100, "prereq": "combinedarms", "obsoletedBy": null, "flags": [], "battleSound": "helicopter", "abbr": "HC", "frame": 31 },
|
||||
{ "id": "stealthfighter", "name": "Stealth Ftr.", "domain": "air", "attack": 8, "defense": 4, "move": 14, "hp": 20, "fp": 2, "cost": 80, "prereq": "stealth", "obsoletedBy": null, "flags": ["fighter"], "battleSound": "missle", "abbr": "SF", "frame": 32 },
|
||||
{ "id": "stealthbomber", "name": "Stealth Bmb.", "domain": "air", "attack": 14, "defense": 5, "move": 12, "hp": 20, "fp": 2, "cost": 160, "prereq": "stealth", "obsoletedBy": null, "flags": [], "battleSound": "missle", "abbr": "SB", "frame": 33 },
|
||||
{ "id": "cruisemsl", "name": "Cruise Msl.", "domain": "air", "attack": 18, "defense": 0, "move": 12, "hp": 10, "fp": 3, "cost": 60, "prereq": "rocketry", "obsoletedBy": null, "flags": ["missile"], "battleSound": "missle", "abbr": "CM", "frame": 34 },
|
||||
{ "id": "nuclearmsl", "name": "Nuclear Msl.", "domain": "air", "attack": 99, "defense": 0, "move": 16, "hp": 10, "fp": 1, "cost": 160, "prereq": "nuclearfission", "obsoletedBy": null, "flags": ["missile", "nuke"], "battleSound": "missle", "abbr": "NM", "frame": 35 },
|
||||
|
||||
{ "id": "trireme", "name": "Trireme", "domain": "sea", "attack": 1, "defense": 1, "move": 3, "hp": 10, "fp": 1, "cost": 40, "prereq": "mapmaking", "obsoletedBy": "caravel", "flags": ["coastal"], "cargo": 2, "abbr": "TR", "frame": 36 },
|
||||
{ "id": "caravel", "name": "Caravel", "domain": "sea", "attack": 2, "defense": 1, "move": 3, "hp": 10, "fp": 1, "cost": 40, "prereq": "navigation", "obsoletedBy": "galleon", "flags": [], "cargo": 3, "abbr": "CL", "frame": 37 },
|
||||
{ "id": "trireme", "name": "Trireme", "domain": "sea", "attack": 1, "defense": 1, "move": 3, "hp": 10, "fp": 1, "cost": 40, "prereq": "mapmaking", "obsoletedBy": "caravel", "flags": ["coastal"], "cargo": 2, "battleSound": "arrows", "abbr": "TR", "frame": 36 },
|
||||
{ "id": "caravel", "name": "Caravel", "domain": "sea", "attack": 2, "defense": 1, "move": 3, "hp": 10, "fp": 1, "cost": 40, "prereq": "navigation", "obsoletedBy": "galleon", "flags": [], "cargo": 3, "battleSound": "cannon", "abbr": "CL", "frame": 37 },
|
||||
{ "id": "galleon", "name": "Galleon", "domain": "sea", "attack": 0, "defense": 2, "move": 4, "hp": 20, "fp": 1, "cost": 40, "prereq": "magnetism", "obsoletedBy": "transport", "flags": [], "cargo": 4, "abbr": "GL", "frame": 38 },
|
||||
{ "id": "frigate", "name": "Frigate", "domain": "sea", "attack": 4, "defense": 2, "move": 4, "hp": 20, "fp": 1, "cost": 50, "prereq": "magnetism", "obsoletedBy": "destroyer", "flags": [], "cargo": 2, "abbr": "FG", "frame": 39 },
|
||||
{ "id": "ironclad", "name": "Ironclad", "domain": "sea", "attack": 4, "defense": 4, "move": 4, "hp": 30, "fp": 1, "cost": 60, "prereq": "steamengine", "obsoletedBy": "destroyer", "flags": [], "abbr": "IC", "frame": 40 },
|
||||
{ "id": "destroyer", "name": "Destroyer", "domain": "sea", "attack": 4, "defense": 4, "move": 6, "hp": 30, "fp": 1, "cost": 60, "prereq": "combustion", "obsoletedBy": null, "flags": [], "abbr": "DS", "frame": 41 },
|
||||
{ "id": "cruiser", "name": "Cruiser", "domain": "sea", "attack": 6, "defense": 6, "move": 5, "hp": 30, "fp": 2, "cost": 80, "prereq": "steel", "obsoletedBy": "aegiscruiser", "flags": [], "abbr": "CU", "frame": 42 },
|
||||
{ "id": "aegiscruiser", "name": "AEGIS Cruiser", "domain": "sea", "attack": 8, "defense": 8, "move": 5, "hp": 30, "fp": 2, "cost": 100, "prereq": "rocketry", "obsoletedBy": null, "flags": ["aegis"], "abbr": "AE", "frame": 43 },
|
||||
{ "id": "battleship", "name": "Battleship", "domain": "sea", "attack": 12, "defense": 12, "move": 4, "hp": 40, "fp": 2, "cost": 160, "prereq": "automobile", "obsoletedBy": null, "flags": [], "abbr": "BS", "frame": 44 },
|
||||
{ "id": "submarine", "name": "Submarine", "domain": "sea", "attack": 10, "defense": 2, "move": 3, "hp": 30, "fp": 2, "cost": 60, "prereq": "combustion", "obsoletedBy": null, "flags": ["submarine"], "abbr": "SM", "frame": 45 },
|
||||
{ "id": "carrier", "name": "Carrier", "domain": "sea", "attack": 1, "defense": 9, "move": 5, "hp": 40, "fp": 2, "cost": 160, "prereq": "advancedflight", "obsoletedBy": null, "flags": [], "cargoAir": 8, "abbr": "CR", "frame": 46 },
|
||||
{ "id": "frigate", "name": "Frigate", "domain": "sea", "attack": 4, "defense": 2, "move": 4, "hp": 20, "fp": 1, "cost": 50, "prereq": "magnetism", "obsoletedBy": "destroyer", "flags": [], "cargo": 2, "battleSound": "cannon", "abbr": "FG", "frame": 39 },
|
||||
{ "id": "ironclad", "name": "Ironclad", "domain": "sea", "attack": 4, "defense": 4, "move": 4, "hp": 30, "fp": 1, "cost": 60, "prereq": "steamengine", "obsoletedBy": "destroyer", "flags": [], "battleSound": "cannon", "abbr": "IC", "frame": 40 },
|
||||
{ "id": "destroyer", "name": "Destroyer", "domain": "sea", "attack": 4, "defense": 4, "move": 6, "hp": 30, "fp": 1, "cost": 60, "prereq": "combustion", "obsoletedBy": null, "flags": [], "battleSound": "cannon", "abbr": "DS", "frame": 41 },
|
||||
{ "id": "cruiser", "name": "Cruiser", "domain": "sea", "attack": 6, "defense": 6, "move": 5, "hp": 30, "fp": 2, "cost": 80, "prereq": "steel", "obsoletedBy": "aegiscruiser", "flags": [], "battleSound": "cannon", "abbr": "CU", "frame": 42 },
|
||||
{ "id": "aegiscruiser", "name": "AEGIS Cruiser", "domain": "sea", "attack": 8, "defense": 8, "move": 5, "hp": 30, "fp": 2, "cost": 100, "prereq": "rocketry", "obsoletedBy": null, "flags": ["aegis"], "battleSound": "missle", "abbr": "AE", "frame": 43 },
|
||||
{ "id": "battleship", "name": "Battleship", "domain": "sea", "attack": 12, "defense": 12, "move": 4, "hp": 40, "fp": 2, "cost": 160, "prereq": "automobile", "obsoletedBy": null, "flags": [], "battleSound": "cannon", "abbr": "BS", "frame": 44 },
|
||||
{ "id": "submarine", "name": "Submarine", "domain": "sea", "attack": 10, "defense": 2, "move": 3, "hp": 30, "fp": 2, "cost": 60, "prereq": "combustion", "obsoletedBy": null, "flags": ["submarine"], "battleSound": "submarine", "abbr": "SM", "frame": 45 },
|
||||
{ "id": "carrier", "name": "Carrier", "domain": "sea", "attack": 1, "defense": 9, "move": 5, "hp": 40, "fp": 2, "cost": 160, "prereq": "advancedflight", "obsoletedBy": null, "flags": [], "cargoAir": 8, "battleSound": "gunfire-modern", "abbr": "CR", "frame": 46 },
|
||||
{ "id": "transport", "name": "Transport", "domain": "sea", "attack": 0, "defense": 3, "move": 5, "hp": 30, "fp": 1, "cost": 50, "prereq": "industrialization", "obsoletedBy": null, "flags": [], "cargo": 8, "abbr": "TP", "frame": 47 },
|
||||
|
||||
{ "id": "ssstructural", "name": "SS Structural", "domain": "project", "attack": 0, "defense": 0, "move": 0, "hp": 1, "fp": 1, "cost": 80, "prereq": "spaceflight", "obsoletedBy": null, "flags": ["spaceship"], "abbr": "S1", "frame": 48 },
|
||||
|
|
|
|||
|
|
@ -158,6 +158,11 @@ export const MANIFEST = {
|
|||
civilization: [
|
||||
{ type: 'json', key: 'civilization-rules', path: 'data/civilization-rules.json' },
|
||||
image('civilization-setup-bg', 'assets/images/civilization/background.png'),
|
||||
// Battle sounds keyed by each unit's `battleSound` in civilization-rules.json
|
||||
// (played on attack by CivilizationMapView.playCombatEvent).
|
||||
...['arrows', 'cannon', 'catapult', 'gunfire-classic', 'gunfire-modern',
|
||||
'helicopter', 'missle', 'submarine', 'sword', 'tank']
|
||||
.map((n) => ({ type: 'audio', key: `sfx-battle-${n}`, path: `assets/fx/battle-${n}.mp3` })),
|
||||
(scene) => sheetsFrom(scene, 'civilization-artwork',
|
||||
['terrainSheet', 'resourceSheet', 'improvementSheet', 'unitSheet', 'iconSheet', 'citySheets']),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -319,6 +319,13 @@ function manageCityBuild(rules, state, civIdx, city, strategy) {
|
|||
setBuild(rules, state, city, 'unit', cvType);
|
||||
return;
|
||||
}
|
||||
// Corruption triage: distant cities bleed trade to corruption and shields
|
||||
// to waste — when the combined loss is worth a building's upkeep several
|
||||
// times over, jump the chain straight to a courthouse (halves both).
|
||||
if ((yields.corruption + yields.waste) >= 4 && avail.some((b) => b.id === 'courthouse')) {
|
||||
setBuild(rules, state, city, 'building', 'courthouse');
|
||||
return;
|
||||
}
|
||||
for (const id of DEVELOP_CHAIN) {
|
||||
if (avail.some((b) => b.id === id)) {
|
||||
// Skip aqueduct/sewer until the city is close to its cap.
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export function openCityScreen(scene, rules, state, city, onClose) {
|
|||
// Yields breakdown.
|
||||
const lines = [
|
||||
`Food ${y.food} (need ${y.foodNeed}) surplus ${y.foodSurplus >= 0 ? '+' : ''}${y.foodSurplus}`,
|
||||
`Shields ${y.shield}${y.supportShields ? ` (support −${y.supportShields})` : ''}`,
|
||||
`Shields ${y.shield}${y.waste ? ` waste −${y.waste}` : ''}${y.supportShields ? ` (support −${y.supportShields})` : ''}`,
|
||||
`Trade ${y.trade} corruption −${y.corruption}${y.routeTrade ? ` routes +${y.routeTrade}` : ''}`,
|
||||
`Gold +${y.gold} · Science +${y.science} · Upkeep −${y.upkeep}`,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1014,7 +1014,7 @@ export default class CivilizationGame extends Phaser.Scene {
|
|||
|
||||
tryCaravan(unit) {
|
||||
const pair = Logic.canEstablishRoute(this.rules, this.state, unit);
|
||||
if (!pair) { this.logMessage('Caravans need a city 8+ tiles from home'); return; }
|
||||
if (!pair) { this.logMessage(`Caravans need a city ${Logic.TRADE_ROUTE_MIN_DIST}+ tiles from home`); return; }
|
||||
const out = Logic.establishTradeRoute(this.rules, this.state, unit);
|
||||
if (out) {
|
||||
this.announceStatus(`Trade route: ${out.from.name} ↔ ${out.to.name} (+${out.bonus} gold & beakers)`);
|
||||
|
|
@ -1236,8 +1236,18 @@ export default class CivilizationGame extends Phaser.Scene {
|
|||
const civ = this.state.civs[human];
|
||||
if (!civ.alive) { this.onGameOver(); return; }
|
||||
|
||||
// Research prompt.
|
||||
if (!civ.researching && Logic.availableTechs(this.rules, civ).length) {
|
||||
// Research: announce the completed discovery FIRST, open the picker as
|
||||
// that popup is dismissed (onDismiss replaces the queue-resume; the tech
|
||||
// screen's close handler resumes it), and let everything else queue up
|
||||
// behind. Without a completion event (fresh game, resumed save) the
|
||||
// picker still opens directly.
|
||||
const techEvent = this.state.events.find((e) => e.type === 'techDone' && e.civ === human && !e.announced);
|
||||
const needsPick = !civ.researching && Logic.availableTechs(this.rules, civ).length > 0;
|
||||
if (techEvent) {
|
||||
techEvent.announced = true;
|
||||
this.announceStatus(`Research complete: ${this.rules.techs[techEvent.tech].name}`,
|
||||
needsPick ? () => this.openTech() : undefined);
|
||||
} else if (needsPick) {
|
||||
this.openTech();
|
||||
}
|
||||
this.presentAIProposals();
|
||||
|
|
@ -1283,20 +1293,26 @@ export default class CivilizationGame extends Phaser.Scene {
|
|||
// "resolve now, animate after" trick as the player's own moves, just
|
||||
// via a before/after snapshot instead of a waypoint path.
|
||||
const combatEvents = this.collectNewCombatEvents();
|
||||
// Combats against any city the human has explored — their own or a
|
||||
// rival's — are always deferred to a dedicated cinematic (camera pan +
|
||||
// replay + outcome popup) played at the start of the human's next
|
||||
// turn, rather than silently replayed here (possibly off-screen) like
|
||||
// ordinary field battles. City tiles stay marked once explored (see
|
||||
// MapView.refresh()'s `explored[idx]` check), so this reaches a known
|
||||
// rival city even if it's currently outside the fog-of-war vision set.
|
||||
// Combats against the HUMAN's cities are deferred to a dedicated
|
||||
// cinematic (camera pan + replay + outcome popup) at the start of the
|
||||
// human's next turn — those demand a response. Rival-vs-rival battles
|
||||
// at cities the human has explored play live right here instead: pan,
|
||||
// replay, and a status-log line, no blocking popup. City tiles stay
|
||||
// marked once explored (see MapView.refresh()'s `explored[idx]`
|
||||
// check), so this reaches a known rival city even if it's currently
|
||||
// outside the fog-of-war vision set. `e.defenderCiv === human` catches
|
||||
// a city the human just lost this step (city.civ already shows the
|
||||
// conqueror by the time we classify).
|
||||
const human = this.state.humanIndex;
|
||||
const elsewhere = [];
|
||||
const rivalCityCombats = [];
|
||||
for (const e of combatEvents) {
|
||||
const city = Logic.cityAt(this.state, e.x, e.y);
|
||||
const cityExplored = city && this.state.explored[human][Logic.tileIndex(this.state.world, city.x, city.y)];
|
||||
if (city && (city.civ === human || cityExplored)) {
|
||||
if (city && (city.civ === human || e.defenderCiv === human)) {
|
||||
this.pendingCityAttacks.push({ e, cityId: city.id, cityName: city.name, x: city.x, y: city.y });
|
||||
} else if (city && cityExplored) {
|
||||
rivalCityCombats.push({ e, city });
|
||||
} else {
|
||||
elsewhere.push(e);
|
||||
}
|
||||
|
|
@ -1318,8 +1334,25 @@ export default class CivilizationGame extends Phaser.Scene {
|
|||
this.time.delayedCall(90, stepCiv);
|
||||
});
|
||||
};
|
||||
if (visibleCombat.length) this.view.animateCombat(visibleCombat, afterCombat);
|
||||
else afterCombat();
|
||||
// Rival-vs-rival city battles first (pan + replay + log line, one at a
|
||||
// time), then ordinary visible field combats, then the move glide.
|
||||
const playRivalCityCombats = (done) => {
|
||||
const item = rivalCityCombats.shift();
|
||||
if (!item) { done(); return; }
|
||||
const { e, city } = item;
|
||||
const attackerName = this.state.civs[e.attackerCiv]?.name ?? 'An enemy';
|
||||
const captured = e.attackerWon && city.civ === e.attackerCiv;
|
||||
const outcome = captured ? `${city.name} has fallen to ${attackerName}!`
|
||||
: e.attackerWon ? `${city.name}'s defenders were defeated!`
|
||||
: `${city.name} held its ground!`;
|
||||
this.logMessage(`${attackerName} attacked ${city.name} — ${outcome}`);
|
||||
this.view.panToTile(city.x, city.y);
|
||||
this.view.animateCombat([e], () => playRivalCityCombats(done));
|
||||
};
|
||||
playRivalCityCombats(() => {
|
||||
if (visibleCombat.length) this.view.animateCombat(visibleCombat, afterCombat);
|
||||
else afterCombat();
|
||||
});
|
||||
};
|
||||
stepCiv();
|
||||
}
|
||||
|
|
@ -1342,10 +1375,10 @@ export default class CivilizationGame extends Phaser.Scene {
|
|||
}
|
||||
}
|
||||
|
||||
// Combats against any city the human has explored (their own or a
|
||||
// rival's), deferred from runToHumanTurn()'s per-civ stepping (see
|
||||
// stepCiv there) so they always get a camera pan + replay + outcome popup
|
||||
// instead of possibly playing silently off-screen.
|
||||
// Combats against the human's own cities, deferred from runToHumanTurn()'s
|
||||
// per-civ stepping (see stepCiv there) so they always get a camera pan +
|
||||
// replay + outcome popup at the start of the human's turn. Rival-vs-rival
|
||||
// city battles play live during the AI phase instead.
|
||||
announceCityAttacks() {
|
||||
const attacks = this.pendingCityAttacks;
|
||||
this.pendingCityAttacks = [];
|
||||
|
|
@ -1504,6 +1537,11 @@ export default class CivilizationGame extends Phaser.Scene {
|
|||
openTechScreen(this, this.rules, this.state, () => {
|
||||
this.modalOpen = false;
|
||||
this.refreshHud();
|
||||
// Resume any statuses that queued while the tech screen was up — in
|
||||
// particular the start-of-turn flow, where the research-complete
|
||||
// popup's onDismiss opens this screen with the rest of the turn's
|
||||
// announcements still waiting in the queue.
|
||||
this.showNextStatus();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -380,14 +380,21 @@ export function cityYields(rules, state, city) {
|
|||
}
|
||||
|
||||
const grossShield = Math.floor(shield * shieldMult);
|
||||
const netShield = Math.max(0, grossShield - supportShields);
|
||||
// Waste — corruption's production sibling: the same capital-distance decay
|
||||
// applied to shields, at half strength (full strength would zero out
|
||||
// distant cities' build boxes entirely under Despotism). Courthouse halves
|
||||
// it, same as corruption.
|
||||
let waste = Math.floor(grossShield * gov.corruptionFactor * Math.min(1, dist / 20) * 0.5);
|
||||
if (city.buildings.courthouse) waste = Math.floor(waste * 0.5);
|
||||
waste = Math.min(waste, grossShield);
|
||||
const netShield = Math.max(0, grossShield - waste - supportShields);
|
||||
const foodNeed = city.size * FOOD_PER_CITIZEN + settlerFood;
|
||||
const upkeep = Object.keys(city.buildings)
|
||||
.reduce((sum, id) => sum + (rules.buildings[id]?.upkeep ?? 0), 0);
|
||||
|
||||
return {
|
||||
food, foodNeed, foodSurplus: food - foodNeed,
|
||||
shield: netShield, grossShield, supportShields,
|
||||
shield: netShield, grossShield, waste, supportShields,
|
||||
trade, corruption, netTrade, routeTrade,
|
||||
gold: Math.floor(baseGold * goldMult), science: Math.floor(baseScience * sciMult),
|
||||
upkeep, supportGold,
|
||||
|
|
@ -1337,7 +1344,7 @@ export function makeContacts(rules, state, civIdx, x, y) {
|
|||
// ---------------------------------------------------------------------------
|
||||
// Trade routes (caravans)
|
||||
|
||||
export const TRADE_ROUTE_MIN_DIST = 8;
|
||||
export const TRADE_ROUTE_MIN_DIST = 6;
|
||||
export const MAX_ROUTES = 3;
|
||||
|
||||
export function canEstablishRoute(rules, state, unit) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
computeVisible, isUnitVisibleTo, shieldGrassAt,
|
||||
} from './CivilizationLogic.js';
|
||||
import { Tooltip } from '../../ui/Tooltip.js';
|
||||
import { playSound } from '../../ui/Sounds.js';
|
||||
import { describeUnitStackTooltip, describeCityTooltip } from './CivilizationTooltips.js';
|
||||
|
||||
export const TILE_W = 128;
|
||||
|
|
@ -914,6 +915,9 @@ export class CivilizationMapView {
|
|||
const target = { x: this.isoX(e.x, e.y), y: this.isoY(e.x, e.y) + TILE_H / 2 };
|
||||
const LUNGE_MS = 250;
|
||||
|
||||
const battleSound = this.rules.units[e.attackerType].battleSound;
|
||||
if (battleSound) playSound(this.scene, `sfx-battle-${battleSound}`);
|
||||
|
||||
// Lunge onto the defender's tile...
|
||||
this.scene.tweens.add({
|
||||
targets: attackerGhost.container,
|
||||
|
|
|
|||
|
|
@ -466,12 +466,12 @@ function govBullets(g) {
|
|||
const pro = (text) => out.push({ text, pro: true });
|
||||
const con = (text) => out.push({ text, pro: false });
|
||||
if (g.noScience) con('No research — science output is zero');
|
||||
if (g.corruptionFactor === 0) pro('No corruption at all');
|
||||
if (g.corruptionFactor === 0) pro('No corruption or waste at all');
|
||||
else if (g.corruptionFactor <= 0.4) {
|
||||
pro(g.flatCorruption ? 'Low corruption, the same across the whole empire' : 'Low corruption');
|
||||
} else if (g.corruptionFactor <= 0.7) pro('Reduced corruption');
|
||||
else if (g.corruptionFactor >= 1.5) con('Crippling corruption');
|
||||
else con('Heavy corruption, worse far from the capital');
|
||||
pro(g.flatCorruption ? 'Low corruption & waste, the same across the whole empire' : 'Low corruption & waste');
|
||||
} else if (g.corruptionFactor <= 0.7) pro('Reduced corruption & waste');
|
||||
else if (g.corruptionFactor >= 1.5) con('Crippling corruption & waste');
|
||||
else con('Heavy corruption & waste, worse far from the capital');
|
||||
if (g.tradeBonus) pro(`+${g.tradeBonus} trade on tiles already producing trade`);
|
||||
if (g.despotPenalty) con('−1 food/shield/trade on tiles yielding 3 or more');
|
||||
if (g.freeUnits > 0) pro(`${g.freeUnits} free units per city, then 1 ${g.unitUpkeep}/unit each turn`);
|
||||
|
|
@ -502,12 +502,12 @@ export function openGovernmentScreen(scene, rules, state, onClose) {
|
|||
// aggregated from the same cityYields the cities themselves run on.
|
||||
const cities = Logic.civCities(state, state.humanIndex);
|
||||
const sum = {
|
||||
gross: 0, support: 0, shield: 0, trade: 0, corruption: 0, netTrade: 0,
|
||||
gross: 0, waste: 0, support: 0, shield: 0, trade: 0, corruption: 0, netTrade: 0,
|
||||
gold: 0, upkeep: 0, supportGold: 0, science: 0, pop: 0,
|
||||
};
|
||||
for (const c of cities) {
|
||||
const y = Logic.cityYields(rules, state, c);
|
||||
sum.gross += y.grossShield; sum.support += y.supportShields; sum.shield += y.shield;
|
||||
sum.gross += y.grossShield; sum.waste += y.waste; sum.support += y.supportShields; sum.shield += y.shield;
|
||||
sum.trade += y.trade; sum.corruption += y.corruption; sum.netTrade += y.netTrade;
|
||||
sum.gold += y.gold; sum.upkeep += y.upkeep; sum.supportGold += y.supportGold;
|
||||
sum.science += y.science; sum.pop += c.size;
|
||||
|
|
@ -521,7 +521,7 @@ export function openGovernmentScreen(scene, rules, state, onClose) {
|
|||
}));
|
||||
const netGold = sum.gold - sum.upkeep - sum.supportGold;
|
||||
content.add(scene.add.text(left, top + 44,
|
||||
`Production: ${sum.shield} shields/turn (${sum.gross} gross − ${sum.support} unit support) `
|
||||
`Production: ${sum.shield} shields/turn (${sum.gross} gross − ${sum.waste} waste − ${sum.support} unit support) `
|
||||
+ `Trade: ${sum.netTrade}/turn (${sum.trade} − ${sum.corruption} corruption)`, {
|
||||
fontFamily: FONT, fontSize: '19px', color: COLORS.textHex,
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ const EFFECT_TEXT = {
|
|||
farmfood: (b) => `+${Math.round(b.value * 100)}% food from farmland-improved tiles worked by this city.`,
|
||||
defenseair: (b) => `Multiplies this city's defense against air attacks by ${b.value}.`,
|
||||
power: (b) => `+${Math.round(b.value * 100)}% shield output from the city's Factory (requires a Factory).`,
|
||||
corruption: (b) => `Cuts this city's corruption by ${Math.round(b.value * 100)}%.`,
|
||||
corruption: (b) => `Cuts this city's corruption and waste by ${Math.round(b.value * 100)}%.`,
|
||||
gold: (b) => `+${Math.round(b.value * 100)}% gold income from this city's trade.`,
|
||||
veterans: () => 'Units built here start as veterans, with a combat bonus.',
|
||||
sizecap: (b) => (b.id === 'sewersystem'
|
||||
|
|
@ -52,7 +52,7 @@ const EFFECT_TEXT = {
|
|||
oceanfood: (b) => `+${b.value} food from every ocean tile this city works.`,
|
||||
shields: (b) => `+${Math.round(b.value * 100)}% shield (production) output from this city.`,
|
||||
oceanshield: (b) => `+${b.value} shield from every ocean tile this city works.`,
|
||||
palace: () => 'Makes this your capital — sharply reduces corruption and stores the seat of government.',
|
||||
palace: () => 'Makes this your capital — corruption and waste grow with distance from it.',
|
||||
defensesea: (b) => `Multiplies this city's defense against naval attacks by ${b.value}.`,
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue