From 737e5fe37f33fd68a95d407406d527b1466b5636 Mon Sep 17 00:00:00 2001 From: jakob Date: Fri, 22 Jan 2021 01:56:23 -0500 Subject: [PATCH] created GitHub repository --- .gitignore | 5 + .idea/.gitignore | 8 + .idea/ftd-data-convert.iml | 2 + .idea/misc.xml | 16 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/xcode.xml | 4 + Examples/buildings-roundabout.json | 2750 +++++++++++++++++ Examples/buildings.json | 2750 +++++++++++++++++ Examples/buildings/Freeze.csv | 13 + Examples/buildings/Mine.csv | 11 + Examples/buildings/SpikeRoller.csv | 15 + Examples/buildings/TurretBasic.csv | 26 + Examples/buildings/TurretBomb.csv | 15 + Examples/buildings/TurretFlamethrower.csv | 11 + Examples/rounds-roundabout.json | 367 +++ Examples/rounds.csv | 26 + Examples/rounds.json | 367 +++ Examples/statusEffects-roundabout.json | 76 + Examples/statusEffects.csv | 18 + Examples/statusEffects.json | 76 + Package.resolved | 16 + Package.swift | 18 + README.md | 22 + Sources/json-csv/ColumnFormat.swift | 24 + Sources/json-csv/Csv.swift | 155 + Sources/json-csv/DecodeCsvError.swift | 23 + Sources/json-csv/DecodeJsonError.swift | 20 + Sources/json-csv/Extensions/Array.swift | 14 + .../ArrayOfCollectionOfHasDefault.swift | 20 + Sources/json-csv/Extensions/Dictionary.swift | 23 + Sources/json-csv/Extensions/HasDefault.swift | 16 + Sources/json-csv/Extensions/JSON.swift | 50 + Sources/json-csv/Extensions/Optional.swift | 16 + Sources/json-csv/Extensions/String.swift | 90 + Sources/json-csv/Extensions/URL.swift | 29 + Sources/json-csv/Log.swift | 14 + Sources/json-csv/ObjectRow.swift | 23 + Sources/json-csv/SwiftyJSON.swift | 1401 +++++++++ Sources/json-csv/main.swift | 449 +++ test.sh | 28 + 41 files changed, 9021 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/ftd-data-convert.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/xcode.xml create mode 100644 Examples/buildings-roundabout.json create mode 100644 Examples/buildings.json create mode 100644 Examples/buildings/Freeze.csv create mode 100644 Examples/buildings/Mine.csv create mode 100644 Examples/buildings/SpikeRoller.csv create mode 100644 Examples/buildings/TurretBasic.csv create mode 100644 Examples/buildings/TurretBomb.csv create mode 100644 Examples/buildings/TurretFlamethrower.csv create mode 100644 Examples/rounds-roundabout.json create mode 100644 Examples/rounds.csv create mode 100644 Examples/rounds.json create mode 100644 Examples/statusEffects-roundabout.json create mode 100644 Examples/statusEffects.csv create mode 100644 Examples/statusEffects.json create mode 100644 Package.resolved create mode 100644 Package.swift create mode 100644 README.md create mode 100644 Sources/json-csv/ColumnFormat.swift create mode 100644 Sources/json-csv/Csv.swift create mode 100644 Sources/json-csv/DecodeCsvError.swift create mode 100644 Sources/json-csv/DecodeJsonError.swift create mode 100644 Sources/json-csv/Extensions/Array.swift create mode 100644 Sources/json-csv/Extensions/ArrayOfCollectionOfHasDefault.swift create mode 100644 Sources/json-csv/Extensions/Dictionary.swift create mode 100644 Sources/json-csv/Extensions/HasDefault.swift create mode 100644 Sources/json-csv/Extensions/JSON.swift create mode 100644 Sources/json-csv/Extensions/Optional.swift create mode 100644 Sources/json-csv/Extensions/String.swift create mode 100644 Sources/json-csv/Extensions/URL.swift create mode 100644 Sources/json-csv/Log.swift create mode 100644 Sources/json-csv/ObjectRow.swift create mode 100644 Sources/json-csv/SwiftyJSON.swift create mode 100644 Sources/json-csv/main.swift create mode 100755 test.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95c4320 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/ftd-data-convert.iml b/.idea/ftd-data-convert.iml new file mode 100644 index 0000000..b5b8674 --- /dev/null +++ b/.idea/ftd-data-convert.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..7026b53 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..38f0355 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..c2365ab --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/xcode.xml b/.idea/xcode.xml new file mode 100644 index 0000000..33a4457 --- /dev/null +++ b/.idea/xcode.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Examples/buildings-roundabout.json b/Examples/buildings-roundabout.json new file mode 100644 index 0000000..ab0d8c8 --- /dev/null +++ b/Examples/buildings-roundabout.json @@ -0,0 +1,2750 @@ +{ + "spikeRoller" : [ + [ + [ + { + "fireRate" : 2, + "view" : { + + }, + "projectile" : { + "speed" : 5, + "type" : "Basic", + "size" : 1, + "view" : { + "textureName" : "SpikedBall", + "animationDuration" : 0.125 + }, + "pierce" : 5, + "maxTravelDistance" : 10, + "damage" : 90 + }, + "cost" : { + "iron" : 100 + }, + "description" : "Shoots a spiked ball in a fixed straight line which goes through many enemies", + "name" : "Spike roller", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "type" : "SpikeRoller" + }, + { + "description" : "Shoots a bigger spiked ball which does more damage", + "projectile" : { + "maxTravelDistance" : 12, + "pierce" : 7, + "type" : "Basic", + "size" : 1.25, + "speed" : 4, + "damage" : 180, + "view" : { + "animationDuration" : 0.125, + "textureName" : "SpikedBallBig" + } + }, + "type" : "SpikeRoller", + "view" : { + + }, + "name" : "Big spiked ball", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "fireRate" : 2.5, + "cost" : { + "iron" : 250 + } + }, + { + "description" : "Spiked ball does more damage, pierce, and slight knockback", + "projectile" : { + "maxTravelDistance" : 14, + "size" : 1.25, + "speed" : 3.5, + "damage" : 360, + "view" : { + "textureName" : "SpikedBallHeavy", + "animationDuration" : 0.125 + }, + "pierce" : 10, + "type" : "Basic", + "effects" : [ + { + "type" : "Stun", + "duration" : 0.5 + }, + { + "type" : "Knockback", + "duration" : 0.5, + "distancePerSecond" : 1 + } + ] + }, + "fireRate" : 2.75, + "type" : "SpikeRoller", + "name" : "Heavy spiked ball", + "cost" : { + "iron" : 550 + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "view" : { + + } + }, + { + "projectile" : { + "maxTravelDistance" : 16, + "effects" : [ + { + "duration" : 1.5, + "type" : "Stun" + }, + { + "distancePerSecond" : 2, + "type" : "Knockback", + "duration" : 1.5 + } + ], + "speed" : 3, + "size" : 2, + "pierce" : 14, + "damage" : 540, + "view" : { + "textureName" : "SteamRoller", + "animationDuration" : 0.125 + }, + "type" : "Basic" + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "fireRate" : 3, + "description" : "Slightly more pierce, much more knockback", + "type" : "SpikeRoller", + "name" : "Steam roller", + "view" : { + + }, + "cost" : { + "iron" : 1450 + } + }, + { + "cost" : { + "iron" : 4150 + }, + "type" : "SpikeRoller", + "view" : { + + }, + "projectile" : { + "type" : "Basic", + "damage" : 810, + "maxTravelDistance" : 18, + "view" : { + "animationDuration" : 0.125, + "textureName" : "SteamRollerGlue" + }, + "size" : 2, + "effects" : [ + { + "type" : "Stun", + "duration" : 2 + }, + { + "type" : "Knockback", + "duration" : 2, + "distancePerSecond" : 2.5 + }, + { + "duration" : 4, + "speedMultiplier" : 0.75, + "type" : "Glue" + } + ], + "speed" : 2.5, + "pierce" : 19 + }, + "description" : "Glue keeps enemies slower. Also more pierce, damage, and knockback", + "fireRate" : 3.5, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "name" : "Glue roller" + } + ], + [ + { + "type" : "SpikeRoller", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "description" : "Shoots smaller spiked balls but much faster", + "projectile" : { + "maxTravelDistance" : 12, + "damage" : 72, + "size" : 0.5, + "type" : "Basic", + "view" : { + "animationDuration" : 0.125, + "textureName" : "SpikedBallSmall" + }, + "speed" : 10, + "pierce" : 3 + }, + "cost" : { + "iron" : 100, + "copper" : 200, + "coal" : 400 + }, + "fireRate" : 0.5, + "name" : "Small spiked ball", + "view" : { + + } + } + ], + [ + { + "fireRate" : 0.0625, + "type" : "SpikeRoller", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "view" : { + + }, + "description" : "Shoots many bullets very fast", + "name" : "Machine gun", + "cost" : { + "copper" : 600, + "iron" : 100, + "coal" : 1200 + }, + "projectile" : { + "maxTravelDistance" : 14, + "type" : "Basic", + "pierce" : 2, + "speed" : 12, + "view" : { + "textureName" : "Bullet" + }, + "damage" : 108, + "size" : 0.125 + } + } + ], + [ + { + "view" : { + + }, + "name" : "Rocket gun", + "fireRate" : 0.125, + "type" : "SpikeRoller", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "description" : "Shoots rockets instead of bullets", + "cost" : { + "copper" : 1000, + "iron" : 100, + "coal" : 2800 + }, + "projectile" : { + "damage" : 108, + "type" : "Bomb", + "view" : { + "textureName" : "Missile" + }, + "explosionView" : "Basic", + "blastRadius" : 0.5, + "speed" : 14.4, + "maxTravelDistance" : 18, + "size" : 0.25 + } + } + ], + [ + { + "numProjectiles" : 3, + "name" : "Gold rocket gun", + "view" : { + + }, + "angleBetweenProjectiles" : "30°", + "cost" : { + "gold" : 250, + "coal" : 100, + "copper" : 1800, + "iron" : 100 + }, + "type" : "SpikeRoller", + "description" : "Shoots better rockets, and 3 at different angles", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "projectile" : { + "view" : { + "textureName" : "MissileSuper" + }, + "size" : 0.25, + "speed" : 14.4, + "type" : "Bomb", + "blastRadius" : 0.75, + "damage" : 216, + "explosionView" : "Basic", + "maxTravelDistance" : 24 + }, + "fireRate" : 0.10000000000000001 + } + ], + [ + { + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "view" : { + + }, + "description" : "Shoots 7 rockets at different angles", + "projectile" : { + "speed" : 14.4, + "damage" : 216, + "view" : { + "textureName" : "MissileSuper" + }, + "type" : "Bomb", + "maxTravelDistance" : 32, + "explosionView" : "Basic", + "blastRadius" : 0.75, + "size" : 0.25 + }, + "type" : "SpikeRoller", + "numProjectiles" : 7, + "name" : "Platinum rocket gun", + "cost" : { + "coal" : 500, + "gold" : 750, + "iron" : 100, + "copper" : 3400 + }, + "angleBetweenProjectiles" : "15°", + "fireRate" : 0.080000000000000002 + } + ] + ], + [ + [ + { + "description" : "Spiked balls have increased pierce, and slightly better fire rate \/ damage", + "name" : "Electric spikes", + "type" : "SpikeRoller", + "projectile" : { + "size" : 1, + "type" : "Basic", + "speed" : 7.5, + "damage" : 135, + "pierce" : 10, + "view" : { + "animationDuration" : 0.125, + "textureName" : "SpikedBallElectric" + }, + "maxTravelDistance" : 12 + }, + "fireRate" : 1.6000000000000001, + "cost" : { + "coal" : 2100, + "iron" : 200 + }, + "view" : { + + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ] + } + ] + ], + [ + [ + { + "name" : "Plasma ball", + "fireRate" : 1.28, + "description" : "Shoots balls of plasma instead of spiked balls, with even better pierce", + "cost" : { + "coal" : 5300, + "iron" : 600 + }, + "view" : { + + }, + "type" : "SpikeRoller", + "projectile" : { + "damage" : 202.5, + "view" : { + "textureName" : "PlasmaBall" + }, + "size" : 1, + "type" : "Basic", + "maxTravelDistance" : 16, + "pierce" : 20, + "speed" : 11.25 + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ] + } + ] + ], + [ + [ + { + "name" : "Laser turret", + "description" : "Instead of a spiked ball, shoots a laser beam with infinite pierce", + "cost" : { + "uranium" : 400, + "iron" : 2200 + }, + "view" : { + + }, + "type" : "MountedLaser", + "projectile" : { + "view" : { + "color" : "0xE02010" + }, + "width" : 0.25, + "type" : "Beam", + "dps" : 400 + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ] + } + ] + ], + [ + [ + { + "cost" : { + "iron" : 5400, + "uranium" : 2000 + }, + "view" : { + + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "name" : "Giant flashlight", + "description" : "Giant flashlight apparently deals more damage, also the beam gets wider the further away", + "projectile" : { + "view" : { + "color" : "0xFFF040" + }, + "dps" : 720, + "initialWidth" : 0.5, + "widthPerTile" : 0.25, + "type" : "Flashlight" + }, + "type" : "MountedLaser" + } + ] + ] + ], + "mine" : [ + [ + [ + { + "degradeMultiplier" : 0.75, + "isDegradeLocal" : true, + "description" : "Mines nearby ores, increasing your resources", + "name" : "Mine", + "radius" : 2, + "ironOresPerYield" : 1, + "cost" : { + "uranium" : 1000, + "aluminum" : 500, + "iron" : 250, + "gold" : 100, + "coal" : 250 + }, + "view" : { + + }, + "ticksPerYield" : 800, + "type" : "BasicMine", + "oresPerYield" : 10 + }, + { + "ticksPerYield" : 16, + "type" : "BasicMine", + "ironOresPerYield" : 5, + "degradeMultiplier" : 0.75, + "oresPerYield" : 1, + "name" : "Laser mine", + "isDegradeLocal" : false, + "description" : "Only mines its own tile, but faster and more consistently", + "cost" : { + "uranium" : 2500, + "aluminum" : 500, + "iron" : 250, + "gold" : 150, + "coal" : 250 + }, + "view" : { + + }, + "radius" : 1 + }, + { + "description" : "Mines faster, and additionally mines iron", + "ticksPerYield" : 16, + "name" : "Deep laser mine", + "radius" : 1, + "oresPerYield" : 4, + "cost" : { + "uranium" : 2500, + "iron" : 2750, + "aluminum" : 8000, + "gold" : 400 + }, + "type" : "IronMine", + "view" : { + + } + }, + { + "description" : "Mines insane amounts of iron. Breaks towers", + "ticksPerYield" : 1, + "radius" : 1, + "oresPerYield" : 2, + "name" : "Core laser mine", + "view" : { + + }, + "type" : "IronMine", + "cost" : { + "gold" : 1150 + } + } + ], + [ + { + "name" : "Bomb mine", + "radius" : 2, + "type" : "BasicMine", + "description" : "Mines larger yields but slower", + "oresPerYield" : 50, + "view" : { + + }, + "ticksPerYield" : 1600, + "cost" : { + "gold" : 200 + } + } + ], + [ + { + "description" : "Mines larger yields and radius but slower", + "ticksPerYield" : 3200, + "radius" : 3, + "oresPerYield" : 200, + "name" : "Bigger bomb mine", + "view" : { + + }, + "type" : "BasicMine", + "cost" : { + "gold" : 400 + } + } + ], + [ + { + "name" : "Atomic bomb mine", + "description" : "Massive yields", + "oresPerYield" : 2000, + "view" : { + + }, + "radius" : 3, + "ticksPerYield" : 6400, + "type" : "BasicMine", + "cost" : { + "gold" : 1250 + } + } + ] + ], + [ + [ + { + "name" : "Larger radius", + "description" : "Mines a bigger radius, but less ore", + "oresPerYield" : 7, + "view" : { + + }, + "radius" : 3, + "ticksPerYield" : 1200, + "type" : "BasicMine", + "cost" : { + "gold" : 200 + } + } + ] + ], + [ + [ + { + "name" : "Fracking mine", + "description" : "Mines an even larger radius, but degrades buildings in the mining zone", + "oresPerYield" : 21, + "view" : { + + }, + "radius" : 5, + "ticksPerYield" : 1800, + "cost" : { + "gold" : 350 + }, + "type" : "FrackingMine" + } + ] + ], + [ + [ + { + "description" : "Mines a huge radius, but degrades every tower", + "cost" : { + "gold" : 800 + }, + "radius" : 10, + "name" : "Super fracking mine", + "type" : "FrackingMine", + "oresPerYield" : 189, + "view" : { + + }, + "ticksPerYield" : 2700 + } + ] + ] + ], + "turretFlamethrower" : [ + [ + [ + { + "projectile" : { + "effects" : [ + { + "dps" : 15, + "type" : "Burn", + "duration" : 5 + } + ], + "damage" : 10, + "view" : { + "textureName" : "Fire" + }, + "pierce" : 3, + "size" : 0.25, + "speed" : 8, + "maxTravelDistance" : 2, + "type" : "Basic" + }, + "description" : "Close range tower with good pierce and a burn effect which persists after enemies leave the tower's radius", + "fireRate" : 0.0625, + "view" : { + + }, + "turnSpeed" : "360°", + "cost" : { + "copper" : 250, + "coal" : 25, + "gold" : 150, + "iron" : 50 + }, + "name" : "Flamethrower", + "radius" : 2, + "type" : "ProjectileTurret", + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ] + }, + { + "projectile" : { + "effects" : [ + { + "duration" : 7.5, + "dps" : 60, + "type" : "Burn" + } + ], + "damage" : 20, + "view" : { + "textureName" : "FireVolatile" + }, + "pierce" : 4, + "size" : 0.25, + "speed" : 8, + "type" : "Basic", + "maxTravelDistance" : 2 + }, + "description" : "More direct damage, much more burn damage, and longer lasting burn", + "fireRate" : 0.0625, + "view" : { + + }, + "turnSpeed" : "360°", + "cost" : { + "copper" : 650, + "coal" : 125, + "iron" : 75, + "gold" : 600 + }, + "name" : "More volatile fuel", + "radius" : 2, + "type" : "ProjectileTurret", + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ] + }, + { + "projectile" : { + "pierce" : 5, + "damage" : 40, + "effects" : [ + { + "duration" : 11.25, + "dps" : 120, + "type" : "Burn" + } + ], + "speed" : 8, + "type" : "Basic", + "size" : 0.25, + "maxTravelDistance" : 2, + "view" : { + "textureName" : "FireYellow" + } + }, + "description" : "More pierce, damage, burn damage, and burn lasts much longer", + "fireRate" : 0.0625, + "view" : { + + }, + "turnSpeed" : "360°", + "cost" : { + "coal" : 525, + "iron" : 150, + "copper" : 2250 + }, + "name" : "Yellow fire", + "radius" : 2, + "type" : "ProjectileTurret", + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ] + }, + { + "name" : "Green fire", + "turnSpeed" : "432°", + "cost" : { + "copper" : 8650, + "iron" : 375, + "coal" : 1325 + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ], + "radius" : 2.5, + "projectile" : { + "type" : "Basic", + "pierce" : 7, + "size" : 0.25, + "damage" : 80, + "effects" : [ + { + "type" : "ChemBurn", + "duration" : 16.875, + "dps" : 240 + } + ], + "speed" : 8, + "view" : { + "textureName" : "FireGreen" + }, + "maxTravelDistance" : 2.5 + }, + "type" : "ProjectileTurret", + "view" : { + + }, + "description" : "Chemical burn (can be combined with freeze), more damage", + "fireRate" : 0.0625 + }, + { + "view" : { + + }, + "description" : "Replaces with the doom effect: major percent damage over time, slower speed, and multiplied damage from all attacks, permanently", + "cost" : { + "iron" : 825, + "coal" : 2925, + "uranium" : 1000 + }, + "fireRate" : 0.0625, + "turnSpeed" : "518°", + "type" : "ProjectileTurret", + "projectile" : { + "view" : { + "textureName" : "FireBlack" + }, + "maxTravelDistance" : 2.5, + "damage" : 160, + "pierce" : 9, + "speed" : 8, + "size" : 0.25, + "effects" : [ + { + "vulnerabilityDamageMultiplier" : 1.75, + "finalFractionDps" : 0.040000000000000001, + "speedMultiplier" : 0.75, + "fractionDpsChangeDuration" : 3, + "initialFractionDps" : 0.25, + "type" : "Doom" + } + ], + "type" : "Basic" + }, + "radius" : 2.5, + "name" : "Black fire", + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ] + } + ], + [ + { + "cost" : { + "coal" : 150, + "iron" : 1725 + }, + "fireRate" : 0.0625, + "description" : "Increased pierce, damage, and turn speed", + "turnSpeed" : "540°", + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ], + "view" : { + + }, + "name" : "Faster fuel", + "projectile" : { + "pierce" : 7, + "type" : "Basic", + "maxTravelDistance" : 2.25, + "size" : 0.25, + "view" : { + "textureName" : "Fire" + }, + "speed" : 8, + "effects" : [ + { + "duration" : 5, + "type" : "Burn", + "dps" : 22.5 + } + ], + "damage" : 30 + }, + "radius" : 2.25, + "type" : "ProjectileTurret" + } + ], + [ + { + "name" : "Larger flames", + "type" : "ProjectileTurret", + "projectile" : { + "type" : "Basic", + "view" : { + "textureName" : "FireBig" + }, + "speed" : 8, + "size" : 0.375, + "pierce" : 9, + "maxTravelDistance" : 2.5, + "damage" : 60, + "effects" : [ + { + "type" : "Burn", + "duration" : 7, + "dps" : 45 + } + ] + }, + "turnSpeed" : "648°", + "view" : { + + }, + "cost" : { + "coal" : 400 + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ], + "description" : "Increased pierce, damage, turn speed, and projectile size", + "radius" : 2.5, + "fireRate" : 0.0625 + } + ], + [ + { + "projectile" : { + "effects" : [ + { + "type" : "Burn", + "duration" : 7, + "dps" : 45 + } + ], + "damage" : 90, + "view" : { + "textureName" : "FireBig" + }, + "pierce" : 14, + "size" : 0.45000000000000001, + "speed" : 8, + "maxTravelDistance" : 2.5, + "type" : "Basic" + }, + "description" : "Shoots flames around the entire tower (TODO)", + "fireRate" : 0.0625, + "view" : { + + }, + "turnSpeed" : "0°", + "cost" : { + "coal" : 900 + }, + "name" : "Circle of fire", + "radius" : 2.5, + "type" : "ProjectileTurret", + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ] + } + ], + [ + { + "projectile" : { + "maxTravelDistance" : 3, + "damage" : 180, + "pierce" : 20, + "speed" : 8, + "view" : { + "textureName" : "FireBig" + }, + "effects" : [ + { + "duration" : 8.4000000000000004, + "type" : "Burn", + "dps" : 67.5 + } + ], + "size" : 0.45000000000000001, + "type" : "Basic" + }, + "description" : "Increased range and damage (TODO)", + "fireRate" : 0.0625, + "view" : { + + }, + "turnSpeed" : "0°", + "cost" : { + "coal" : 2400 + }, + "name" : "Golden circle", + "radius" : 3, + "type" : "ProjectileTurret", + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ] + } + ], + [ + { + "turnSpeed" : "0°", + "view" : { + + }, + "radius" : 3, + "name" : "Inferno", + "type" : "ProjectileTurret", + "description" : "Massive damage, and makes enemies in radius slower and more vulnerable", + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ], + "fireRate" : 0.0625, + "projectile" : { + "view" : { + "textureName" : "FireBig" + }, + "type" : "Basic", + "speed" : 8, + "size" : 0.45000000000000001, + "pierce" : 40, + "damage" : 540, + "maxTravelDistance" : 3, + "effects" : [ + { + "type" : "Burn", + "duration" : 8.4000000000000004, + "dps" : 135 + }, + { + "type" : "Slow", + "speedMultiplier" : 0.5, + "duration" : 0.25 + }, + { + "type" : "Vulnerable", + "damageMultiplier" : 1.5, + "duration" : 0.25 + } + ] + }, + "cost" : { + "uranium" : 1000, + "coal" : 5400 + } + } + ] + ] + ], + "turretBomb" : [ + [ + [ + { + "name" : "Bomb launcher", + "view" : { + + }, + "turnSpeed" : "720°", + "type" : "ProjectileTurret", + "radius" : 6, + "fireRate" : 1.5, + "description" : "Shoots a bomb which explodes on impact, damaging nearby enemies", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "projectile" : { + "view" : { + "textureName" : "Bomb" + }, + "blastRadius" : 1, + "size" : 0.5, + "speed" : 7, + "type" : "Bomb", + "maxTravelDistance" : 6.5, + "damage" : 70, + "explosionView" : "Basic" + }, + "cost" : { + "iron" : 25, + "copper" : 75 + } + }, + { + "description" : "Increased damage", + "view" : { + + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "cost" : { + "iron" : 125, + "copper" : 225 + }, + "name" : "Heavy bombs", + "radius" : 6, + "fireRate" : 1.5, + "projectile" : { + "blastRadius" : 1, + "view" : { + "textureName" : "BombHeavy" + }, + "speed" : 7, + "maxTravelDistance" : 6.5, + "size" : 0.5, + "damage" : 140, + "type" : "Bomb", + "explosionView" : "Basic" + }, + "turnSpeed" : "720°", + "type" : "ProjectileTurret" + }, + { + "radius" : 6, + "description" : "Increased damage, slight burn", + "type" : "ProjectileTurret", + "turnSpeed" : "720°", + "projectile" : { + "maxTravelDistance" : 6.5, + "explosionView" : "Basic", + "effects" : [ + { + "duration" : 5, + "type" : "Burn", + "dps" : 20 + } + ], + "damage" : 280, + "size" : 0.59999999999999998, + "type" : "Bomb", + "speed" : 7, + "view" : { + "textureName" : "BombPropane" + }, + "blastRadius" : 1.2 + }, + "cost" : { + "coal" : 150, + "copper" : 475, + "iron" : 375 + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "name" : "Propane bombs", + "view" : { + + }, + "fireRate" : 1.8 + }, + { + "radius" : 6, + "fireRate" : 1.98, + "name" : "Napalm bombs", + "projectile" : { + "blastRadius" : 1.4399999999999999, + "type" : "Bomb", + "effects" : [ + { + "duration" : 5, + "dps" : 80, + "type" : "Burn" + } + ], + "view" : { + "textureName" : "BombNapalm" + }, + "speed" : 7, + "size" : 0.71999999999999997, + "explosionView" : "Basic", + "damage" : 336, + "maxTravelDistance" : 6.5 + }, + "cost" : { + "copper" : 975, + "coal" : 400, + "iron" : 375 + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "description" : "Slightly increased damage, major burn", + "type" : "ProjectileTurret", + "view" : { + + }, + "turnSpeed" : "720°" + }, + { + "fireRate" : 2.9700000000000002, + "view" : { + + }, + "type" : "ProjectileTurret", + "turnSpeed" : "720°", + "cost" : { + "coal" : 2900, + "uranium" : 2500, + "copper" : 1075 + }, + "radius" : 6, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "name" : "Atomic bombs", + "description" : "Increased damage and blast radius, replaces effect with chemical burn", + "projectile" : { + "damage" : 1008, + "view" : { + "textureName" : "BombNuke" + }, + "type" : "Bomb", + "maxTravelDistance" : 6.5, + "effects" : [ + { + "duration" : 7.5, + "dps" : 320, + "type" : "ChemBurn" + } + ], + "speed" : 7, + "size" : 0.86399999999999999, + "explosionView" : "Nuke", + "blastRadius" : 2.8799999999999999 + } + } + ], + [ + { + "projectile" : { + "damage" : 105, + "type" : "AirBomb", + "speed" : 7, + "view" : { + "arcs" : true, + "textureName" : "Bomb" + }, + "explosionView" : "Basic", + "blastRadius" : 1 + }, + "needsToRotateToTarget" : false, + "turnSpeed" : "720°", + "fireRate" : 1.5, + "radius" : 7.5, + "type" : "ProjectileTurret", + "cost" : { + "aluminum" : 25 + }, + "description" : "Increased range, and bombs travel above enemies to target", + "view" : { + + }, + "name" : "Upward launcher", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ] + } + ], + [ + { + "projectile" : { + "explosionView" : "Basic", + "damage" : 126, + "blastRadius" : 1, + "type" : "Missile", + "view" : { + "arcs" : true, + "textureName" : "Missile" + }, + "speed" : 9 + }, + "fireRate" : 1.2, + "turnSpeed" : "720°", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "needsToRotateToTarget" : false, + "type" : "ProjectileTurret", + "cost" : { + "aluminum" : 100 + }, + "name" : "Missile launcher", + "view" : { + + }, + "description" : "Massive range, shoots missiles which hone onto targets", + "radius" : 10 + } + ], + [ + { + "type" : "ProjectileTurret", + "projectile" : { + "blastRadius" : 1, + "speed" : 9, + "damage" : 126, + "explosionView" : "Basic", + "type" : "Missile", + "view" : { + "textureName" : "Missile", + "arcs" : true + } + }, + "fireRate" : 1.2, + "view" : { + + }, + "cost" : { + "aluminum" : 350 + }, + "turnSpeed" : "720°", + "description" : "Shoots 3 missiles at once. Special targeting mode means that each missile will target different enemies", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "radius" : 10, + "needsToRotateToTarget" : false, + "name" : "Triple launcher", + "numProjectiles" : 3 + } + ], + [ + { + "needsToRotateToTarget" : false, + "turnSpeed" : "720°", + "projectile" : { + "blastRadius" : 1, + "speed" : 9, + "type" : "Missile", + "view" : { + "textureName" : "Missile", + "arcs" : true + }, + "damage" : 126, + "explosionView" : "Basic" + }, + "fireRate" : 0.17142857142857101, + "name" : "Silo", + "description" : "Increased range, shoots individual bombs but 7x as fast", + "view" : { + + }, + "type" : "ProjectileTurret", + "radius" : 12, + "cost" : { + "aluminum" : 1350 + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ] + } + ], + [ + { + "turnSpeed" : "720°", + "type" : "ProjectileTurret", + "needsToRotateToTarget" : false, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "projectile" : { + "damage" : 378, + "view" : { + "arcs" : true, + "textureName" : "MissileSuper" + }, + "blastRadius" : 1.3999999999999999, + "explosionView" : "Basic", + "type" : "Missile", + "speed" : 14 + }, + "description" : "Shoots better missiles, increased range, slightly faster than before", + "fireRate" : 0.12, + "cost" : { + "aluminum" : 3850 + }, + "view" : { + + }, + "name" : "Super Silo", + "radius" : 14 + } + ] + ], + [ + [ + { + "turnSpeed" : "720°", + "type" : "ProjectileTurret", + "projectile" : { + "type" : "Bomb", + "size" : 0.75, + "view" : { + "textureName" : "BombBig" + }, + "explosionView" : "Basic", + "blastRadius" : 1.3999999999999999, + "speed" : 7, + "maxTravelDistance" : 6.5, + "damage" : 84 + }, + "description" : "Increased blast radius", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "fireRate" : 1.5, + "view" : { + + }, + "name" : "Big bombs", + "cost" : { + "coal" : 75 + }, + "radius" : 6 + } + ] + ], + [ + [ + { + "name" : "Giant bombs", + "projectile" : { + "effects" : [ + { + "duration" : 0.25, + "type" : "Stun" + }, + { + "distancePerSecond" : 0.5, + "duration" : 0.25, + "type" : "Knockback" + } + ], + "maxTravelDistance" : 5, + "damage" : 117.59999999999999, + "view" : { + "textureName" : "BombGiant" + }, + "speed" : 5.5, + "blastRadius" : 1.6799999999999999, + "explosionView" : "Basic", + "type" : "Bomb", + "size" : 1.125 + }, + "radius" : 4.5, + "type" : "ProjectileTurret", + "turnSpeed" : "720°", + "view" : { + + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "description" : "Increased blast radius, slight knockback", + "cost" : { + "coal" : 275 + }, + "fireRate" : 1.5 + } + ] + ], + [ + [ + { + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "radius" : 4.5, + "type" : "ProjectileTurret", + "cost" : { + "coal" : 775 + }, + "projectile" : { + "blastRadius" : 1.8480000000000001, + "type" : "Bomb", + "damage" : 164.63999999999999, + "explosionView" : "Basic", + "effects" : [ + { + "duration" : 1, + "type" : "Stun" + }, + { + "duration" : 1, + "distancePerSecond" : 0.5, + "type" : "Knockback" + } + ], + "view" : { + "textureName" : "BombStun" + }, + "speed" : 4.5, + "size" : 1.125, + "maxTravelDistance" : 5 + }, + "turnSpeed" : "720°", + "name" : "Stun bomb", + "fireRate" : 1.5, + "description" : "Stuns enemies, and more knockback and damage", + "view" : { + + } + } + ] + ], + [ + [ + { + "radius" : 4.5, + "fireRate" : 4, + "view" : { + + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "cost" : { + "coal" : 10775 + }, + "type" : "ProjectileTurret", + "projectile" : { + "blastRadius" : 4, + "explosionView" : "BlackHole", + "view" : { + "textureName" : "BombBlackHole", + "arcs" : "False" + }, + "speed" : 3, + "effects" : [ + { + "pullPerSecond" : 0.25, + "type" : "BlackHoleStun", + "dps" : 300, + "duration" : 4 + } + ], + "type" : "AirBomb", + "damage" : 0 + }, + "description" : "Bombs create a black hole which stuns even resistant enemies and draws them to the center", + "name" : "Black hole bomb", + "turnSpeed" : "720°" + } + ] + ] + ], + "turretBasic" : [ + [ + [ + { + "name" : "Standard", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "view" : { + + }, + "radius" : 4, + "type" : "ProjectileTurret", + "projectile" : { + "size" : 0.0625, + "view" : { + "textureName" : "Bullet" + }, + "speed" : 18, + "pierce" : 1, + "effects" : [ + { + "type" : "ChemBurn", + "dps" : 4032, + "duration" : 0.25, + "fractionDps" : 0.025000000000000001 + } + ], + "maxTravelDistance" : 4.5, + "fractionDamage" : 0.14999999999999999, + "damage" : 50, + "type" : "Basic" + }, + "fireRate" : 0.5, + "cost" : { + "gold" : 100, + "iron" : 50 + }, + "turnSpeed" : "720°", + "description" : "All-around turret with medium damage, fire rate, and range. No pierce and projectiles travel fast" + }, + { + "name" : "Bronze standard", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "view" : { + + }, + "radius" : 4.25, + "type" : "ProjectileTurret", + "projectile" : { + "damage" : 75, + "maxTravelDistance" : 4.75, + "size" : 0.0625, + "pierce" : 1, + "view" : { + "textureName" : "Bullet" + }, + "effects" : [ + { + "fractionDps" : 0.025000000000000001, + "type" : "Hack", + "duration" : 15 + } + ], + "fractionDamage" : 0.14999999999999999, + "type" : "Basic", + "speed" : 18 + }, + "fireRate" : 0.40000000000000002, + "cost" : { + "gold" : 100, + "iron" : 150 + }, + "turnSpeed" : "720°", + "description" : "Increased fire rate, range, and damage" + }, + { + "cost" : { + "gold" : 100, + "iron" : 350 + }, + "type" : "ProjectileTurret", + "fireRate" : 0.32000000000000001, + "description" : "Increased damage, fire rate, range, and pierce", + "turnSpeed" : "720°", + "radius" : 4.5, + "name" : "Silver standard", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "view" : { + + }, + "projectile" : { + "effects" : [ + { + "type" : "Hack", + "duration" : 15, + "fractionDps" : 0.025000000000000001 + } + ], + "speed" : 18, + "fractionDamage" : 0.14999999999999999, + "size" : 0.0625, + "view" : { + "textureName" : "Bullet" + }, + "type" : "Basic", + "damage" : 112, + "maxTravelDistance" : 5, + "pierce" : 2 + } + }, + { + "cost" : { + "gold" : 300, + "iron" : 750 + }, + "type" : "ProjectileTurret", + "fireRate" : 0.25600000000000001, + "description" : "Increased range, pierce, damage, and fire rate", + "turnSpeed" : "720°", + "radius" : 5, + "name" : "Gold standard", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "view" : { + + }, + "projectile" : { + "maxTravelDistance" : 5.5, + "type" : "Basic", + "speed" : 20, + "fractionDamage" : 0.12, + "pierce" : 4, + "effects" : [ + { + "type" : "Hack", + "duration" : 18 + } + ], + "view" : { + "textureName" : "BulletGold" + }, + "size" : 0.0625, + "damage" : 168 + } + }, + { + "fireRate" : 0.20480000000000001, + "type" : "ProjectileTurret", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "projectile" : { + "pierce" : 8, + "size" : 0.0625, + "type" : "Basic", + "speed" : 24, + "maxTravelDistance" : 6.5, + "view" : { + "textureName" : "BulletPlatinum" + }, + "damage" : 252 + }, + "view" : { + + }, + "description" : "Increased pierce, damage, fire rate, and range", + "cost" : { + "iron" : 1550, + "gold" : 300 + }, + "name" : "Platinum standard", + "radius" : 6, + "turnSpeed" : "720°" + }, + { + "name" : "The best one", + "type" : "ProjectileTurret", + "projectile" : { + "size" : 0.25, + "damage" : 1000, + "type" : "Basic", + "maxTravelDistance" : 100, + "pierce" : 1e+100, + "speed" : 24, + "view" : { + "textureName" : "Best" + } + }, + "fireRate" : 0.01, + "turnSpeed" : "720°", + "description" : "This tower is better than you are", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "cost" : { + "iron" : 101550 + }, + "radius" : 100, + "view" : { + + } + } + ], + [ + { + "fireRate" : 3, + "type" : "SniperTurret", + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "projectile" : { + "damage" : 500 + }, + "view" : { + + }, + "description" : "Big range increase, and fires instant projectiles which are guaranteed to hit the intended target. However, fires much slower", + "cost" : { + "iron" : 125 + }, + "name" : "Sniper", + "radius" : 9, + "turnSpeed" : "720°" + }, + [ + + ], + { + "description" : "Increased damage, range, and projectile speed", + "projectile" : { + "maxTravelDistance" : 6, + "type" : "Basic", + "speed" : 21.600000000000001, + "size" : 0.0625, + "damage" : 224, + "pierce" : 2, + "fractionDamage" : 0.12, + "view" : { + "textureName" : "Bullet" + }, + "effects" : [ + { + "duration" : 15, + "type" : "Hack", + "fractionDps" : 0.029999999999999999 + } + ] + }, + "turnSpeed" : "720°", + "name" : "Silver standard lvl2", + "type" : "ProjectileTurret", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "cost" : { + "gold" : 300, + "iron" : 550 + }, + "radius" : 5.5, + "view" : { + + }, + "fireRate" : 0.32000000000000001 + }, + { + "description" : "Increased damage, range, and projectile speed", + "projectile" : { + "maxTravelDistance" : 6.5, + "type" : "Basic", + "fractionDamage" : 0.12, + "speed" : 24, + "damage" : 336, + "pierce" : 4, + "size" : 0.0625, + "view" : { + "textureName" : "Bullet" + } + }, + "turnSpeed" : "720°", + "name" : "Gold standard lvl2", + "type" : "ProjectileTurret", + "radius" : 6, + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "view" : { + + }, + "cost" : { + "iron" : 1150, + "gold" : 300 + }, + "fireRate" : 0.25600000000000001 + }, + { + "radius" : 7, + "name" : "Platinum standard lvl2", + "projectile" : { + "damage" : 504, + "size" : 0.0625, + "speed" : 28.800000000000001, + "maxTravelDistance" : 7.5, + "type" : "Basic", + "pierce" : 8, + "view" : { + "textureName" : "Bullet" + } + }, + "cost" : { + "iron" : 2350 + }, + "description" : "Increased damage, range, and projectile speed", + "fireRate" : 0.20480000000000001, + "turnSpeed" : "720°", + "type" : "ProjectileTurret", + "view" : { + + }, + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ] + } + ], + [ + { + "type" : "SniperTurret", + "projectile" : { + "damage" : 1000 + }, + "description" : "Increased range and damage", + "name" : "Elite sniper", + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "cost" : { + "iron" : 275 + }, + "view" : { + + }, + "turnSpeed" : "720°", + "fireRate" : 3, + "radius" : 10 + }, + { + "cost" : { + "iron" : 575 + }, + "type" : "SniperTurret", + "name" : "Advanced sniper", + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "turnSpeed" : "720°", + "projectile" : { + "damage" : 1000 + }, + "description" : "Increased fire rate", + "radius" : 10, + "view" : { + + }, + "fireRate" : 2.3999999999999999 + }, + [ + + ], + { + "type" : "ProjectileTurret", + "view" : { + + }, + "fireRate" : 0.25600000000000001, + "name" : "Gold standard lvl3", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "cost" : { + "gold" : 300, + "iron" : 2750 + }, + "turnSpeed" : "720°", + "radius" : 7, + "projectile" : { + "fractionDamage" : 0.12, + "maxTravelDistance" : 7.5, + "size" : 0.0625, + "speed" : 28.800000000000001, + "view" : { + "textureName" : "Bullet" + }, + "pierce" : 5, + "type" : "Basic", + "damage" : 672 + }, + "description" : "Increased damage, range, projectile speed, and pierce" + }, + { + "type" : "ProjectileTurret", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "radius" : 8, + "fireRate" : 0.20480000000000001, + "description" : "Increased damage, range, projectile speed, and pierce", + "name" : "Platinum standard lvl3", + "projectile" : { + "maxTravelDistance" : 8.5, + "speed" : 34.560000000000002, + "type" : "Basic", + "damage" : 1008, + "view" : { + "textureName" : "Bullet" + }, + "size" : 0.0625, + "pierce" : 10 + }, + "view" : { + + }, + "turnSpeed" : "720°", + "cost" : { + "iron" : 5550 + } + } + ], + [ + { + "fireRate" : 4, + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "type" : "SniperTurret", + "cost" : { + "iron" : 875 + }, + "name" : "Marine sniper", + "projectile" : { + "damage" : 4000 + }, + "radius" : 11, + "view" : { + + }, + "turnSpeed" : "720°", + "description" : "% damage" + }, + { + "fireRate" : 3.2000000000000002, + "view" : { + + }, + "name" : "Commando sniper", + "projectile" : { + "damage" : 4000 + }, + "type" : "SniperTurret", + "cost" : { + "iron" : 1475 + }, + "radius" : 11, + "turnSpeed" : "720°", + "description" : "Increased fire rate", + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ] + }, + { + "radius" : 15.4, + "projectile" : { + "damage" : 4000 + }, + "cost" : { + "iron" : 2375 + }, + "view" : { + + }, + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "name" : "Agent sniper", + "type" : "SniperTurret", + "turnSpeed" : "720°", + "fireRate" : 2.8799999999999999, + "description" : "Increased range, slightly increased fire rate" + }, + [ + + ], + { + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "turnSpeed" : "720°", + "radius" : 9, + "fireRate" : 0.20480000000000001, + "name" : "Platinum standard lvl4", + "projectile" : { + "speed" : 41.472000000000001, + "pierce" : 15, + "maxTravelDistance" : 9.5, + "damage" : 2016, + "type" : "Basic", + "view" : { + "textureName" : "Bullet" + }, + "size" : 0.0625 + }, + "view" : { + + }, + "description" : "Increased damage, range, projectile speed, and pierce", + "type" : "ProjectileTurret", + "cost" : { + "iron" : 8750 + } + } + ], + [ + { + "description" : "Hack effect deals % damage over time", + "type" : "SniperTurret", + "cost" : { + "iron" : 3275 + }, + "turnSpeed" : "720°", + "name" : "Robot sniper", + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "projectile" : { + "damage" : 4800 + }, + "radius" : 12, + "view" : { + + }, + "fireRate" : 5 + }, + { + "description" : "Increased fire rate", + "cost" : { + "iron" : 4175 + }, + "turnSpeed" : "720°", + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "name" : "Sniper X", + "fireRate" : 4, + "view" : { + + }, + "projectile" : { + "damage" : 4800 + }, + "type" : "SniperTurret", + "radius" : 12 + }, + { + "view" : { + + }, + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "description" : "Increased range, slightly increased fire rate", + "radius" : 16.800000000000001, + "type" : "SniperTurret", + "name" : "Sniper Z", + "turnSpeed" : "720°", + "fireRate" : 3.6000000000000001, + "projectile" : { + "damage" : 4800 + }, + "cost" : { + "iron" : 6875 + } + }, + { + "view" : { + + }, + "fireRate" : 3.6000000000000001, + "description" : "Unlimited range", + "projectile" : { + "damage" : 4800 + }, + "turnSpeed" : "720°", + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "cost" : { + "iron" : 12275 + }, + "name" : ".sniper", + "radius" : 100, + "type" : "SniperTurret" + } + ] + ] + ], + "freeze" : [ + [ + [ + { + "name" : "Ice tower", + "type" : "RadialFreeze", + "description" : "Slows enemies in range. Remember that burn and freeze cancel each other out", + "cost" : { + "iron" : 125 + }, + "radius" : 2.5, + "view" : { + "animationDuration" : 2 + }, + "effects" : [ + { + "type" : "Freeze", + "speedMultiplier" : 0.5 + } + ], + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ] + }, + { + "type" : "RadialFreeze", + "radius" : 3.5, + "cost" : { + "iron" : 275 + }, + "effects" : [ + { + "type" : "Freeze", + "speedMultiplier" : 0.5, + "duration" : 1 + } + ], + "description" : "Bigger radius to freeze", + "name" : "Bigger radius", + "view" : { + "animationDuration" : 2 + }, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ] + }, + { + "type" : "RadialFreeze", + "radius" : 4.25, + "cost" : { + "iron" : 1025 + }, + "effects" : [ + { + "speedMultiplier" : 0.5, + "duration" : 2, + "type" : "Freeze" + }, + { + "type" : "FrozenVulnerable", + "duration" : 1, + "damageMultiplier" : 1.125 + } + ], + "description" : "Enemies stay frozen after they leave and 'thaw out' over a period of time", + "name" : "Snow-blower", + "view" : { + "animationDuration" : 2 + }, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ] + }, + { + "cost" : { + "iron" : 3275 + }, + "radius" : 4.5, + "type" : "RadialFreeze", + "effects" : [ + { + "speedMultiplier" : 0.5, + "type" : "Freeze", + "duration" : 4 + }, + { + "speedMultiplier" : 0.69999999999999996, + "duration" : 1, + "type" : "ResistantFreeze" + }, + { + "type" : "FrozenVulnerable", + "damageMultiplier" : 1.2375 + } + ], + "description" : "Enemies move slower, and affect resistant enemies", + "name" : "Chill reactor", + "view" : { + "animationDuration" : 2 + }, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ] + } + ], + [ + { + "radius" : 2.5, + "type" : "RadialFreeze", + "name" : "Slower", + "view" : { + "animationDuration" : 2 + }, + "effects" : [ + { + "type" : "Freeze", + "speedMultiplier" : 0.40000000000000002 + }, + { + "speedMultiplier" : 0.94999999999999996, + "type" : "ResistantFreeze" + } + ], + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "description" : "Slows enemies more", + "cost" : { + "iron" : 300 + } + } + ], + [ + { + "cost" : { + "iron" : 600 + }, + "type" : "RadialFreeze", + "description" : "Enemies are less resistant to other attacks, and take slight damage while in radius", + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "radius" : 2.5, + "view" : { + "animationDuration" : 2 + }, + "name" : "Wind chill", + "effects" : [ + { + "speedMultiplier" : 0.44, + "type" : "Freeze" + }, + { + "speedMultiplier" : 0.85499999999999998, + "type" : "ResistantFreeze" + }, + { + "type" : "Frostbite", + "dps" : 40 + }, + { + "damageMultiplier" : 1.25, + "type" : "FrozenVulnerable" + } + ] + } + ], + [ + { + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "cost" : { + "iron" : 1500 + }, + "radius" : 2.5, + "view" : { + "animationDuration" : 2 + }, + "name" : "Ultra cold", + "description" : "Enemies take a lot of damage while in radius", + "effects" : [ + { + "type" : "Freeze", + "speedMultiplier" : 0.48399999999999999 + }, + { + "speedMultiplier" : 0.76949999999999996, + "type" : "ResistantFreeze" + }, + { + "dps" : 320, + "type" : "Frostbite" + }, + { + "type" : "FrozenVulnerable", + "damageMultiplier" : 1.75 + } + ], + "type" : "RadialFreeze" + } + ], + [ + { + "name" : "Insane cold", + "cost" : { + "uranium" : 1000, + "iron" : 6000 + }, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "effects" : [ + { + "speedMultiplier" : 0.58079999999999998, + "type" : "Freeze" + }, + { + "speedMultiplier" : 0.61560000000000004, + "type" : "ResistantFreeze" + }, + { + "dps" : 640, + "type" : "Frostbite" + }, + { + "type" : "FrozenVulnerable", + "damageMultiplier" : 2.4500000000000002 + } + ], + "radius" : 2, + "view" : { + "animationDuration" : 2 + }, + "description" : "Increased vulnerability, slightly increased damage, affects resistant enemies more", + "type" : "RadialFreeze" + } + ] + ], + [ + [ + { + "view" : { + "animationDuration" : 2 + }, + "fireRate" : 2, + "projectile" : { + "view" : { + "textureName" : "FreezeBall" + }, + "effects" : [ + { + "type" : "Freeze", + "speedMultiplier" : 0.5 + } + ], + "dps" : 0, + "size" : 2.5, + "type" : "MovingArea", + "speed" : 3, + "maxTravelDistance" : 5 + }, + "name" : "Ice shooter", + "turnSpeed" : "360°", + "radius" : 4, + "type" : "ProjectileTurret", + "cost" : { + "iron" : 125, + "aluminum" : 250 + }, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "description" : "Shoots projectiles that freeze nearby enemies" + } + ] + ], + [ + [ + { + "turnSpeed" : "360°", + "projectile" : { + "dps" : 0, + "effects" : [ + { + "speedMultiplier" : 0.40000000000000002, + "type" : "Freeze" + } + ], + "size" : 2.75, + "maxTravelDistance" : 6, + "type" : "MovingArea", + "speed" : 2.3999999999999999, + "view" : { + "textureName" : "FreezeBall" + } + }, + "radius" : 4, + "type" : "ProjectileTurret", + "name" : "Cold shooter", + "fireRate" : 2, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "view" : { + "animationDuration" : 2 + }, + "description" : "Projectiles slow more and travel further", + "cost" : { + "iron" : 125, + "aluminum" : 750 + } + } + ] + ], + [ + [ + { + "cost" : { + "iron" : 125, + "aluminum" : 1500 + }, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "description" : "Projectiles cause affected enemies to be more vulnerable", + "radius" : 4, + "view" : { + "animationDuration" : 2 + }, + "turnSpeed" : "360°", + "type" : "ProjectileTurret", + "projectile" : { + "maxTravelDistance" : 7, + "effects" : [ + { + "speedMultiplier" : 0.5, + "type" : "Freeze" + }, + { + "speedMultiplier" : 0.80000000000000004, + "type" : "ResistantFreeze" + }, + { + "damageMultiplier" : 1.5, + "type" : "FrozenVulnerable" + } + ], + "size" : 3, + "speed" : 1.9199999999999999, + "view" : { + "textureName" : "FreezeBall" + }, + "dps" : 0, + "type" : "MovingArea" + }, + "fireRate" : 2, + "name" : "Chill shooter" + } + ] + ], + [ + [ + { + "view" : { + "animationDuration" : 2 + }, + "cost" : { + "iron" : 125, + "uranium" : 500, + "aluminum" : 4000 + }, + "fireRate" : 2, + "name" : "Freeze shooter", + "turnSpeed" : "360°", + "type" : "ProjectileTurret", + "radius" : 4, + "description" : "Projectiles inflict minor damage on enemies", + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "projectile" : { + "dps" : 0, + "effects" : [ + { + "duration" : 0.5, + "type" : "Freeze", + "speedMultiplier" : 0.5 + }, + { + "type" : "ResistantFreeze", + "duration" : 0.5, + "speedMultiplier" : 0.71999999999999997 + }, + { + "damageMultiplier" : 1.8, + "duration" : 0.5, + "type" : "FrozenVulnerable" + }, + { + "duration" : 0.5, + "dps" : 175, + "type" : "Frostbite" + } + ], + "speed" : 1.536, + "type" : "MovingArea", + "view" : { + "textureName" : "FreezeBall" + }, + "size" : 3, + "maxTravelDistance" : 8 + } + } + ] + ] + ] +} \ No newline at end of file diff --git a/Examples/buildings.json b/Examples/buildings.json new file mode 100644 index 0000000..173a7f2 --- /dev/null +++ b/Examples/buildings.json @@ -0,0 +1,2750 @@ +{ + "turretBomb" : [ + [ + [ + { + "view" : { + + }, + "type" : "ProjectileTurret", + "description" : "Shoots a bomb which explodes on impact, damaging nearby enemies", + "cost" : { + "copper" : 75, + "iron" : 25 + }, + "name" : "Bomb launcher", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "fireRate" : 1.5, + "turnSpeed" : "720°", + "radius" : 6, + "projectile" : { + "damage" : 70, + "blastRadius" : 1, + "maxTravelDistance" : 6.5, + "type" : "Bomb", + "speed" : 7, + "view" : { + "textureName" : "Bomb" + }, + "explosionView" : "Basic", + "size" : 0.5 + } + }, + { + "fireRate" : 1.5, + "cost" : { + "iron" : 125, + "copper" : 225 + }, + "projectile" : { + "damage" : 140, + "blastRadius" : 1, + "size" : 0.5, + "maxTravelDistance" : 6.5, + "explosionView" : "Basic", + "type" : "Bomb", + "view" : { + "textureName" : "BombHeavy" + }, + "speed" : 7 + }, + "name" : "Heavy bombs", + "radius" : 6, + "description" : "Increased damage", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "view" : { + + }, + "turnSpeed" : "720°", + "type" : "ProjectileTurret" + }, + { + "turnSpeed" : "720°", + "projectile" : { + "view" : { + "textureName" : "BombPropane" + }, + "blastRadius" : 1.2, + "damage" : 280, + "effects" : [ + { + "dps" : 20, + "type" : "Burn", + "duration" : 5 + } + ], + "explosionView" : "Basic", + "speed" : 7, + "size" : 0.59999999999999998, + "maxTravelDistance" : 6.5, + "type" : "Bomb" + }, + "fireRate" : 1.8, + "radius" : 6, + "view" : { + + }, + "type" : "ProjectileTurret", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "name" : "Propane bombs", + "description" : "Increased damage, slight burn", + "cost" : { + "coal" : 150, + "iron" : 375, + "copper" : 475 + } + }, + { + "view" : { + + }, + "radius" : 6, + "fireRate" : 1.98, + "description" : "Slightly increased damage, major burn", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "cost" : { + "iron" : 375, + "coal" : 400, + "copper" : 975 + }, + "name" : "Napalm bombs", + "projectile" : { + "size" : 0.71999999999999997, + "type" : "Bomb", + "effects" : [ + { + "type" : "Burn", + "duration" : 5, + "dps" : 80 + } + ], + "blastRadius" : 1.4399999999999999, + "maxTravelDistance" : 6.5, + "explosionView" : "Basic", + "view" : { + "textureName" : "BombNapalm" + }, + "speed" : 7, + "damage" : 336 + }, + "type" : "ProjectileTurret", + "turnSpeed" : "720°" + }, + { + "projectile" : { + "type" : "Bomb", + "view" : { + "textureName" : "BombNuke" + }, + "speed" : 7, + "effects" : [ + { + "dps" : 320, + "type" : "ChemBurn", + "duration" : 7.5 + } + ], + "maxTravelDistance" : 6.5, + "size" : 0.86399999999999999, + "explosionView" : "Nuke", + "damage" : 1008, + "blastRadius" : 2.8799999999999999 + }, + "cost" : { + "copper" : 1075, + "coal" : 2900, + "uranium" : 2500 + }, + "fireRate" : 2.9700000000000002, + "type" : "ProjectileTurret", + "radius" : 6, + "name" : "Atomic bombs", + "turnSpeed" : "720°", + "description" : "Increased damage and blast radius, replaces effect with chemical burn", + "view" : { + + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ] + } + ], + [ + { + "view" : { + + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "description" : "Increased range, and bombs travel above enemies to target", + "projectile" : { + "damage" : 105, + "speed" : 7, + "type" : "AirBomb", + "blastRadius" : 1, + "explosionView" : "Basic", + "view" : { + "textureName" : "Bomb", + "arcs" : true + } + }, + "needsToRotateToTarget" : false, + "radius" : 7.5, + "turnSpeed" : "720°", + "type" : "ProjectileTurret", + "name" : "Upward launcher", + "cost" : { + "aluminum" : 25 + }, + "fireRate" : 1.5 + } + ], + [ + { + "projectile" : { + "speed" : 9, + "type" : "Missile", + "explosionView" : "Basic", + "view" : { + "arcs" : true, + "textureName" : "Missile" + }, + "blastRadius" : 1, + "damage" : 126 + }, + "fireRate" : 1.2, + "cost" : { + "aluminum" : 100 + }, + "type" : "ProjectileTurret", + "radius" : 10, + "needsToRotateToTarget" : false, + "name" : "Missile launcher", + "turnSpeed" : "720°", + "description" : "Massive range, shoots missiles which hone onto targets", + "view" : { + + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ] + } + ], + [ + { + "name" : "Triple launcher", + "description" : "Shoots 3 missiles at once. Special targeting mode means that each missile will target different enemies", + "radius" : 10, + "projectile" : { + "type" : "Missile", + "blastRadius" : 1, + "explosionView" : "Basic", + "view" : { + "arcs" : true, + "textureName" : "Missile" + }, + "speed" : 9, + "damage" : 126 + }, + "numProjectiles" : 3, + "turnSpeed" : "720°", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "fireRate" : 1.2, + "view" : { + + }, + "type" : "ProjectileTurret", + "cost" : { + "aluminum" : 350 + }, + "needsToRotateToTarget" : false + } + ], + [ + { + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "cost" : { + "aluminum" : 1350 + }, + "fireRate" : 0.17142857142857101, + "projectile" : { + "type" : "Missile", + "explosionView" : "Basic", + "blastRadius" : 1, + "damage" : 126, + "view" : { + "textureName" : "Missile", + "arcs" : true + }, + "speed" : 9 + }, + "view" : { + + }, + "name" : "Silo", + "needsToRotateToTarget" : false, + "type" : "ProjectileTurret", + "turnSpeed" : "720°", + "radius" : 12, + "description" : "Increased range, shoots individual bombs but 7x as fast" + } + ], + [ + { + "projectile" : { + "blastRadius" : 1.3999999999999999, + "type" : "Missile", + "speed" : 14, + "explosionView" : "Basic", + "damage" : 378, + "view" : { + "arcs" : true, + "textureName" : "MissileSuper" + } + }, + "radius" : 14, + "view" : { + + }, + "fireRate" : 0.12, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "name" : "Super Silo", + "cost" : { + "aluminum" : 3850 + }, + "description" : "Shoots better missiles, increased range, slightly faster than before", + "needsToRotateToTarget" : false, + "type" : "ProjectileTurret", + "turnSpeed" : "720°" + } + ] + ], + [ + [ + { + "turnSpeed" : "720°", + "name" : "Big bombs", + "fireRate" : 1.5, + "view" : { + + }, + "projectile" : { + "blastRadius" : 1.3999999999999999, + "size" : 0.75, + "explosionView" : "Basic", + "damage" : 84, + "view" : { + "textureName" : "BombBig" + }, + "speed" : 7, + "type" : "Bomb", + "maxTravelDistance" : 6.5 + }, + "radius" : 6, + "description" : "Increased blast radius", + "cost" : { + "coal" : 75 + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "type" : "ProjectileTurret" + } + ] + ], + [ + [ + { + "projectile" : { + "maxTravelDistance" : 5, + "size" : 1.125, + "explosionView" : "Basic", + "blastRadius" : 1.6799999999999999, + "damage" : 117.59999999999999, + "speed" : 5.5, + "type" : "Bomb", + "view" : { + "textureName" : "BombGiant" + }, + "effects" : [ + { + "type" : "Stun", + "duration" : 0.25 + }, + { + "type" : "Knockback", + "duration" : 0.25, + "distancePerSecond" : 0.5 + } + ] + }, + "turnSpeed" : "720°", + "radius" : 4.5, + "cost" : { + "coal" : 275 + }, + "fireRate" : 1.5, + "type" : "ProjectileTurret", + "view" : { + + }, + "name" : "Giant bombs", + "description" : "Increased blast radius, slight knockback", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ] + } + ] + ], + [ + [ + { + "view" : { + + }, + "description" : "Stuns enemies, and more knockback and damage", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "turnSpeed" : "720°", + "type" : "ProjectileTurret", + "radius" : 4.5, + "name" : "Stun bomb", + "projectile" : { + "view" : { + "textureName" : "BombStun" + }, + "size" : 1.125, + "damage" : 164.63999999999999, + "maxTravelDistance" : 5, + "type" : "Bomb", + "blastRadius" : 1.8480000000000001, + "explosionView" : "Basic", + "effects" : [ + { + "type" : "Stun", + "duration" : 1 + }, + { + "distancePerSecond" : 0.5, + "type" : "Knockback", + "duration" : 1 + } + ], + "speed" : 4.5 + }, + "cost" : { + "coal" : 775 + }, + "fireRate" : 1.5 + } + ] + ], + [ + [ + { + "description" : "Bombs create a black hole which stuns even resistant enemies and draws them to the center", + "projectile" : { + "damage" : 0, + "effects" : [ + { + "pullPerSecond" : 0.25, + "dps" : 300, + "type" : "BlackHoleStun", + "duration" : 4 + } + ], + "type" : "AirBomb", + "explosionView" : "BlackHole", + "blastRadius" : 4, + "view" : { + "arcs" : "False", + "textureName" : "BombBlackHole" + }, + "speed" : 3 + }, + "fireRate" : 4, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "type" : "ProjectileTurret", + "turnSpeed" : "720°", + "cost" : { + "coal" : 10775 + }, + "radius" : 4.5, + "view" : { + + }, + "name" : "Black hole bomb" + } + ] + ] + ], + "freeze" : [ + [ + [ + { + "type" : "RadialFreeze", + "name" : "Ice tower", + "effects" : [ + { + "type" : "Freeze", + "speedMultiplier" : 0.5 + } + ], + "view" : { + "animationDuration" : 2 + }, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "cost" : { + "iron" : 125 + }, + "radius" : 2.5, + "description" : "Slows enemies in range. Remember that burn and freeze cancel each other out" + }, + { + "type" : "RadialFreeze", + "name" : "Bigger radius", + "effects" : [ + { + "type" : "Freeze", + "speedMultiplier" : 0.5, + "duration" : 1 + } + ], + "view" : { + "animationDuration" : 2 + }, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "cost" : { + "iron" : 275 + }, + "radius" : 3.5, + "description" : "Bigger radius to freeze" + }, + { + "description" : "Enemies stay frozen after they leave and 'thaw out' over a period of time", + "name" : "Snow-blower", + "view" : { + "animationDuration" : 2 + }, + "radius" : 4.25, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "type" : "RadialFreeze", + "effects" : [ + { + "speedMultiplier" : 0.5, + "type" : "Freeze", + "duration" : 2 + }, + { + "damageMultiplier" : 1.125, + "type" : "FrozenVulnerable", + "duration" : 1 + } + ], + "cost" : { + "iron" : 1025 + } + }, + { + "radius" : 4.5, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "description" : "Enemies move slower, and affect resistant enemies", + "cost" : { + "iron" : 3275 + }, + "view" : { + "animationDuration" : 2 + }, + "name" : "Chill reactor", + "effects" : [ + { + "speedMultiplier" : 0.5, + "duration" : 4, + "type" : "Freeze" + }, + { + "type" : "ResistantFreeze", + "duration" : 1, + "speedMultiplier" : 0.69999999999999996 + }, + { + "damageMultiplier" : 1.2375, + "type" : "FrozenVulnerable" + } + ], + "type" : "RadialFreeze" + } + ], + [ + { + "type" : "RadialFreeze", + "name" : "Slower", + "description" : "Slows enemies more", + "radius" : 2.5, + "cost" : { + "iron" : 300 + }, + "effects" : [ + { + "type" : "Freeze", + "speedMultiplier" : 0.40000000000000002 + }, + { + "speedMultiplier" : 0.94999999999999996, + "type" : "ResistantFreeze" + } + ], + "view" : { + "animationDuration" : 2 + }, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ] + } + ], + [ + { + "name" : "Wind chill", + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "effects" : [ + { + "type" : "Freeze", + "speedMultiplier" : 0.44 + }, + { + "type" : "ResistantFreeze", + "speedMultiplier" : 0.85499999999999998 + }, + { + "type" : "Frostbite", + "dps" : 40 + }, + { + "type" : "FrozenVulnerable", + "damageMultiplier" : 1.25 + } + ], + "view" : { + "animationDuration" : 2 + }, + "radius" : 2.5, + "cost" : { + "iron" : 600 + }, + "description" : "Enemies are less resistant to other attacks, and take slight damage while in radius", + "type" : "RadialFreeze" + } + ], + [ + { + "effects" : [ + { + "speedMultiplier" : 0.48399999999999999, + "type" : "Freeze" + }, + { + "type" : "ResistantFreeze", + "speedMultiplier" : 0.76949999999999996 + }, + { + "type" : "Frostbite", + "dps" : 320 + }, + { + "damageMultiplier" : 1.75, + "type" : "FrozenVulnerable" + } + ], + "cost" : { + "iron" : 1500 + }, + "radius" : 2.5, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "view" : { + "animationDuration" : 2 + }, + "name" : "Ultra cold", + "type" : "RadialFreeze", + "description" : "Enemies take a lot of damage while in radius" + } + ], + [ + { + "type" : "RadialFreeze", + "name" : "Insane cold", + "effects" : [ + { + "type" : "Freeze", + "speedMultiplier" : 0.58079999999999998 + }, + { + "speedMultiplier" : 0.61560000000000004, + "type" : "ResistantFreeze" + }, + { + "type" : "Frostbite", + "dps" : 640 + }, + { + "damageMultiplier" : 2.4500000000000002, + "type" : "FrozenVulnerable" + } + ], + "radius" : 2, + "cost" : { + "iron" : 6000, + "uranium" : 1000 + }, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "view" : { + "animationDuration" : 2 + }, + "description" : "Increased vulnerability, slightly increased damage, affects resistant enemies more" + } + ] + ], + [ + [ + { + "name" : "Ice shooter", + "type" : "ProjectileTurret", + "cost" : { + "aluminum" : 250, + "iron" : 125 + }, + "projectile" : { + "effects" : [ + { + "type" : "Freeze", + "speedMultiplier" : 0.5 + } + ], + "speed" : 3, + "type" : "MovingArea", + "view" : { + "textureName" : "FreezeBall" + }, + "dps" : 0, + "size" : 2.5, + "maxTravelDistance" : 5 + }, + "view" : { + "animationDuration" : 2 + }, + "radius" : 4, + "description" : "Shoots projectiles that freeze nearby enemies", + "fireRate" : 2, + "turnSpeed" : "360°", + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ] + } + ] + ], + [ + [ + { + "cost" : { + "aluminum" : 750, + "iron" : 125 + }, + "fireRate" : 2, + "radius" : 4, + "turnSpeed" : "360°", + "view" : { + "animationDuration" : 2 + }, + "projectile" : { + "speed" : 2.3999999999999999, + "dps" : 0, + "type" : "MovingArea", + "view" : { + "textureName" : "FreezeBall" + }, + "maxTravelDistance" : 6, + "effects" : [ + { + "type" : "Freeze", + "speedMultiplier" : 0.40000000000000002 + } + ], + "size" : 2.75 + }, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "type" : "ProjectileTurret", + "name" : "Cold shooter", + "description" : "Projectiles slow more and travel further" + } + ] + ], + [ + [ + { + "name" : "Chill shooter", + "description" : "Projectiles cause affected enemies to be more vulnerable", + "view" : { + "animationDuration" : 2 + }, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ], + "turnSpeed" : "360°", + "projectile" : { + "maxTravelDistance" : 7, + "view" : { + "textureName" : "FreezeBall" + }, + "size" : 3, + "speed" : 1.9199999999999999, + "type" : "MovingArea", + "dps" : 0, + "effects" : [ + { + "type" : "Freeze", + "speedMultiplier" : 0.5 + }, + { + "type" : "ResistantFreeze", + "speedMultiplier" : 0.80000000000000004 + }, + { + "type" : "FrozenVulnerable", + "damageMultiplier" : 1.5 + } + ] + }, + "fireRate" : 2, + "type" : "ProjectileTurret", + "radius" : 4, + "cost" : { + "iron" : 125, + "aluminum" : 1500 + } + } + ] + ], + [ + [ + { + "projectile" : { + "speed" : 1.536, + "view" : { + "textureName" : "FreezeBall" + }, + "effects" : [ + { + "duration" : 0.5, + "type" : "Freeze", + "speedMultiplier" : 0.5 + }, + { + "duration" : 0.5, + "type" : "ResistantFreeze", + "speedMultiplier" : 0.71999999999999997 + }, + { + "duration" : 0.5, + "damageMultiplier" : 1.8, + "type" : "FrozenVulnerable" + }, + { + "duration" : 0.5, + "type" : "Frostbite", + "dps" : 175 + } + ], + "maxTravelDistance" : 8, + "type" : "MovingArea", + "size" : 3, + "dps" : 0 + }, + "name" : "Freeze shooter", + "description" : "Projectiles inflict minor damage on enemies", + "turnSpeed" : "360°", + "cost" : { + "uranium" : 500, + "aluminum" : 4000, + "iron" : 125 + }, + "view" : { + "animationDuration" : 2 + }, + "fireRate" : 2, + "type" : "ProjectileTurret", + "radius" : 4, + "terrains" : [ + "Grass", + "Rocks", + "Snow" + ] + } + ] + ] + ], + "mine" : [ + [ + [ + { + "view" : { + + }, + "description" : "Mines nearby ores, increasing your resources", + "degradeMultiplier" : 0.75, + "name" : "Mine", + "radius" : 2, + "ironOresPerYield" : 1, + "cost" : { + "iron" : 250, + "gold" : 100, + "coal" : 250, + "uranium" : 1000, + "aluminum" : 500 + }, + "type" : "BasicMine", + "oresPerYield" : 10, + "isDegradeLocal" : true, + "ticksPerYield" : 800 + }, + { + "ironOresPerYield" : 5, + "type" : "BasicMine", + "description" : "Only mines its own tile, but faster and more consistently", + "degradeMultiplier" : 0.75, + "ticksPerYield" : 16, + "cost" : { + "iron" : 250, + "uranium" : 2500, + "aluminum" : 500, + "gold" : 150, + "coal" : 250 + }, + "view" : { + + }, + "name" : "Laser mine", + "radius" : 1, + "isDegradeLocal" : false, + "oresPerYield" : 1 + }, + { + "radius" : 1, + "ticksPerYield" : 16, + "type" : "IronMine", + "oresPerYield" : 4, + "description" : "Mines faster, and additionally mines iron", + "view" : { + + }, + "name" : "Deep laser mine", + "cost" : { + "iron" : 2750, + "gold" : 400, + "aluminum" : 8000, + "uranium" : 2500 + } + }, + { + "description" : "Mines insane amounts of iron. Breaks towers", + "cost" : { + "gold" : 1150 + }, + "ticksPerYield" : 1, + "oresPerYield" : 2, + "name" : "Core laser mine", + "radius" : 1, + "view" : { + + }, + "type" : "IronMine" + } + ], + [ + { + "description" : "Mines larger yields but slower", + "cost" : { + "gold" : 200 + }, + "ticksPerYield" : 1600, + "oresPerYield" : 50, + "name" : "Bomb mine", + "radius" : 2, + "view" : { + + }, + "type" : "BasicMine" + } + ], + [ + { + "name" : "Bigger bomb mine", + "ticksPerYield" : 3200, + "view" : { + + }, + "cost" : { + "gold" : 400 + }, + "description" : "Mines larger yields and radius but slower", + "radius" : 3, + "oresPerYield" : 200, + "type" : "BasicMine" + } + ], + [ + { + "name" : "Atomic bomb mine", + "ticksPerYield" : 6400, + "view" : { + + }, + "cost" : { + "gold" : 1250 + }, + "description" : "Massive yields", + "radius" : 3, + "oresPerYield" : 2000, + "type" : "BasicMine" + } + ] + ], + [ + [ + { + "name" : "Larger radius", + "ticksPerYield" : 1200, + "view" : { + + }, + "cost" : { + "gold" : 200 + }, + "description" : "Mines a bigger radius, but less ore", + "radius" : 3, + "oresPerYield" : 7, + "type" : "BasicMine" + } + ] + ], + [ + [ + { + "cost" : { + "gold" : 350 + }, + "description" : "Mines an even larger radius, but degrades buildings in the mining zone", + "radius" : 5, + "oresPerYield" : 21, + "type" : "FrackingMine", + "name" : "Fracking mine", + "view" : { + + }, + "ticksPerYield" : 1800 + } + ] + ], + [ + [ + { + "name" : "Super fracking mine", + "ticksPerYield" : 2700, + "view" : { + + }, + "cost" : { + "gold" : 800 + }, + "description" : "Mines a huge radius, but degrades every tower", + "radius" : 10, + "oresPerYield" : 189, + "type" : "FrackingMine" + } + ] + ] + ], + "turretFlamethrower" : [ + [ + [ + { + "radius" : 2, + "view" : { + + }, + "cost" : { + "coal" : 25, + "gold" : 150, + "copper" : 250, + "iron" : 50 + }, + "fireRate" : 0.0625, + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ], + "turnSpeed" : "360°", + "projectile" : { + "view" : { + "textureName" : "Fire" + }, + "pierce" : 3, + "maxTravelDistance" : 2, + "speed" : 8, + "size" : 0.25, + "damage" : 10, + "effects" : [ + { + "dps" : 15, + "type" : "Burn", + "duration" : 5 + } + ], + "type" : "Basic" + }, + "name" : "Flamethrower", + "description" : "Close range tower with good pierce and a burn effect which persists after enemies leave the tower's radius", + "type" : "ProjectileTurret" + }, + { + "radius" : 2, + "view" : { + + }, + "cost" : { + "coal" : 125, + "copper" : 650, + "iron" : 75, + "gold" : 600 + }, + "fireRate" : 0.0625, + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ], + "turnSpeed" : "360°", + "projectile" : { + "speed" : 8, + "size" : 0.25, + "type" : "Basic", + "damage" : 20, + "pierce" : 4, + "view" : { + "textureName" : "FireVolatile" + }, + "effects" : [ + { + "type" : "Burn", + "duration" : 7.5, + "dps" : 60 + } + ], + "maxTravelDistance" : 2 + }, + "name" : "More volatile fuel", + "description" : "More direct damage, much more burn damage, and longer lasting burn", + "type" : "ProjectileTurret" + }, + { + "radius" : 2, + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ], + "projectile" : { + "pierce" : 5, + "damage" : 40, + "type" : "Basic", + "maxTravelDistance" : 2, + "effects" : [ + { + "type" : "Burn", + "duration" : 11.25, + "dps" : 120 + } + ], + "size" : 0.25, + "speed" : 8, + "view" : { + "textureName" : "FireYellow" + } + }, + "cost" : { + "coal" : 525, + "iron" : 150, + "copper" : 2250 + }, + "name" : "Yellow fire", + "fireRate" : 0.0625, + "description" : "More pierce, damage, burn damage, and burn lasts much longer", + "turnSpeed" : "360°", + "type" : "ProjectileTurret", + "view" : { + + } + }, + { + "radius" : 2.5, + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ], + "projectile" : { + "pierce" : 7, + "damage" : 80, + "maxTravelDistance" : 2.5, + "view" : { + "textureName" : "FireGreen" + }, + "speed" : 8, + "type" : "Basic", + "effects" : [ + { + "duration" : 16.875, + "type" : "ChemBurn", + "dps" : 240 + } + ], + "size" : 0.25 + }, + "cost" : { + "coal" : 1325, + "copper" : 8650, + "iron" : 375 + }, + "name" : "Green fire", + "fireRate" : 0.0625, + "description" : "Chemical burn (can be combined with freeze), more damage", + "turnSpeed" : "432°", + "type" : "ProjectileTurret", + "view" : { + + } + }, + { + "radius" : 2.5, + "type" : "ProjectileTurret", + "view" : { + + }, + "projectile" : { + "maxTravelDistance" : 2.5, + "size" : 0.25, + "speed" : 8, + "pierce" : 9, + "view" : { + "textureName" : "FireBlack" + }, + "damage" : 160, + "effects" : [ + { + "fractionDpsChangeDuration" : 3, + "type" : "Doom", + "initialFractionDps" : 0.25, + "finalFractionDps" : 0.040000000000000001, + "vulnerabilityDamageMultiplier" : 1.75, + "speedMultiplier" : 0.75 + } + ], + "type" : "Basic" + }, + "fireRate" : 0.0625, + "description" : "Replaces with the doom effect: major percent damage over time, slower speed, and multiplied damage from all attacks, permanently", + "cost" : { + "coal" : 2925, + "iron" : 825, + "uranium" : 1000 + }, + "name" : "Black fire", + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ], + "turnSpeed" : "518°" + } + ], + [ + { + "projectile" : { + "size" : 0.25, + "maxTravelDistance" : 2.25, + "type" : "Basic", + "damage" : 30, + "pierce" : 7, + "view" : { + "textureName" : "Fire" + }, + "effects" : [ + { + "type" : "Burn", + "duration" : 5, + "dps" : 22.5 + } + ], + "speed" : 8 + }, + "type" : "ProjectileTurret", + "description" : "Increased pierce, damage, and turn speed", + "radius" : 2.25, + "cost" : { + "coal" : 150, + "iron" : 1725 + }, + "turnSpeed" : "540°", + "view" : { + + }, + "name" : "Faster fuel", + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ], + "fireRate" : 0.0625 + } + ], + [ + { + "type" : "ProjectileTurret", + "cost" : { + "coal" : 400 + }, + "view" : { + + }, + "radius" : 2.5, + "projectile" : { + "type" : "Basic", + "damage" : 60, + "effects" : [ + { + "dps" : 45, + "duration" : 7, + "type" : "Burn" + } + ], + "view" : { + "textureName" : "FireBig" + }, + "maxTravelDistance" : 2.5, + "pierce" : 9, + "speed" : 8, + "size" : 0.375 + }, + "turnSpeed" : "648°", + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ], + "fireRate" : 0.0625, + "description" : "Increased pierce, damage, turn speed, and projectile size", + "name" : "Larger flames" + } + ], + [ + { + "type" : "ProjectileTurret", + "cost" : { + "coal" : 900 + }, + "view" : { + + }, + "radius" : 2.5, + "projectile" : { + "pierce" : 14, + "maxTravelDistance" : 2.5, + "size" : 0.45000000000000001, + "speed" : 8, + "type" : "Basic", + "effects" : [ + { + "duration" : 7, + "type" : "Burn", + "dps" : 45 + } + ], + "view" : { + "textureName" : "FireBig" + }, + "damage" : 90 + }, + "turnSpeed" : "0°", + "description" : "Shoots flames around the entire tower (TODO)", + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ], + "fireRate" : 0.0625, + "name" : "Circle of fire" + } + ], + [ + { + "cost" : { + "coal" : 2400 + }, + "projectile" : { + "type" : "Basic", + "damage" : 180, + "speed" : 8, + "size" : 0.45000000000000001, + "effects" : [ + { + "dps" : 67.5, + "duration" : 8.4000000000000004, + "type" : "Burn" + } + ], + "view" : { + "textureName" : "FireBig" + }, + "maxTravelDistance" : 3, + "pierce" : 20 + }, + "fireRate" : 0.0625, + "view" : { + + }, + "radius" : 3, + "turnSpeed" : "0°", + "name" : "Golden circle", + "type" : "ProjectileTurret", + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ], + "description" : "Increased range and damage (TODO)" + } + ], + [ + { + "turnSpeed" : "0°", + "cost" : { + "coal" : 5400, + "uranium" : 1000 + }, + "projectile" : { + "size" : 0.45000000000000001, + "damage" : 540, + "maxTravelDistance" : 3, + "pierce" : 40, + "type" : "Basic", + "speed" : 8, + "effects" : [ + { + "type" : "Burn", + "duration" : 8.4000000000000004, + "dps" : 135 + }, + { + "speedMultiplier" : 0.5, + "duration" : 0.25, + "type" : "Slow" + }, + { + "damageMultiplier" : 1.5, + "duration" : 0.25, + "type" : "Vulnerable" + } + ], + "view" : { + "textureName" : "FireBig" + } + }, + "type" : "ProjectileTurret", + "view" : { + + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks" + ], + "fireRate" : 0.0625, + "radius" : 3, + "name" : "Inferno", + "description" : "Massive damage, and makes enemies in radius slower and more vulnerable" + } + ] + ] + ], + "turretBasic" : [ + [ + [ + { + "description" : "All-around turret with medium damage, fire rate, and range. No pierce and projectiles travel fast", + "turnSpeed" : "720°", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "name" : "Standard", + "radius" : 4, + "cost" : { + "gold" : 100, + "iron" : 50 + }, + "fireRate" : 0.5, + "view" : { + + }, + "projectile" : { + "speed" : 18, + "damage" : 50, + "effects" : [ + { + "fractionDps" : 0.025000000000000001, + "type" : "ChemBurn", + "dps" : 4032, + "duration" : 0.25 + } + ], + "maxTravelDistance" : 4.5, + "fractionDamage" : 0.14999999999999999, + "view" : { + "textureName" : "Bullet" + }, + "pierce" : 1, + "type" : "Basic", + "size" : 0.0625 + }, + "type" : "ProjectileTurret" + }, + { + "name" : "Bronze standard", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "turnSpeed" : "720°", + "description" : "Increased fire rate, range, and damage", + "radius" : 4.25, + "cost" : { + "gold" : 100, + "iron" : 150 + }, + "fireRate" : 0.40000000000000002, + "view" : { + + }, + "projectile" : { + "maxTravelDistance" : 4.75, + "pierce" : 1, + "view" : { + "textureName" : "Bullet" + }, + "type" : "Basic", + "speed" : 18, + "effects" : [ + { + "fractionDps" : 0.025000000000000001, + "duration" : 15, + "type" : "Hack" + } + ], + "damage" : 75, + "fractionDamage" : 0.14999999999999999, + "size" : 0.0625 + }, + "type" : "ProjectileTurret" + }, + { + "turnSpeed" : "720°", + "type" : "ProjectileTurret", + "cost" : { + "gold" : 100, + "iron" : 350 + }, + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "view" : { + + }, + "projectile" : { + "effects" : [ + { + "duration" : 15, + "fractionDps" : 0.025000000000000001, + "type" : "Hack" + } + ], + "damage" : 112, + "type" : "Basic", + "speed" : 18, + "pierce" : 2, + "fractionDamage" : 0.14999999999999999, + "view" : { + "textureName" : "Bullet" + }, + "maxTravelDistance" : 5, + "size" : 0.0625 + }, + "radius" : 4.5, + "description" : "Increased damage, fire rate, range, and pierce", + "fireRate" : 0.32000000000000001, + "name" : "Silver standard" + }, + { + "turnSpeed" : "720°", + "radius" : 5, + "name" : "Gold standard", + "description" : "Increased range, pierce, damage, and fire rate", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "view" : { + + }, + "type" : "ProjectileTurret", + "fireRate" : 0.25600000000000001, + "cost" : { + "gold" : 300, + "iron" : 750 + }, + "projectile" : { + "speed" : 20, + "maxTravelDistance" : 5.5, + "size" : 0.0625, + "damage" : 168, + "effects" : [ + { + "type" : "Hack", + "duration" : 18 + } + ], + "type" : "Basic", + "view" : { + "textureName" : "BulletGold" + }, + "pierce" : 4, + "fractionDamage" : 0.12 + } + }, + { + "turnSpeed" : "720°", + "fireRate" : 0.20480000000000001, + "radius" : 6, + "name" : "Platinum standard", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "type" : "ProjectileTurret", + "view" : { + + }, + "cost" : { + "gold" : 300, + "iron" : 1550 + }, + "description" : "Increased pierce, damage, fire rate, and range", + "projectile" : { + "size" : 0.0625, + "type" : "Basic", + "view" : { + "textureName" : "BulletPlatinum" + }, + "pierce" : 8, + "damage" : 252, + "maxTravelDistance" : 6.5, + "speed" : 24 + } + }, + { + "name" : "The best one", + "description" : "This tower is better than you are", + "fireRate" : 0.01, + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "radius" : 100, + "cost" : { + "iron" : 101550 + }, + "projectile" : { + "damage" : 1000, + "size" : 0.25, + "view" : { + "textureName" : "Best" + }, + "pierce" : 1e+100, + "speed" : 24, + "type" : "Basic", + "maxTravelDistance" : 100 + }, + "type" : "ProjectileTurret", + "turnSpeed" : "720°", + "view" : { + + } + } + ], + [ + { + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "name" : "Sniper", + "turnSpeed" : "720°", + "radius" : 9, + "type" : "SniperTurret", + "cost" : { + "iron" : 125 + }, + "description" : "Big range increase, and fires instant projectiles which are guaranteed to hit the intended target. However, fires much slower", + "view" : { + + }, + "projectile" : { + "damage" : 500 + }, + "fireRate" : 3 + }, + [ + + ], + { + "turnSpeed" : "720°", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "name" : "Silver standard lvl2", + "description" : "Increased damage, range, and projectile speed", + "radius" : 5.5, + "cost" : { + "gold" : 300, + "iron" : 550 + }, + "fireRate" : 0.32000000000000001, + "view" : { + + }, + "projectile" : { + "fractionDamage" : 0.12, + "speed" : 21.600000000000001, + "type" : "Basic", + "pierce" : 2, + "damage" : 224, + "maxTravelDistance" : 6, + "size" : 0.0625, + "view" : { + "textureName" : "Bullet" + }, + "effects" : [ + { + "type" : "Hack", + "fractionDps" : 0.029999999999999999, + "duration" : 15 + } + ] + }, + "type" : "ProjectileTurret" + }, + { + "projectile" : { + "size" : 0.0625, + "pierce" : 4, + "view" : { + "textureName" : "Bullet" + }, + "damage" : 336, + "type" : "Basic", + "fractionDamage" : 0.12, + "maxTravelDistance" : 6.5, + "speed" : 24 + }, + "radius" : 6, + "description" : "Increased damage, range, and projectile speed", + "turnSpeed" : "720°", + "fireRate" : 0.25600000000000001, + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "view" : { + + }, + "cost" : { + "gold" : 300, + "iron" : 1150 + }, + "name" : "Gold standard lvl2", + "type" : "ProjectileTurret" + }, + { + "description" : "Increased damage, range, and projectile speed", + "cost" : { + "iron" : 2350 + }, + "fireRate" : 0.20480000000000001, + "projectile" : { + "pierce" : 8, + "size" : 0.0625, + "damage" : 504, + "type" : "Basic", + "speed" : 28.800000000000001, + "view" : { + "textureName" : "Bullet" + }, + "maxTravelDistance" : 7.5 + }, + "name" : "Platinum standard lvl2", + "view" : { + + }, + "turnSpeed" : "720°", + "type" : "ProjectileTurret", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "radius" : 7 + } + ], + [ + { + "fireRate" : 3, + "projectile" : { + "damage" : 1000 + }, + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "turnSpeed" : "720°", + "name" : "Elite sniper", + "type" : "SniperTurret", + "view" : { + + }, + "radius" : 10, + "description" : "Increased range and damage", + "cost" : { + "iron" : 275 + } + }, + { + "radius" : 10, + "description" : "Increased fire rate", + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "cost" : { + "iron" : 575 + }, + "type" : "SniperTurret", + "name" : "Advanced sniper", + "projectile" : { + "damage" : 1000 + }, + "turnSpeed" : "720°", + "fireRate" : 2.3999999999999999, + "view" : { + + } + }, + [ + + ], + { + "turnSpeed" : "720°", + "fireRate" : 0.25600000000000001, + "radius" : 7, + "name" : "Gold standard lvl3", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "type" : "ProjectileTurret", + "view" : { + + }, + "cost" : { + "iron" : 2750, + "gold" : 300 + }, + "description" : "Increased damage, range, projectile speed, and pierce", + "projectile" : { + "fractionDamage" : 0.12, + "pierce" : 5, + "damage" : 672, + "maxTravelDistance" : 7.5, + "size" : 0.0625, + "speed" : 28.800000000000001, + "type" : "Basic", + "view" : { + "textureName" : "Bullet" + } + } + }, + { + "name" : "Platinum standard lvl3", + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "view" : { + + }, + "turnSpeed" : "720°", + "radius" : 8, + "projectile" : { + "maxTravelDistance" : 8.5, + "speed" : 34.560000000000002, + "damage" : 1008, + "view" : { + "textureName" : "Bullet" + }, + "size" : 0.0625, + "pierce" : 10, + "type" : "Basic" + }, + "description" : "Increased damage, range, projectile speed, and pierce", + "fireRate" : 0.20480000000000001, + "cost" : { + "iron" : 5550 + }, + "type" : "ProjectileTurret" + } + ], + [ + { + "view" : { + + }, + "type" : "SniperTurret", + "turnSpeed" : "720°", + "description" : "% damage", + "name" : "Marine sniper", + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "cost" : { + "iron" : 875 + }, + "projectile" : { + "damage" : 4000 + }, + "fireRate" : 4, + "radius" : 11 + }, + { + "radius" : 11, + "view" : { + + }, + "name" : "Commando sniper", + "type" : "SniperTurret", + "fireRate" : 3.2000000000000002, + "turnSpeed" : "720°", + "cost" : { + "iron" : 1475 + }, + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "projectile" : { + "damage" : 4000 + }, + "description" : "Increased fire rate" + }, + { + "projectile" : { + "damage" : 4000 + }, + "turnSpeed" : "720°", + "type" : "SniperTurret", + "cost" : { + "iron" : 2375 + }, + "view" : { + + }, + "radius" : 15.4, + "description" : "Increased range, slightly increased fire rate", + "name" : "Agent sniper", + "fireRate" : 2.8799999999999999, + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ] + }, + [ + + ], + { + "view" : { + + }, + "terrains" : [ + "Grass", + "sand", + "snow", + "rock" + ], + "projectile" : { + "view" : { + "textureName" : "Bullet" + }, + "pierce" : 15, + "maxTravelDistance" : 9.5, + "type" : "Basic", + "damage" : 2016, + "speed" : 41.472000000000001, + "size" : 0.0625 + }, + "radius" : 9, + "name" : "Platinum standard lvl4", + "cost" : { + "iron" : 8750 + }, + "type" : "ProjectileTurret", + "turnSpeed" : "720°", + "fireRate" : 0.20480000000000001, + "description" : "Increased damage, range, projectile speed, and pierce" + } + ], + [ + { + "name" : "Robot sniper", + "cost" : { + "iron" : 3275 + }, + "description" : "Hack effect deals % damage over time", + "projectile" : { + "damage" : 4800 + }, + "view" : { + + }, + "fireRate" : 5, + "turnSpeed" : "720°", + "type" : "SniperTurret", + "radius" : 12, + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ] + }, + { + "projectile" : { + "damage" : 4800 + }, + "view" : { + + }, + "type" : "SniperTurret", + "turnSpeed" : "720°", + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "fireRate" : 4, + "cost" : { + "iron" : 4175 + }, + "name" : "Sniper X", + "radius" : 12, + "description" : "Increased fire rate" + }, + { + "projectile" : { + "damage" : 4800 + }, + "turnSpeed" : "720°", + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "description" : "Increased range, slightly increased fire rate", + "name" : "Sniper Z", + "fireRate" : 3.6000000000000001, + "cost" : { + "iron" : 6875 + }, + "view" : { + + }, + "type" : "SniperTurret", + "radius" : 16.800000000000001 + }, + { + "projectile" : { + "damage" : 4800 + }, + "fireRate" : 3.6000000000000001, + "type" : "SniperTurret", + "terrains" : [ + "Rocks", + "grass", + "sand", + "snow" + ], + "turnSpeed" : "720°", + "radius" : 100, + "view" : { + + }, + "description" : "Unlimited range", + "name" : ".sniper", + "cost" : { + "iron" : 12275 + } + } + ] + ] + ], + "spikeRoller" : [ + [ + [ + { + "description" : "Shoots a spiked ball in a fixed straight line which goes through many enemies", + "type" : "SpikeRoller", + "projectile" : { + "type" : "Basic", + "damage" : 90, + "pierce" : 5, + "maxTravelDistance" : 10, + "size" : 1, + "view" : { + "animationDuration" : 0.125, + "textureName" : "SpikedBall" + }, + "speed" : 5 + }, + "name" : "Spike roller", + "cost" : { + "iron" : 100 + }, + "view" : { + + }, + "fireRate" : 2, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ] + }, + { + "description" : "Shoots a bigger spiked ball which does more damage", + "type" : "SpikeRoller", + "projectile" : { + "maxTravelDistance" : 12, + "view" : { + "animationDuration" : 0.125, + "textureName" : "SpikedBallBig" + }, + "damage" : 180, + "speed" : 4, + "size" : 1.25, + "pierce" : 7, + "type" : "Basic" + }, + "name" : "Big spiked ball", + "cost" : { + "iron" : 250 + }, + "view" : { + + }, + "fireRate" : 2.5, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ] + }, + { + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "view" : { + + }, + "cost" : { + "iron" : 550 + }, + "projectile" : { + "pierce" : 10, + "effects" : [ + { + "duration" : 0.5, + "type" : "Stun" + }, + { + "type" : "Knockback", + "duration" : 0.5, + "distancePerSecond" : 1 + } + ], + "damage" : 360, + "type" : "Basic", + "size" : 1.25, + "maxTravelDistance" : 14, + "speed" : 3.5, + "view" : { + "textureName" : "SpikedBallHeavy", + "animationDuration" : 0.125 + } + }, + "type" : "SpikeRoller", + "name" : "Heavy spiked ball", + "fireRate" : 2.75, + "description" : "Spiked ball does more damage, pierce, and slight knockback" + }, + { + "description" : "Slightly more pierce, much more knockback", + "name" : "Steam roller", + "view" : { + + }, + "projectile" : { + "damage" : 540, + "speed" : 3, + "type" : "Basic", + "effects" : [ + { + "duration" : 1.5, + "type" : "Stun" + }, + { + "type" : "Knockback", + "distancePerSecond" : 2, + "duration" : 1.5 + } + ], + "pierce" : 14, + "maxTravelDistance" : 16, + "size" : 2, + "view" : { + "textureName" : "SteamRoller", + "animationDuration" : 0.125 + } + }, + "cost" : { + "iron" : 1450 + }, + "fireRate" : 3, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "type" : "SpikeRoller" + }, + { + "name" : "Glue roller", + "cost" : { + "iron" : 4150 + }, + "view" : { + + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "projectile" : { + "damage" : 810, + "pierce" : 19, + "size" : 2, + "maxTravelDistance" : 18, + "view" : { + "textureName" : "SteamRollerGlue", + "animationDuration" : 0.125 + }, + "effects" : [ + { + "type" : "Stun", + "duration" : 2 + }, + { + "type" : "Knockback", + "distancePerSecond" : 2.5, + "duration" : 2 + }, + { + "type" : "Glue", + "speedMultiplier" : 0.75, + "duration" : 4 + } + ], + "speed" : 2.5, + "type" : "Basic" + }, + "description" : "Glue keeps enemies slower. Also more pierce, damage, and knockback", + "fireRate" : 3.5, + "type" : "SpikeRoller" + } + ], + [ + { + "fireRate" : 0.5, + "view" : { + + }, + "cost" : { + "coal" : 400, + "iron" : 100, + "copper" : 200 + }, + "description" : "Shoots smaller spiked balls but much faster", + "projectile" : { + "type" : "Basic", + "maxTravelDistance" : 12, + "pierce" : 3, + "view" : { + "animationDuration" : 0.125, + "textureName" : "SpikedBallSmall" + }, + "speed" : 10, + "size" : 0.5, + "damage" : 72 + }, + "type" : "SpikeRoller", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "name" : "Small spiked ball" + } + ], + [ + { + "fireRate" : 0.0625, + "view" : { + + }, + "cost" : { + "iron" : 100, + "copper" : 600, + "coal" : 1200 + }, + "description" : "Shoots many bullets very fast", + "projectile" : { + "damage" : 108, + "view" : { + "textureName" : "Bullet" + }, + "type" : "Basic", + "speed" : 12, + "size" : 0.125, + "pierce" : 2, + "maxTravelDistance" : 14 + }, + "type" : "SpikeRoller", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "name" : "Machine gun" + } + ], + [ + { + "fireRate" : 0.125, + "cost" : { + "copper" : 1000, + "iron" : 100, + "coal" : 2800 + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "view" : { + + }, + "type" : "SpikeRoller", + "description" : "Shoots rockets instead of bullets", + "projectile" : { + "explosionView" : "Basic", + "type" : "Bomb", + "speed" : 14.4, + "view" : { + "textureName" : "Missile" + }, + "maxTravelDistance" : 18, + "size" : 0.25, + "damage" : 108, + "blastRadius" : 0.5 + }, + "name" : "Rocket gun" + } + ], + [ + { + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "angleBetweenProjectiles" : "30°", + "projectile" : { + "explosionView" : "Basic", + "type" : "Bomb", + "maxTravelDistance" : 24, + "view" : { + "textureName" : "MissileSuper" + }, + "speed" : 14.4, + "blastRadius" : 0.75, + "size" : 0.25, + "damage" : 216 + }, + "cost" : { + "gold" : 250, + "copper" : 1800, + "iron" : 100, + "coal" : 100 + }, + "type" : "SpikeRoller", + "name" : "Gold rocket gun", + "fireRate" : 0.10000000000000001, + "numProjectiles" : 3, + "description" : "Shoots better rockets, and 3 at different angles", + "view" : { + + } + } + ], + [ + { + "type" : "SpikeRoller", + "description" : "Shoots 7 rockets at different angles", + "numProjectiles" : 7, + "view" : { + + }, + "angleBetweenProjectiles" : "15°", + "projectile" : { + "type" : "Bomb", + "view" : { + "textureName" : "MissileSuper" + }, + "size" : 0.25, + "damage" : 216, + "blastRadius" : 0.75, + "explosionView" : "Basic", + "speed" : 14.4, + "maxTravelDistance" : 32 + }, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "name" : "Platinum rocket gun", + "cost" : { + "coal" : 500, + "iron" : 100, + "gold" : 750, + "copper" : 3400 + }, + "fireRate" : 0.080000000000000002 + } + ] + ], + [ + [ + { + "name" : "Electric spikes", + "description" : "Spiked balls have increased pierce, and slightly better fire rate \/ damage", + "fireRate" : 1.6000000000000001, + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "view" : { + + }, + "projectile" : { + "type" : "Basic", + "size" : 1, + "maxTravelDistance" : 12, + "speed" : 7.5, + "damage" : 135, + "pierce" : 10, + "view" : { + "animationDuration" : 0.125, + "textureName" : "SpikedBallElectric" + } + }, + "type" : "SpikeRoller", + "cost" : { + "iron" : 200, + "coal" : 2100 + } + } + ] + ], + [ + [ + { + "projectile" : { + "maxTravelDistance" : 16, + "view" : { + "textureName" : "PlasmaBall" + }, + "pierce" : 20, + "type" : "Basic", + "damage" : 202.5, + "size" : 1, + "speed" : 11.25 + }, + "type" : "SpikeRoller", + "cost" : { + "iron" : 600, + "coal" : 5300 + }, + "name" : "Plasma ball", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "description" : "Shoots balls of plasma instead of spiked balls, with even better pierce", + "fireRate" : 1.28, + "view" : { + + } + } + ] + ], + [ + [ + { + "projectile" : { + "dps" : 400, + "type" : "Beam", + "width" : 0.25, + "view" : { + "color" : "0xE02010" + } + }, + "name" : "Laser turret", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "description" : "Instead of a spiked ball, shoots a laser beam with infinite pierce", + "cost" : { + "uranium" : 400, + "iron" : 2200 + }, + "view" : { + + }, + "type" : "MountedLaser" + } + ] + ], + [ + [ + { + "projectile" : { + "dps" : 720, + "type" : "Flashlight", + "view" : { + "color" : "0xFFF040" + }, + "initialWidth" : 0.5, + "widthPerTile" : 0.25 + }, + "name" : "Giant flashlight", + "terrains" : [ + "Grass", + "Sand", + "Rocks", + "Snow" + ], + "description" : "Giant flashlight apparently deals more damage, also the beam gets wider the further away", + "cost" : { + "uranium" : 2000, + "iron" : 5400 + }, + "view" : { + + }, + "type" : "MountedLaser" + } + ] + ] + ] +} \ No newline at end of file diff --git a/Examples/buildings/Freeze.csv b/Examples/buildings/Freeze.csv new file mode 100644 index 0000000..0c9f30c --- /dev/null +++ b/Examples/buildings/Freeze.csv @@ -0,0 +1,13 @@ +Freeze; name; description; view.animationDuration; cost.iron; cost.aluminum; cost.uranium; type; terrains; radius; fireRate; turnSpeed; projectile.view.textureName; projectile.type; projectile.dps; projectile.maxTravelDistance; projectile.speed; projectile.size; projectile.effects.0.duration; projectile.effects.0.speedMultiplier; projectile.effects.0.type; projectile.effects.1.duration; projectile.effects.1.speedMultiplier; projectile.effects.1.type; projectile.effects.2.damageMultiplier; projectile.effects.2.duration; projectile.effects.2.type; projectile.effects.3.dps; projectile.effects.3.duration; projectile.effects.3.type; effects.0.duration; effects.0.speedMultiplier; effects.0.type; effects.1.damageMultiplier; effects.1.duration; effects.1.speedMultiplier; effects.1.type; effects.2.damageMultiplier; effects.2.dps; effects.2.type; effects.3.damageMultiplier; effects.3.type +0-0-0; Ice tower; Slows enemies in range. Remember that burn and freeze cancel each other out; 2; 125; ; ; RadialFreeze; Grass, Rocks, Snow; 2.5; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 0.5; Freeze; ; ; ; ; ; ; ; ; +1-0-0; Bigger radius; Bigger radius to freeze; 2; 275; ; ; RadialFreeze; Grass, Rocks, Snow; 3.5; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 1; 0.5; Freeze; ; ; ; ; ; ; ; ; +2-0-0; Snow-blower; Enemies stay frozen after they leave and 'thaw out' over a period of time; 2; 1025; ; ; RadialFreeze; Grass, Rocks, Snow; 4.25; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 2; 0.5; Freeze; 1.125; 1; ; FrozenVulnerable; ; ; ; ; +3-0-0; Chill reactor; Enemies move slower, and affect resistant enemies; 2; 3275; ; ; RadialFreeze; Grass, Rocks, Snow; 4.5; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 4; 0.5; Freeze; ; 1; 0.7; ResistantFreeze; 1.2375; ; FrozenVulnerable; ; +0-1-0; Slower; Slows enemies more; 2; 300; ; ; RadialFreeze; Grass, Rocks, Snow; 2.5; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 0.4; Freeze; ; ; 0.95; ResistantFreeze; ; ; ; ; +0-2-0; Wind chill; Enemies are less resistant to other attacks, and take slight damage while in radius; 2; 600; ; ; RadialFreeze; Grass, Rocks, Snow; 2.5; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 0.44; Freeze; ; ; 0.855; ResistantFreeze; ; 40; Frostbite; 1.25; FrozenVulnerable +0-3-0; Ultra cold; Enemies take a lot of damage while in radius; 2; 1500; ; ; RadialFreeze; Grass, Rocks, Snow; 2.5; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 0.484; Freeze; ; ; 0.7695; ResistantFreeze; ; 320; Frostbite; 1.75; FrozenVulnerable +0-4-0; Insane cold; Increased vulnerability, slightly increased damage, affects resistant enemies more; 2; 6000; ; 1000; RadialFreeze; Grass, Rocks, Snow; 2; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 0.5808; Freeze; ; ; 0.6156; ResistantFreeze; ; 640; Frostbite; 2.45; FrozenVulnerable +0-0-1; Ice shooter; Shoots projectiles that freeze nearby enemies; 2; 125; 250; ; ProjectileTurret; Grass, Rocks, Snow; 4; 2; 360°; FreezeBall; MovingArea; 0; 5; 3; 2.5; ; 0.5; Freeze; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +0-0-2; Cold shooter; Projectiles slow more and travel further; 2; 125; 750; ; ProjectileTurret; Grass, Rocks, Snow; 4; 2; 360°; FreezeBall; MovingArea; 0; 6; 2.4; 2.75; ; 0.4; Freeze; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +0-0-3; Chill shooter; Projectiles cause affected enemies to be more vulnerable; 2; 125; 1500; ; ProjectileTurret; Grass, Rocks, Snow; 4; 2; 360°; FreezeBall; MovingArea; 0; 7; 1.92; 3; ; 0.5; Freeze; ; 0.8; ResistantFreeze; 1.5; ; FrozenVulnerable; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +0-0-4; Freeze shooter; Projectiles inflict minor damage on enemies; 2; 125; 4000; 500; ProjectileTurret; Grass, Rocks, Snow; 4; 2; 360°; FreezeBall; MovingArea; 0; 8; 1.536; 3; 0.5; 0.5; Freeze; 0.5; 0.72; ResistantFreeze; 1.8; 0.5; FrozenVulnerable; 175; 0.5; Frostbite; ; ; ; ; ; ; ; ; ; ; ; \ No newline at end of file diff --git a/Examples/buildings/Mine.csv b/Examples/buildings/Mine.csv new file mode 100644 index 0000000..f1f24d4 --- /dev/null +++ b/Examples/buildings/Mine.csv @@ -0,0 +1,11 @@ +Mine; name; description; view; cost.iron; cost.gold; cost.coal; cost.aluminum; cost.uranium; type; radius; degradeMultiplier; ironOresPerYield; isDegradeLocal; oresPerYield; ticksPerYield +0-0-0; Mine; Mines nearby ores, increasing your resources; {}; 250; 100; 250; 500; 1000; BasicMine; 2; 0.75; 1; TRUE; 10; 800 +1-0-0; Laser mine; Only mines its own tile, but faster and more consistently; {}; 250; 150; 250; 500; 2500; BasicMine; 1; 0.75; 5; FALSE; 1; 16 +2-0-0; Deep laser mine; Mines faster, and additionally mines iron; {}; 2750; 400; ; 8000; 2500; IronMine; 1; ; ; ; 4; 16 +3-0-0; Core laser mine; Mines insane amounts of iron. Breaks towers; {}; ; 1150; ; ; ; IronMine; 1; ; ; ; 2; 1 +0-1-0; Bomb mine; Mines larger yields but slower; {}; ; 200; ; ; ; BasicMine; 2; ; ; ; 50; 1600 +0-2-0; Bigger bomb mine; Mines larger yields and radius but slower; {}; ; 400; ; ; ; BasicMine; 3; ; ; ; 200; 3200 +0-3-0; Atomic bomb mine; Massive yields; {}; ; 1250; ; ; ; BasicMine; 3; ; ; ; 2000; 6400 +0-0-1; Larger radius; Mines a bigger radius, but less ore; {}; ; 200; ; ; ; BasicMine; 3; ; ; ; 7; 1200 +0-0-2; Fracking mine; Mines an even larger radius, but degrades buildings in the mining zone; {}; ; 350; ; ; ; FrackingMine; 5; ; ; ; 21; 1800 +0-0-3; Super fracking mine; Mines a huge radius, but degrades every tower; {}; ; 800; ; ; ; FrackingMine; 10; ; ; ; 189; 2700 \ No newline at end of file diff --git a/Examples/buildings/SpikeRoller.csv b/Examples/buildings/SpikeRoller.csv new file mode 100644 index 0000000..23e84a8 --- /dev/null +++ b/Examples/buildings/SpikeRoller.csv @@ -0,0 +1,15 @@ +SpikeRoller; name; description; view; cost.iron; cost.gold; cost.coal; cost.copper; cost.uranium; type; terrains; fireRate; projectile.view.animationDuration; projectile.view.color; projectile.view.textureName; projectile.type; projectile.damage; projectile.dps; projectile.blastRadius; projectile.explosionView; projectile.pierce; projectile.maxTravelDistance; projectile.speed; projectile.size; projectile.width; projectile.initialWidth; projectile.widthPerTile; projectile.effects.0.duration; projectile.effects.0.type; projectile.effects.1.distancePerSecond; projectile.effects.1.duration; projectile.effects.1.type; projectile.effects.2.duration; projectile.effects.2.speedMultiplier; projectile.effects.2.type; numProjectiles; angleBetweenProjectiles +0-0-0; Spike roller; Shoots a spiked ball in a fixed straight line which goes through many enemies; {}; 100; ; ; ; ; SpikeRoller; Grass, Sand, Rocks, Snow; 2; 0.125; ; SpikedBall; Basic; 90; ; ; ; 5; 10; 5; 1; ; ; ; ; ; ; ; ; ; ; ; ; +1-0-0; Big spiked ball; Shoots a bigger spiked ball which does more damage; {}; 250; ; ; ; ; SpikeRoller; Grass, Sand, Rocks, Snow; 2.5; 0.125; ; SpikedBallBig; Basic; 180; ; ; ; 7; 12; 4; 1.25; ; ; ; ; ; ; ; ; ; ; ; ; +2-0-0; Heavy spiked ball; Spiked ball does more damage, pierce, and slight knockback; {}; 550; ; ; ; ; SpikeRoller; Grass, Sand, Rocks, Snow; 2.75; 0.125; ; SpikedBallHeavy; Basic; 360; ; ; ; 10; 14; 3.5; 1.25; ; ; ; 0.5; Stun; 1; 0.5; Knockback; ; ; ; ; +3-0-0; Steam roller; Slightly more pierce, much more knockback; {}; 1450; ; ; ; ; SpikeRoller; Grass, Sand, Rocks, Snow; 3; 0.125; ; SteamRoller; Basic; 540; ; ; ; 14; 16; 3; 2; ; ; ; 1.5; Stun; 2; 1.5; Knockback; ; ; ; ; +4-0-0; Glue roller; Glue keeps enemies slower. Also more pierce, damage, and knockback; {}; 4150; ; ; ; ; SpikeRoller; Grass, Sand, Rocks, Snow; 3.5; 0.125; ; SteamRollerGlue; Basic; 810; ; ; ; 19; 18; 2.5; 2; ; ; ; 2; Stun; 2.5; 2; Knockback; 4; 0.75; Glue; ; +0-1-0; Small spiked ball; Shoots smaller spiked balls but much faster; {}; 100; ; 400; 200; ; SpikeRoller; Grass, Sand, Rocks, Snow; 0.5; 0.125; ; SpikedBallSmall; Basic; 72; ; ; ; 3; 12; 10; 0.5; ; ; ; ; ; ; ; ; ; ; ; ; +0-2-0; Machine gun; Shoots many bullets very fast; {}; 100; ; 1200; 600; ; SpikeRoller; Grass, Sand, Rocks, Snow; 0.0625; ; ; Bullet; Basic; 108; ; ; ; 2; 14; 12; 0.125; ; ; ; ; ; ; ; ; ; ; ; ; +0-3-0; Rocket gun; Shoots rockets instead of bullets; {}; 100; ; 2800; 1000; ; SpikeRoller; Grass, Sand, Rocks, Snow; 0.125; ; ; Missile; Bomb; 108; ; 0.5; Basic; ; 18; 14.4; 0.25; ; ; ; ; ; ; ; ; ; ; ; ; +0-4-0; Gold rocket gun; Shoots better rockets, and 3 at different angles; {}; 100; 250; 100; 1800; ; SpikeRoller; Grass, Sand, Rocks, Snow; 0.1; ; ; MissileSuper; Bomb; 216; ; 0.75; Basic; ; 24; 14.4; 0.25; ; ; ; ; ; ; ; ; ; ; ; 3; 30° +0-5-0; Platinum rocket gun; Shoots 7 rockets at different angles; {}; 100; 750; 500; 3400; ; SpikeRoller; Grass, Sand, Rocks, Snow; 0.080000000000000002; ; ; MissileSuper; Bomb; 216; ; 0.75; Basic; ; 32; 14.4; 0.25; ; ; ; ; ; ; ; ; ; ; ; 7; 15° +0-0-1; Electric spikes; Spiked balls have increased pierce, and slightly better fire rate / damage; {}; 200; ; 2100; ; ; SpikeRoller; Grass, Sand, Rocks, Snow; 1.6; 0.125; ; SpikedBallElectric; Basic; 135; ; ; ; 10; 12; 7.5; 1; ; ; ; ; ; ; ; ; ; ; ; ; +0-0-2; Plasma ball; Shoots balls of plasma instead of spiked balls, with even better pierce; {}; 600; ; 5300; ; ; SpikeRoller; Grass, Sand, Rocks, Snow; 1.28; ; ; PlasmaBall; Basic; 202.5; ; ; ; 20; 16; 11.25; 1; ; ; ; ; ; ; ; ; ; ; ; ; +0-0-3; Laser turret; Instead of a spiked ball, shoots a laser beam with infinite pierce; {}; 2200; ; ; ; 400; MountedLaser; Grass, Sand, Rocks, Snow; ; ; 0xE02010; ; Beam; ; 400; ; ; ; ; ; ; 0.25; ; ; ; ; ; ; ; ; ; ; ; +0-0-4; Giant flashlight; Giant flashlight apparently deals more damage, also the beam gets wider the further away; {}; 5400; ; ; ; 2000; MountedLaser; Grass, Sand, Rocks, Snow; ; ; 0xFFF040; ; Flashlight; ; 720; ; ; ; ; ; ; ; 0.5; 0.25; ; ; ; ; ; ; ; ; ; \ No newline at end of file diff --git a/Examples/buildings/TurretBasic.csv b/Examples/buildings/TurretBasic.csv new file mode 100644 index 0000000..07878eb --- /dev/null +++ b/Examples/buildings/TurretBasic.csv @@ -0,0 +1,26 @@ +TurretBasic; name; description; view; cost.iron; cost.gold; type; terrains; radius; fireRate; turnSpeed; projectile.view.textureName; projectile.type; projectile.damage; projectile.fractionDamage; projectile.pierce; projectile.maxTravelDistance; projectile.speed; projectile.size; projectile.effects.0.dps; projectile.effects.0.duration; projectile.effects.0.fractionDps; projectile.effects.0.type +0-0-0; Standard; All-around turret with medium damage, fire rate, and range. No pierce and projectiles travel fast; {}; 50; 100; ProjectileTurret; Grass, sand, snow, rock; 4; 0.5; 720°; Bullet; Basic; 50; 0.15; 1; 4.5; 18; 0.0625; 4032; 0.25; 0.025000000000000001; ChemBurn +1-0-0; Bronze standard; Increased fire rate, range, and damage; {}; 150; 100; ProjectileTurret; Grass, sand, snow, rock; 4.25; 0.4; 720°; Bullet; Basic; 75; 0.15; 1; 4.75; 18; 0.0625; ; 15; 0.025000000000000001; Hack +2-0-0; Silver standard; Increased damage, fire rate, range, and pierce; {}; 350; 100; ProjectileTurret; Grass, sand, snow, rock; 4.5; 0.32; 720°; Bullet; Basic; 112; 0.15; 2; 5; 18; 0.0625; ; 15; 0.025000000000000001; Hack +3-0-0; Gold standard; Increased range, pierce, damage, and fire rate; {}; 750; 300; ProjectileTurret; Grass, sand, snow, rock; 5; 0.256; 720°; BulletGold; Basic; 168; 0.12; 4; 5.5; 20; 0.0625; ; 18; ; Hack +4-0-0; Platinum standard; Increased pierce, damage, fire rate, and range; {}; 1550; 300; ProjectileTurret; Grass, sand, snow, rock; 6; 0.2048; 720°; BulletPlatinum; Basic; 252; ; 8; 6.5; 24; 0.0625; ; ; ; +5-0-0; The best one; This tower is better than you are; {}; 101550; ; ProjectileTurret; Grass, sand, snow, rock; 100; 0.01; 720°; Best; Basic; 1000; ; 1e+100; 100; 24; 0.25; ; ; ; +0-1-0; Sniper; Big range increase, and fires instant projectiles which are guaranteed to hit the intended target. However, fires much slower; {}; 125; ; SniperTurret; Rocks, grass, sand, snow; 9; 3; 720°; ; ; 500; ; ; ; ; ; ; ; ; +1-1-0; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +2-1-0; Silver standard lvl2; Increased damage, range, and projectile speed; {}; 550; 300; ProjectileTurret; Grass, sand, snow, rock; 5.5; 0.32; 720°; Bullet; Basic; 224; 0.12; 2; 6; 21.6; 0.0625; ; 15; 0.029999999999999999; Hack +3-1-0; Gold standard lvl2; Increased damage, range, and projectile speed; {}; 1150; 300; ProjectileTurret; Grass, sand, snow, rock; 6; 0.256; 720°; Bullet; Basic; 336; 0.12; 4; 6.5; 24; 0.0625; ; ; ; +4-1-0; Platinum standard lvl2; Increased damage, range, and projectile speed; {}; 2350; ; ProjectileTurret; Grass, sand, snow, rock; 7; 0.2048; 720°; Bullet; Basic; 504; ; 8; 7.5; 28.8; 0.0625; ; ; ; +0-2-0; Elite sniper; Increased range and damage; {}; 275; ; SniperTurret; Rocks, grass, sand, snow; 10; 3; 720°; ; ; 1000; ; ; ; ; ; ; ; ; +1-2-0; Advanced sniper; Increased fire rate; {}; 575; ; SniperTurret; Rocks, grass, sand, snow; 10; 2.4; 720°; ; ; 1000; ; ; ; ; ; ; ; ; +2-2-0; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +3-2-0; Gold standard lvl3; Increased damage, range, projectile speed, and pierce; {}; 2750; 300; ProjectileTurret; Grass, sand, snow, rock; 7; 0.256; 720°; Bullet; Basic; 672; 0.12; 5; 7.5; 28.8; 0.0625; ; ; ; +4-2-0; Platinum standard lvl3; Increased damage, range, projectile speed, and pierce; {}; 5550; ; ProjectileTurret; Grass, sand, snow, rock; 8; 0.2048; 720°; Bullet; Basic; 1008; ; 10; 8.5; 34.56; 0.0625; ; ; ; +0-3-0; Marine sniper; % damage; {}; 875; ; SniperTurret; Rocks, grass, sand, snow; 11; 4; 720°; ; ; 4000; ; ; ; ; ; ; ; ; +1-3-0; Commando sniper; Increased fire rate; {}; 1475; ; SniperTurret; Rocks, grass, sand, snow; 11; 3.2; 720°; ; ; 4000; ; ; ; ; ; ; ; ; +2-3-0; Agent sniper; Increased range, slightly increased fire rate; {}; 2375; ; SniperTurret; Rocks, grass, sand, snow; 15.4; 2.88; 720°; ; ; 4000; ; ; ; ; ; ; ; ; +3-3-0; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +4-3-0; Platinum standard lvl4; Increased damage, range, projectile speed, and pierce; {}; 8750; ; ProjectileTurret; Grass, sand, snow, rock; 9; 0.2048; 720°; Bullet; Basic; 2016; ; 15; 9.5; 41.472; 0.0625; ; ; ; +0-4-0; Robot sniper; Hack effect deals % damage over time; {}; 3275; ; SniperTurret; Rocks, grass, sand, snow; 12; 5; 720°; ; ; 4800; ; ; ; ; ; ; ; ; +1-4-0; Sniper X; Increased fire rate; {}; 4175; ; SniperTurret; Rocks, grass, sand, snow; 12; 4; 720°; ; ; 4800; ; ; ; ; ; ; ; ; +2-4-0; Sniper Z; Increased range, slightly increased fire rate; {}; 6875; ; SniperTurret; Rocks, grass, sand, snow; 16.8; 3.6; 720°; ; ; 4800; ; ; ; ; ; ; ; ; +3-4-0; .sniper; Unlimited range; {}; 12275; ; SniperTurret; Rocks, grass, sand, snow; 100; 3.6; 720°; ; ; 4800; ; ; ; ; ; ; ; ; \ No newline at end of file diff --git a/Examples/buildings/TurretBomb.csv b/Examples/buildings/TurretBomb.csv new file mode 100644 index 0000000..5774336 --- /dev/null +++ b/Examples/buildings/TurretBomb.csv @@ -0,0 +1,15 @@ +TurretBomb; name; description; view; cost.iron; cost.coal; cost.copper; cost.aluminum; cost.uranium; type; terrains; radius; fireRate; turnSpeed; projectile.view.arcs; projectile.view.textureName; projectile.type; projectile.damage; projectile.blastRadius; projectile.explosionView; projectile.maxTravelDistance; projectile.speed; projectile.size; projectile.effects.0.dps; projectile.effects.0.duration; projectile.effects.0.pullPerSecond; projectile.effects.0.type; projectile.effects.1.distancePerSecond; projectile.effects.1.duration; projectile.effects.1.type; numProjectiles; needsToRotateToTarget +0-0-0; Bomb launcher; Shoots a bomb which explodes on impact, damaging nearby enemies; {}; 25; ; 75; ; ; ProjectileTurret; Grass, Sand, Rocks, Snow; 6; 1.5; 720°; ; Bomb; Bomb; 70; 1; Basic; 6.5; 7; 0.5; ; ; ; ; ; ; ; ; +1-0-0; Heavy bombs; Increased damage; {}; 125; ; 225; ; ; ProjectileTurret; Grass, Sand, Rocks, Snow; 6; 1.5; 720°; ; BombHeavy; Bomb; 140; 1; Basic; 6.5; 7; 0.5; ; ; ; ; ; ; ; ; +2-0-0; Propane bombs; Increased damage, slight burn; {}; 375; 150; 475; ; ; ProjectileTurret; Grass, Sand, Rocks, Snow; 6; 1.8; 720°; ; BombPropane; Bomb; 280; 1.2; Basic; 6.5; 7; 0.6; 20; 5; ; Burn; ; ; ; ; +3-0-0; Napalm bombs; Slightly increased damage, major burn; {}; 375; 400; 975; ; ; ProjectileTurret; Grass, Sand, Rocks, Snow; 6; 1.98; 720°; ; BombNapalm; Bomb; 336; 1.44; Basic; 6.5; 7; 0.72; 80; 5; ; Burn; ; ; ; ; +4-0-0; Atomic bombs; Increased damage and blast radius, replaces effect with chemical burn; {}; ; 2900; 1075; ; 2500; ProjectileTurret; Grass, Sand, Rocks, Snow; 6; 2.97; 720°; ; BombNuke; Bomb; 1008; 2.88; Nuke; 6.5; 7; 0.864; 320; 7.5; ; ChemBurn; ; ; ; ; +0-1-0; Upward launcher; Increased range, and bombs travel above enemies to target; {}; ; ; ; 25; ; ProjectileTurret; Grass, Sand, Rocks, Snow; 7.5; 1.5; 720°; TRUE; Bomb; AirBomb; 105; 1; Basic; ; 7; ; ; ; ; ; ; ; ; ; FALSE +0-2-0; Missile launcher; Massive range, shoots missiles which hone onto targets; {}; ; ; ; 100; ; ProjectileTurret; Grass, Sand, Rocks, Snow; 10; 1.2; 720°; TRUE; Missile; Missile; 126; 1; Basic; ; 9; ; ; ; ; ; ; ; ; ; FALSE +0-3-0; Triple launcher; Shoots 3 missiles at once. Special targeting mode means that each missile will target different enemies; {}; ; ; ; 350; ; ProjectileTurret; Grass, Sand, Rocks, Snow; 10; 1.2; 720°; TRUE; Missile; Missile; 126; 1; Basic; ; 9; ; ; ; ; ; ; ; ; 3; FALSE +0-4-0; Silo; Increased range, shoots individual bombs but 7x as fast; {}; ; ; ; 1350; ; ProjectileTurret; Grass, Sand, Rocks, Snow; 12; 0.171428571428571; 720°; TRUE; Missile; Missile; 126; 1; Basic; ; 9; ; ; ; ; ; ; ; ; ; FALSE +0-5-0; Super Silo; Shoots better missiles, increased range, slightly faster than before; {}; ; ; ; 3850; ; ProjectileTurret; Grass, Sand, Rocks, Snow; 14; 0.12; 720°; TRUE; MissileSuper; Missile; 378; 1.4; Basic; ; 14; ; ; ; ; ; ; ; ; ; FALSE +0-0-1; Big bombs; Increased blast radius; {}; ; 75; ; ; ; ProjectileTurret; Grass, Sand, Rocks, Snow; 6; 1.5; 720°; ; BombBig; Bomb; 84; 1.4; Basic; 6.5; 7; 0.75; ; ; ; ; ; ; ; ; +0-0-2; Giant bombs; Increased blast radius, slight knockback; {}; ; 275; ; ; ; ProjectileTurret; Grass, Sand, Rocks, Snow; 4.5; 1.5; 720°; ; BombGiant; Bomb; 117.6; 1.68; Basic; 5; 5.5; 1.125; ; 0.25; ; Stun; 0.5; 0.25; Knockback; ; +0-0-3; Stun bomb; Stuns enemies, and more knockback and damage; {}; ; 775; ; ; ; ProjectileTurret; Grass, Sand, Rocks, Snow; 4.5; 1.5; 720°; ; BombStun; Bomb; 164.64; 1.848; Basic; 5; 4.5; 1.125; ; 1; ; Stun; 0.5; 1; Knockback; ; +0-0-4; Black hole bomb; Bombs create a black hole which stuns even resistant enemies and draws them to the center; {}; ; 10775; ; ; ; ProjectileTurret; Grass, Sand, Rocks, Snow; 4.5; 4; 720°; False; BombBlackHole; AirBomb; 0; 4; BlackHole; ; 3; ; 300; 4; 0.25; BlackHoleStun; ; ; ; ; \ No newline at end of file diff --git a/Examples/buildings/TurretFlamethrower.csv b/Examples/buildings/TurretFlamethrower.csv new file mode 100644 index 0000000..4e902ab --- /dev/null +++ b/Examples/buildings/TurretFlamethrower.csv @@ -0,0 +1,11 @@ +TurretFlamethrower; name; description; view; cost.iron; cost.gold; cost.coal; cost.copper; cost.uranium; type; terrains; radius; fireRate; turnSpeed; projectile.view.textureName; projectile.type; projectile.damage; projectile.pierce; projectile.maxTravelDistance; projectile.speed; projectile.size; projectile.effects.0.dps; projectile.effects.0.duration; projectile.effects.0.finalFractionDps; projectile.effects.0.fractionDpsChangeDuration; projectile.effects.0.initialFractionDps; projectile.effects.0.speedMultiplier; projectile.effects.0.type; projectile.effects.0.vulnerabilityDamageMultiplier; projectile.effects.1.duration; projectile.effects.1.speedMultiplier; projectile.effects.1.type; projectile.effects.2.damageMultiplier; projectile.effects.2.duration; projectile.effects.2.type +0-0-0; Flamethrower; Close range tower with good pierce and a burn effect which persists after enemies leave the tower's radius; {}; 50; 150; 25; 250; ; ProjectileTurret; Grass, Sand, Rocks; 2; 0.0625; 360°; Fire; Basic; 10; 3; 2; 8; 0.25; 15; 5; ; ; ; ; Burn; ; ; ; ; ; ; +1-0-0; More volatile fuel; More direct damage, much more burn damage, and longer lasting burn; {}; 75; 600; 125; 650; ; ProjectileTurret; Grass, Sand, Rocks; 2; 0.0625; 360°; FireVolatile; Basic; 20; 4; 2; 8; 0.25; 60; 7.5; ; ; ; ; Burn; ; ; ; ; ; ; +2-0-0; Yellow fire; More pierce, damage, burn damage, and burn lasts much longer; {}; 150; ; 525; 2250; ; ProjectileTurret; Grass, Sand, Rocks; 2; 0.0625; 360°; FireYellow; Basic; 40; 5; 2; 8; 0.25; 120; 11.25; ; ; ; ; Burn; ; ; ; ; ; ; +3-0-0; Green fire; Chemical burn (can be combined with freeze), more damage; {}; 375; ; 1325; 8650; ; ProjectileTurret; Grass, Sand, Rocks; 2.5; 0.0625; 432°; FireGreen; Basic; 80; 7; 2.5; 8; 0.25; 240; 16.875; ; ; ; ; ChemBurn; ; ; ; ; ; ; +4-0-0; Black fire; Replaces with the doom effect: major percent damage over time, slower speed, and multiplied damage from all attacks, permanently; {}; 825; ; 2925; ; 1000; ProjectileTurret; Grass, Sand, Rocks; 2.5; 0.0625; 518°; FireBlack; Basic; 160; 9; 2.5; 8; 0.25; ; ; 0.040000000000000001; 3; 0.25; 0.75; Doom; 1.75; ; ; ; ; ; +0-1-0; Faster fuel; Increased pierce, damage, and turn speed; {}; 1725; ; 150; ; ; ProjectileTurret; Grass, Sand, Rocks; 2.25; 0.0625; 540°; Fire; Basic; 30; 7; 2.25; 8; 0.25; 22.5; 5; ; ; ; ; Burn; ; ; ; ; ; ; +0-2-0; Larger flames; Increased pierce, damage, turn speed, and projectile size; {}; ; ; 400; ; ; ProjectileTurret; Grass, Sand, Rocks; 2.5; 0.0625; 648°; FireBig; Basic; 60; 9; 2.5; 8; 0.375; 45; 7; ; ; ; ; Burn; ; ; ; ; ; ; +0-3-0; Circle of fire; Shoots flames around the entire tower (TODO); {}; ; ; 900; ; ; ProjectileTurret; Grass, Sand, Rocks; 2.5; 0.0625; 0°; FireBig; Basic; 90; 14; 2.5; 8; 0.45; 45; 7; ; ; ; ; Burn; ; ; ; ; ; ; +0-4-0; Golden circle; Increased range and damage (TODO); {}; ; ; 2400; ; ; ProjectileTurret; Grass, Sand, Rocks; 3; 0.0625; 0°; FireBig; Basic; 180; 20; 3; 8; 0.45; 67.5; 8.4; ; ; ; ; Burn; ; ; ; ; ; ; +0-5-0; Inferno; Massive damage, and makes enemies in radius slower and more vulnerable; {}; ; ; 5400; ; 1000; ProjectileTurret; Grass, Sand, Rocks; 3; 0.0625; 0°; FireBig; Basic; 540; 40; 3; 8; 0.45; 135; 8.4; ; ; ; ; Burn; ; 0.25; 0.5; Slow; 1.5; 0.25; Vulnerable \ No newline at end of file diff --git a/Examples/rounds-roundabout.json b/Examples/rounds-roundabout.json new file mode 100644 index 0000000..be9531f --- /dev/null +++ b/Examples/rounds-roundabout.json @@ -0,0 +1,367 @@ +[ + [ + { + "count" : 10, + "type" : "Pyramid3", + "delayBetween" : 2 + } + ], + [ + { + "type" : "Pyramid3", + "delayBetween" : 0.125, + "count" : 15 + } + ], + [ + { + "type" : "Pyramid3", + "count" : 25, + "delayBetween" : 0.5 + } + ], + [ + { + "count" : 10, + "delayBetween" : 2, + "type" : "Cube" + } + ], + [ + { + "type" : "Cube", + "count" : 3, + "delayBetween" : 0.5 + }, + { + "type" : "Pyramid3", + "count" : 2, + "delayBetween" : 0.5 + }, + { + "type" : "Cube", + "count" : 3, + "delayBetween" : 0.5 + }, + { + "type" : "Pyramid3", + "delayBetween" : 0.5, + "count" : 2 + }, + { + "type" : "Cube", + "count" : 3, + "delayBetween" : 0.5 + }, + { + "type" : "Pyramid3", + "count" : 2, + "delayBetween" : 0.5 + } + ], + [ + { + "type" : "Cube", + "count" : 20, + "delayBetween" : 0.125 + } + ], + [ + { + "delayBetween" : 1, + "count" : 25, + "type" : "Pyramid4" + } + ], + [ + { + "delayBetween" : 0.0625, + "type" : "Pyramid4", + "count" : 50 + } + ], + [ + { + "delayBetween" : 0.25, + "count" : 3, + "type" : "Pyramid3" + }, + { + "type" : "Pyramid4", + "delayBetween" : 0.25, + "count" : 3 + }, + { + "type" : "Cube", + "delayBetween" : 0.25, + "count" : 3 + }, + { + "count" : 3, + "delayBetween" : 0.25, + "type" : "Pyramid3" + }, + { + "count" : 3, + "delayBetween" : 0.25, + "type" : "Pyramid4" + }, + { + "count" : 3, + "delayBetween" : 0.25, + "type" : "Cube" + }, + { + "count" : 3, + "delayBetween" : 0.25, + "type" : "Pyramid3" + }, + { + "type" : "Pyramid4", + "delayBetween" : 0.25, + "count" : 3 + }, + { + "count" : 3, + "delayBetween" : 0.25, + "type" : "Cube" + }, + { + "delayBetween" : 0.25, + "type" : "Pyramid3", + "count" : 3 + }, + { + "type" : "Pyramid3", + "delayBetween" : 0.25, + "count" : 3 + }, + { + "count" : 3, + "delayBetween" : 0.25, + "type" : "Cube" + }, + { + "count" : 3, + "delayBetween" : 0.25, + "type" : "Pyramid3" + }, + { + "count" : 3, + "delayBetween" : 0.25, + "type" : "Pyramid4" + }, + { + "type" : "Cube", + "delayBetween" : 0.25, + "count" : 3 + } + ], + [ + { + "type" : "SuperCube", + "count" : 1, + "delayBetween" : 0 + } + ], + [ + { + "type" : "Prism5", + "delayBetween" : 2, + "count" : 25 + } + ], + [ + { + "count" : 20, + "delayBetween" : 0.125, + "type" : "Prism5" + } + ], + [ + { + "type" : "Diamond4", + "delayBetween" : 0.25, + "count" : 30 + } + ], + [ + { + "delayBetween" : 0.050000000000000003, + "type" : "Diamond4", + "count" : 30 + } + ], + [ + { + "type" : "Prism5", + "count" : 5, + "delayBetween" : 0.25 + }, + { + "count" : 5, + "type" : "Diamond4", + "delayBetween" : 0.25 + }, + { + "type" : "Prism5", + "delayBetween" : 0.5, + "count" : 5 + }, + { + "delayBetween" : 0.25, + "count" : 5, + "type" : "Diamond4" + }, + { + "count" : 5, + "delayBetween" : 0.5, + "type" : "Prism5" + }, + { + "count" : 5, + "type" : "Diamond4", + "delayBetween" : 0.25 + }, + { + "count" : 5, + "delayBetween" : 0.5, + "type" : "Prism5" + }, + { + "count" : 5, + "delayBetween" : 0.25, + "type" : "Diamond4" + }, + { + "count" : 5, + "delayBetween" : 0.5, + "type" : "Prism5" + }, + { + "count" : 50, + "delayBetween" : 0.25, + "type" : "Diamond4" + } + ], + [ + { + "count" : 15, + "type" : "Dodecahedron", + "delayBetween" : 3 + } + ], + [ + { + "delayBetween" : 0.5, + "type" : "Diamond4", + "count" : 10 + }, + { + "type" : "Dodecahedron", + "count" : 1, + "delayBetween" : 0.5 + }, + { + "delayBetween" : 0.5, + "type" : "Diamond4", + "count" : 10 + }, + { + "type" : "Dodecahedron", + "count" : 1, + "delayBetween" : 0.5 + }, + { + "count" : 10, + "delayBetween" : 0.5, + "type" : "Diamond4" + }, + { + "count" : 1, + "delayBetween" : 0.5, + "type" : "Dodecahedron" + }, + { + "type" : "Diamond4", + "delayBetween" : 0.5, + "count" : 10 + }, + { + "type" : "Dodecahedron", + "delayBetween" : 0.5, + "count" : 1 + }, + { + "count" : 10, + "delayBetween" : 0.5, + "type" : "Diamond4" + }, + { + "type" : "Dodecahedron", + "count" : 1, + "delayBetween" : 0.5 + } + ], + [ + { + "type" : "Dodecahedron", + "delayBetween" : 0.125, + "count" : 10 + } + ], + [ + { + "type" : "Prism5", + "count" : 100, + "delayBetween" : 0.125 + }, + { + "delayBetween" : 0.25, + "type" : "Diamond4", + "count" : 10 + } + ], + [ + { + "type" : "SuperPyramid3", + "count" : 8, + "delayBetween" : 4 + } + ], + [ + { + "delayBetween" : 3, + "count" : 10, + "type" : "StellatedDodecahedron" + } + ], + [ + { + "count" : 10, + "type" : "StellatedDodecahedron", + "delayBetween" : 0.125 + } + ], + [ + { + "delayBetween" : 3, + "count" : 10, + "type" : "StellatedDodecahedron" + } + ], + [ + { + "count" : 10, + "type" : "StellatedDodecahedron", + "delayBetween" : 0.125 + } + ], + [ + { + "type" : "Icosahedron", + "delayBetween" : 2, + "count" : 20 + } + ] +] \ No newline at end of file diff --git a/Examples/rounds.csv b/Examples/rounds.csv new file mode 100644 index 0000000..e3b91c2 --- /dev/null +++ b/Examples/rounds.csv @@ -0,0 +1,26 @@ +Rounds; 0.count; 0.delayBetween; 0.type; 1.count; 1.delayBetween; 1.type; 10.count; 10.delayBetween; 10.type; 11.count; 11.delayBetween; 11.type; 12.count; 12.delayBetween; 12.type; 13.count; 13.delayBetween; 13.type; 14.count; 14.delayBetween; 14.type; 2.count; 2.delayBetween; 2.type; 3.count; 3.delayBetween; 3.type; 4.count; 4.delayBetween; 4.type; 5.count; 5.delayBetween; 5.type; 6.count; 6.delayBetween; 6.type; 7.count; 7.delayBetween; 7.type; 8.count; 8.delayBetween; 8.type; 9.count; 9.delayBetween; 9.type +14; 5; 0.25; Prism5; 5; 0.25; Diamond4; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 5; 0.5; Prism5; 5; 0.25; Diamond4; 5; 0.5; Prism5; 5; 0.25; Diamond4; 5; 0.5; Prism5; 5; 0.25; Diamond4; 5; 0.5; Prism5; 50; 0.25; Diamond4 +20; 10; 3; StellatedDodecahedron; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +12; 30; 0.25; Diamond4; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +19; 8; 4; SuperPyramid3; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +22; 10; 3; StellatedDodecahedron; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +3; 10; 2; Cube; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +13; 30; 0.05; Diamond4; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +21; 10; 0.125; StellatedDodecahedron; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +7; 50; 0.0625; Pyramid4; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +6; 25; 1; Pyramid4; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +5; 20; 0.125; Cube; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +10; 25; 2; Prism5; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +23; 10; 0.125; StellatedDodecahedron; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +18; 100; 0.125; Prism5; 10; 0.25; Diamond4; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +1; 15; 0.125; Pyramid3; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +9; 1; 0; SuperCube; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +15; 15; 3; Dodecahedron; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +2; 25; 0.5; Pyramid3; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +11; 20; 0.125; Prism5; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +16; 10; 0.5; Diamond4; 1; 0.5; Dodecahedron; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 10; 0.5; Diamond4; 1; 0.5; Dodecahedron; 10; 0.5; Diamond4; 1; 0.5; Dodecahedron; 10; 0.5; Diamond4; 1; 0.5; Dodecahedron; 10; 0.5; Diamond4; 1; 0.5; Dodecahedron +24; 20; 2; Icosahedron; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +0; 10; 2; Pyramid3; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +17; 10; 0.125; Dodecahedron; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +8; 3; 0.25; Pyramid3; 3; 0.25; Pyramid4; 3; 0.25; Pyramid3; 3; 0.25; Cube; 3; 0.25; Pyramid3; 3; 0.25; Pyramid4; 3; 0.25; Cube; 3; 0.25; Cube; 3; 0.25; Pyramid3; 3; 0.25; Pyramid4; 3; 0.25; Cube; 3; 0.25; Pyramid3; 3; 0.25; Pyramid4; 3; 0.25; Cube; 3; 0.25; Pyramid3 +4; 3; 0.5; Cube; 2; 0.5; Pyramid3; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 3; 0.5; Cube; 2; 0.5; Pyramid3; 3; 0.5; Cube; 2; 0.5; Pyramid3; ; ; ; ; ; ; ; ; ; ; ; \ No newline at end of file diff --git a/Examples/rounds.json b/Examples/rounds.json new file mode 100644 index 0000000..76f7356 --- /dev/null +++ b/Examples/rounds.json @@ -0,0 +1,367 @@ +[ + [ + { + "type": "Pyramid3", + "count": 10, + "delayBetween": 2 + } + ], + [ + { + "type": "Pyramid3", + "count": 15, + "delayBetween": 0.125 + } + ], + [ + { + "type": "Pyramid3", + "count": 25, + "delayBetween": 0.5 + } + ], + [ + { + "type": "Cube", + "count": 10, + "delayBetween": 2 + } + ], + [ + { + "type": "Cube", + "count": 3, + "delayBetween": 0.5 + }, + { + "type": "Pyramid3", + "count": 2, + "delayBetween": 0.5 + }, + { + "type": "Cube", + "count": 3, + "delayBetween": 0.5 + }, + { + "type": "Pyramid3", + "count": 2, + "delayBetween": 0.5 + }, + { + "type": "Cube", + "count": 3, + "delayBetween": 0.5 + }, + { + "type": "Pyramid3", + "count": 2, + "delayBetween": 0.5 + } + ], + [ + { + "type": "Cube", + "count": 20, + "delayBetween": 0.125 + } + ], + [ + { + "type": "Pyramid4", + "count": 25, + "delayBetween": 1 + } + ], + [ + { + "type": "Pyramid4", + "count": 50, + "delayBetween": 0.0625 + } + ], + [ + { + "type": "Pyramid3", + "count": 3, + "delayBetween": 0.25 + }, + { + "type": "Pyramid4", + "count": 3, + "delayBetween": 0.25 + }, + { + "type": "Cube", + "count": 3, + "delayBetween": 0.25 + }, + { + "type": "Pyramid3", + "count": 3, + "delayBetween": 0.25 + }, + { + "type": "Pyramid4", + "count": 3, + "delayBetween": 0.25 + }, + { + "type": "Cube", + "count": 3, + "delayBetween": 0.25 + }, + { + "type": "Pyramid3", + "count": 3, + "delayBetween": 0.25 + }, + { + "type": "Pyramid4", + "count": 3, + "delayBetween": 0.25 + }, + { + "type": "Cube", + "count": 3, + "delayBetween": 0.25 + }, + { + "type": "Pyramid3", + "count": 3, + "delayBetween": 0.25 + }, + { + "type": "Pyramid3", + "count": 3, + "delayBetween": 0.25 + }, + { + "type": "Cube", + "count": 3, + "delayBetween": 0.25 + }, + { + "type": "Pyramid3", + "count": 3, + "delayBetween": 0.25 + }, + { + "type": "Pyramid4", + "count": 3, + "delayBetween": 0.25 + }, + { + "type": "Cube", + "count": 3, + "delayBetween": 0.25 + } + ], + [ + { + "type": "SuperCube", + "count": 1, + "delayBetween": 0 + } + ], + [ + { + "type": "Prism5", + "count": 25, + "delayBetween": 2 + } + ], + [ + { + "type": "Prism5", + "count": 20, + "delayBetween": 0.125 + } + ], + [ + { + "type": "Diamond4", + "count": 30, + "delayBetween": 0.25 + } + ], + [ + { + "type": "Diamond4", + "count": 30, + "delayBetween": 0.05 + } + ], + [ + { + "type": "Prism5", + "count": 5, + "delayBetween": 0.25 + }, + { + "type": "Diamond4", + "count": 5, + "delayBetween": 0.25 + }, + { + "type": "Prism5", + "count": 5, + "delayBetween": 0.5 + }, + { + "type": "Diamond4", + "count": 5, + "delayBetween": 0.25 + }, + { + "type": "Prism5", + "count": 5, + "delayBetween": 0.5 + }, + { + "type": "Diamond4", + "count": 5, + "delayBetween": 0.25 + }, + { + "type": "Prism5", + "count": 5, + "delayBetween": 0.5 + }, + { + "type": "Diamond4", + "count": 5, + "delayBetween": 0.25 + }, + { + "type": "Prism5", + "count": 5, + "delayBetween": 0.5 + }, + { + "type": "Diamond4", + "count": 50, + "delayBetween": 0.25 + } + ], + [ + { + "type": "Dodecahedron", + "count": 15, + "delayBetween": 3 + } + ], + [ + { + "type": "Diamond4", + "count": 10, + "delayBetween": 0.5 + }, + { + "type": "Dodecahedron", + "count": 1, + "delayBetween": 0.5 + }, + { + "type": "Diamond4", + "count": 10, + "delayBetween": 0.5 + }, + { + "type": "Dodecahedron", + "count": 1, + "delayBetween": 0.5 + }, + { + "type": "Diamond4", + "count": 10, + "delayBetween": 0.5 + }, + { + "type": "Dodecahedron", + "count": 1, + "delayBetween": 0.5 + }, + { + "type": "Diamond4", + "count": 10, + "delayBetween": 0.5 + }, + { + "type": "Dodecahedron", + "count": 1, + "delayBetween": 0.5 + }, + { + "type": "Diamond4", + "count": 10, + "delayBetween": 0.5 + }, + { + "type": "Dodecahedron", + "count": 1, + "delayBetween": 0.5 + } + ], + [ + { + "type": "Dodecahedron", + "count": 10, + "delayBetween": 0.125 + } + ], + [ + { + "type": "Prism5", + "count": 100, + "delayBetween": 0.125 + }, + { + "type": "Diamond4", + "count": 10, + "delayBetween": 0.25 + } + ], + [ + { + "type": "SuperPyramid3", + "count": 8, + "delayBetween": 4 + } + ], + [ + { + "type": "StellatedDodecahedron", + "count": 10, + "delayBetween": 3 + } + ], + [ + { + "type": "StellatedDodecahedron", + "count": 10, + "delayBetween": 0.125 + } + ], + [ + { + "type": "StellatedDodecahedron", + "count": 10, + "delayBetween": 3 + } + ], + [ + { + "type": "StellatedDodecahedron", + "count": 10, + "delayBetween": 0.125 + } + ], + [ + { + "type": "Icosahedron", + "count": 20, + "delayBetween": 2 + } + ] + ] \ No newline at end of file diff --git a/Examples/statusEffects-roundabout.json b/Examples/statusEffects-roundabout.json new file mode 100644 index 0000000..1dca3a4 --- /dev/null +++ b/Examples/statusEffects-roundabout.json @@ -0,0 +1,76 @@ +{ + "glue" : { + "view" : { + "scale" : 0.5, + "animationDuration" : 1 + }, + "name" : "Glue", + "isSpreadable" : true + }, + "knockback" : { + "name" : "Knockback" + }, + "burn" : { + "view" : { + "animationDuration" : 1, + "scale" : 1 + }, + "kind" : "Burn", + "name" : "Burn" + }, + "frostbite" : { + "kind" : "Freeze", + "name" : "Freeze" + }, + "slow" : { + "name" : "Slow" + }, + "vulnerable" : { + "name" : "Vulnerable" + }, + "resistantSlow" : { + "name" : "Resistant Slow" + }, + "poison" : { + "name" : "Poison" + }, + "stun" : { + "name" : "Stun" + }, + "doom" : { + "name" : "Doom" + }, + "chemBurn" : { + "view" : { + "animationDuration" : 1, + "scale" : 1 + }, + "name" : "Chemical-burn" + }, + "hack" : { + "name" : "Hack" + }, + "blackHoleStun" : { + "name" : "Black-hole" + }, + "freeze" : { + "view" : { + "scale" : 0.5, + "animationDuration" : 1 + }, + "kind" : "Freeze", + "name" : "Freeze" + }, + "frozenVulnerable" : { + "kind" : "Freeze", + "name" : "Frozen-vulnerable" + }, + "disease" : { + "name" : "Disease", + "isSpreadable" : true + }, + "resistantFreeze" : { + "name" : "Resistant Freeze", + "kind" : "Freeze" + } +} \ No newline at end of file diff --git a/Examples/statusEffects.csv b/Examples/statusEffects.csv new file mode 100644 index 0000000..4490db7 --- /dev/null +++ b/Examples/statusEffects.csv @@ -0,0 +1,18 @@ +StatusEffects; name; view.animationDuration; view.scale; kind; isSpreadable +resistantFreeze; Resistant Freeze; ; ; Freeze; +disease; Disease; ; ; ; TRUE +hack; Hack; ; ; ; +frostbite; Freeze; ; ; Freeze; +poison; Poison; ; ; ; +stun; Stun; ; ; ; +knockback; Knockback; ; ; ; +frozenVulnerable; Frozen-vulnerable; ; ; Freeze; +slow; Slow; ; ; ; +burn; Burn; 1; 1; Burn; +vulnerable; Vulnerable; ; ; ; +chemBurn; Chemical-burn; 1; 1; ; +blackHoleStun; Black-hole; ; ; ; +freeze; Freeze; 1; 0.5; Freeze; +glue; Glue; 1; 0.5; ; TRUE +doom; Doom; ; ; ; +resistantSlow; Resistant Slow; ; ; ; \ No newline at end of file diff --git a/Examples/statusEffects.json b/Examples/statusEffects.json new file mode 100644 index 0000000..8693cf5 --- /dev/null +++ b/Examples/statusEffects.json @@ -0,0 +1,76 @@ +{ + "burn": { + "name": "Burn", + "view": { + "animationDuration": 1, + "scale": 1 + }, + "kind": "Burn" + }, + "frostbite": { + "name": "Freeze", + "kind": "Freeze" + }, + "chemBurn": { + "name": "Chemical-burn", + "view": { + "animationDuration": 1, + "scale": 1 + } + }, + "disease": { + "name": "Disease", + "isSpreadable": true + }, + "poison": { + "name": "Poison" + }, + "hack": { + "name": "Hack" + }, + "doom": { + "name": "Doom" + }, + "vulnerable": { + "name": "Vulnerable" + }, + "frozenVulnerable": { + "name": "Frozen-vulnerable", + "kind": "Freeze" + }, + "freeze": { + "name": "Freeze", + "view": { + "animationDuration": 1, + "scale": 0.5 + }, + "kind": "Freeze" + }, + "resistantFreeze": { + "name": "Resistant Freeze", + "kind": "Freeze" + }, + "slow": { + "name": "Slow" + }, + "resistantSlow": { + "name": "Resistant Slow" + }, + "glue": { + "name": "Glue", + "view": { + "animationDuration": 1, + "scale": 0.5 + }, + "isSpreadable": true + }, + "stun": { + "name": "Stun" + }, + "blackHoleStun": { + "name": "Black-hole" + }, + "knockback": { + "name": "Knockback" + } +} \ No newline at end of file diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..228d9c6 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "swift-argument-parser", + "repositoryURL": "https://github.com/apple/swift-argument-parser", + "state": { + "branch": null, + "revision": "92646c0cdbaca076c8d3d0207891785b3379cbff", + "version": "0.3.1" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..20d3cc3 --- /dev/null +++ b/Package.swift @@ -0,0 +1,18 @@ +// swift-tools-version:5.2 + +import PackageDescription + +let package = Package( + name: "json-csv", + products: [ + .executable(name: "json-csv", targets: ["json-csv"]) + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-argument-parser", from: "0.3.0"), + ], + targets: [ + .target(name: "json-csv", dependencies: [ + .product(name: "ArgumentParser", package: "swift-argument-parser"), + ]), + ] +) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..468e913 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# json-csv + +This is a command-line tool that converts JSON files into CSV format and vice versa. See the examples in `Data`. + +This is very useful because the CSVs can be imported and exported from LibreOffice or Excel, where you can use formulas to synchronize data across fields. + +## CSV format + +The rows in the CSV file correspond to different values depending on the optional `--column-format` argument of the command: + +- **(default) standard:** the JSON is an object, and the rows correspond to top-level fields. Or, if the rows all start with a number, the JSON is an array, and the rows correspond to indices +- **array:** the JSON is a 3-dimensional staggered array. Each row must start with `#-#-#`, where each `#` is a number - this sequence of numbers is the index path of the row's corresponding JSON. This is used internally, in the future it might support arrays of different dimensions. + +The columns in the CSV file correspond to property paths. For example, `car.model` corresponds to the property `model` inside of the property `car` (which is an object), inside of the object that corresponds to the entire row. + +You can also splice a CSV into an existing JSON, so that the CSV replaces one of the top-level fields of the JSON instead of being converted into its own file. This won't rearrange any of the other fields, either. *Note that this feature requires the [`jq` command-line tool](https://stedolan.github.io/jq/) to be installed. + +## Building + +`json-csv` is built using the [Swift Package Manager](https://swift.org/package-manager/). Run `swift build` to build, and `swift run ftd-data-convert ` to run. + +You can run `test.sh` to perform some basic integration tests. The tests require [`jq`](https://stedolan.github.io/jq/) to work. \ No newline at end of file diff --git a/Sources/json-csv/ColumnFormat.swift b/Sources/json-csv/ColumnFormat.swift new file mode 100644 index 0000000..2690acb --- /dev/null +++ b/Sources/json-csv/ColumnFormat.swift @@ -0,0 +1,24 @@ +// +// Created by Jakob Hain on 12/4/20. +// Copyright (c) 2020 Jakobeha. All rights reserved. +// + +import ArgumentParser +import Foundation + +enum ColumnFormat: String, CaseIterable, ExpressibleByArgument { + case standard + case array + + public init?(argument: String) { + self.init(rawValue: argument) + } + + public var defaultValueDescription: String { + rawValue + } + + public static var defaultCompletionKind: CompletionKind { + CompletionKind.list(allCases.map { $0.rawValue }) + } +} \ No newline at end of file diff --git a/Sources/json-csv/Csv.swift b/Sources/json-csv/Csv.swift new file mode 100644 index 0000000..df65f20 --- /dev/null +++ b/Sources/json-csv/Csv.swift @@ -0,0 +1,155 @@ +// +// Created by Jakob Hain on 12/4/20. +// Copyright (c) 2020 Jakobeha. All rights reserved. +// + +import Foundation + +struct Csv: LosslessStringConvertible { + static let separator: Character = ";" + static let columnOrder: [String] = [ + "name", + "description", + "view", + "cost", + "type", + "kind", + "terrains", + "radius", + "fireRate", + "maxInaccuracy", + "turnSpeed", + "availableTargetModes", + "projectile", + "numProjectiles", + "angleBetweenProjectiles", + "children", + + "iron", + "gold", + "coal", + "copper", + "aluminum", + "lead", + "uranium", + + "damage", + "dps", + "fractionDamage", + "fractionDps", + "blastRadius", + "explosionView", + "pierce", + "maxTravelDistance", + "speed", + "size", + "width", + "initialWidth", + "widthPerTile", + "effects", + + "damageMultiplier", + "speedMultiplier", + "distancePerSecond", + "duration", + "isDeep" + ] + + static func shouldProcessEntry(header: String) -> Bool { + !header.isEmpty && !header.hasPrefix("#") + } + + var columns: [[String]] + + init(name: String, rowHeaders: [String], columns: [String:[String]]) { + let sortedColumns = columns.sorted { lhs, rhs in + let columnHeaderLhs = lhs.key + let columnHeaderRhs = rhs.key + let columnHeaderComponentsLhs = columnHeaderLhs.split(separator: ".") + let columnHeaderComponentsRhs = columnHeaderRhs.split(separator: ".") + for (columnHeaderComponentLhsSubsequence, columnHeaderComponentRhsSubsequence) in zip(columnHeaderComponentsLhs, columnHeaderComponentsRhs) { + let columnHeaderComponentLhs = String(columnHeaderComponentLhsSubsequence) + let columnHeaderComponentRhs = String(columnHeaderComponentRhsSubsequence) + + let componentComparison: Int + if Csv.columnOrder.contains(columnHeaderComponentLhs) && Csv.columnOrder.contains(columnHeaderComponentRhs) { + componentComparison = Csv.columnOrder.firstIndex(of: columnHeaderComponentLhs)! - Csv.columnOrder.firstIndex(of: columnHeaderComponentRhs)! + } else if Csv.columnOrder.contains(columnHeaderComponentLhs) { + componentComparison = ComparisonResult.orderedAscending.rawValue + } else if Csv.columnOrder.contains(columnHeaderComponentRhs) { + componentComparison = ComparisonResult.orderedDescending.rawValue + } else if Int(columnHeaderLhs) != nil && Int(columnHeaderRhs) != nil { + componentComparison = Int(columnHeaderLhs)! - Int(columnHeaderRhs)! + } else { + componentComparison = columnHeaderLhs.compare(columnHeaderRhs).rawValue + } + + if componentComparison < 0 { + return true + } else if componentComparison > 0 { + return false + } + } + + // We will probably never get to this point + return columnHeaderComponentsLhs.count < columnHeaderComponentsRhs.count + }.map { (columnHeader, column) in + [columnHeader] + column + } + + self.columns = [[name] + rowHeaders] + sortedColumns + } + + init(_ description: String) { + columns = description.split(separator: "\n", omittingEmptySubsequences: false).map { + $0.split(separator: Csv.separator, omittingEmptySubsequences: false).map { $0.trimmingCharacters(in: CharacterSet.whitespaces) } + }.transposed + } + + var name: String? { + if columns.isEmpty || columns[0].isEmpty { + return nil + } else { + return columns[0][0] + } + } + + var headers: ArraySlice { + if columns.isEmpty || columns[0].isEmpty { + return [] + } else { + return columns[0].dropFirst() + } + } + + /// We would use [String:ArraySlice], but for some reason indexing the array slice causes a segfault + var fields: [String:[String]] { + if columns.isEmpty { + return [:] + } else { + return [String:[String]](uniqueKeysWithValues: columns.dropFirst().filter { + !$0.isEmpty && Csv.shouldProcessEntry(header: $0[0]) + }.map { + ($0[0], Array($0.dropFirst())) + }) + } + } + + func fieldsAt(index: Int) -> [String:String] { + fields.mapValues { + if index < $0.count { + return $0[index] + } else { + return "" + } + } + } + + var description: String { + columns.transposed.map { $0.joined(separator: "\(Csv.separator) ") }.joined(separator: "\n") + } + + func write(to url: URL, atomically: Bool) throws { + try description.write(to: url, atomically: atomically, encoding: .utf8) + } +} \ No newline at end of file diff --git a/Sources/json-csv/DecodeCsvError.swift b/Sources/json-csv/DecodeCsvError.swift new file mode 100644 index 0000000..f6b0124 --- /dev/null +++ b/Sources/json-csv/DecodeCsvError.swift @@ -0,0 +1,23 @@ +// +// Created by Jakob Hain on 11/24/20. +// Copyright (c) 2020 Jakobeha. All rights reserved. +// + +import Foundation + +enum DecodeCsvError: Error { + case wrongType(expected: Any.Type, actual: Type) + case wrongTypeMultipleOptions(expecteds: [Type], actual: Type) + case encounteredLiteralUnknown + + var localizedDescription: String { + switch (self) { + case .wrongType(let expected, let actual): + return "wrong type: expected \(expected), got \(actual)" + case .wrongTypeMultipleOptions(let expecteds, let actual): + return "wrong type: expected any of \(expecteds), got \(actual)" + case .encounteredLiteralUnknown: + return "encountered a literal 'unknown' in JSON. Use 'null' instead" + } + } +} \ No newline at end of file diff --git a/Sources/json-csv/DecodeJsonError.swift b/Sources/json-csv/DecodeJsonError.swift new file mode 100644 index 0000000..4f8df27 --- /dev/null +++ b/Sources/json-csv/DecodeJsonError.swift @@ -0,0 +1,20 @@ +// +// Created by Jakob Hain on 11/24/20. +// Copyright (c) 2020 Jakobeha. All rights reserved. +// + +import Foundation + +enum DecodeJsonError: Error { + case badColumnHeader(String) + case missingSubArrayIndex(Int) + + var localizedDescription: String { + switch (self) { + case .badColumnHeader(let columnHeader): + return "bad column header (must be an upgrade path): \(columnHeader)" + case .missingSubArrayIndex(let index): + return "gaps in sub-array indices, missing: \(index)" + } + } +} \ No newline at end of file diff --git a/Sources/json-csv/Extensions/Array.swift b/Sources/json-csv/Extensions/Array.swift new file mode 100644 index 0000000..42016c4 --- /dev/null +++ b/Sources/json-csv/Extensions/Array.swift @@ -0,0 +1,14 @@ +// +// Created by Jakob Hain on 11/24/20. +// Copyright (c) 2020 Jakobeha. All rights reserved. +// + +import Foundation + +extension Array { + mutating func padTo(minCount: Int, filler: Element) { + while count < minCount { + append(filler) + } + } +} \ No newline at end of file diff --git a/Sources/json-csv/Extensions/ArrayOfCollectionOfHasDefault.swift b/Sources/json-csv/Extensions/ArrayOfCollectionOfHasDefault.swift new file mode 100644 index 0000000..b9c6c92 --- /dev/null +++ b/Sources/json-csv/Extensions/ArrayOfCollectionOfHasDefault.swift @@ -0,0 +1,20 @@ +// +// Created by Jakob Hain on 11/28/20. +// Copyright (c) 2020 Jakobeha. All rights reserved. +// + +import Foundation + +extension Array where Element: Collection, Element.Index == Int, Element.Element: HasDefault { + var transposed: [[Element.Element]] { + let newSize = map { $0.count }.max() ?? 0 + var result = [[Element.Element]](repeating: [Element.Element](repeating: Element.Element.defaultValue, count: count), count: newSize) + for y in 0..(_ transform: (Key) throws -> Key2) rethrows -> [Key2:Value] { + [Key2:Value](uniqueKeysWithValues: try map { (key, value) in (try transform(key), value) }) + } + + func mapToDict(_ transform: ((key: Key, value: Value)) throws -> (key: Key2, value: Value2)) rethrows -> [Key2:Value2] { + [Key2:Value2](uniqueKeysWithValues: try map { keyAndValue in try transform(keyAndValue) }) + } + + mutating func getOrInsert(_ key: Key, getDefault: () throws -> Value) rethrows -> Value { + if (self[key] == nil) { + self[key] = try getDefault() + } + return self[key]! + } +} diff --git a/Sources/json-csv/Extensions/HasDefault.swift b/Sources/json-csv/Extensions/HasDefault.swift new file mode 100644 index 0000000..2fc0e73 --- /dev/null +++ b/Sources/json-csv/Extensions/HasDefault.swift @@ -0,0 +1,16 @@ +// +// Created by Jakob Hain on 11/28/20. +// Copyright (c) 2020 Jakobeha. All rights reserved. +// + +import Foundation + +protocol HasDefault { + static var defaultValue: Self { get } +} + +extension String: HasDefault { + static var defaultValue: Self { + "" + } +} diff --git a/Sources/json-csv/Extensions/JSON.swift b/Sources/json-csv/Extensions/JSON.swift new file mode 100644 index 0000000..fb8df9d --- /dev/null +++ b/Sources/json-csv/Extensions/JSON.swift @@ -0,0 +1,50 @@ +// +// Created by Jakob Hain on 5/12/20. +// Copyright (c) 2020 Jakobeha. All rights reserved. +// + +import SpriteKit + +extension JSON { + var isNull: Bool { type == .null } + + func toString() throws -> String { + try string.orThrow { DecodeCsvError.wrongType(expected: String.self, actual: self.type) } + } + + func toFloat() throws -> Float { + try float.orThrow { DecodeCsvError.wrongType(expected: Float.self, actual: self.type) } + } + + func toDouble() throws -> Double { + try double.orThrow { DecodeCsvError.wrongType(expected: Double.self, actual: self.type)} + } + + func toCgFloat() throws -> CGFloat { + CGFloat(try toFloat()) + } + + func toTimeInterval() throws -> TimeInterval { + TimeInterval(try toDouble()) + } + + func toInt() throws -> Int { + try int.orThrow { DecodeCsvError.wrongType(expected: Int.self, actual: self.type) } + } + + func toUInt8() throws -> UInt8 { + try uInt8.orThrow { DecodeCsvError.wrongType(expected: UInt8.self, actual: self.type) } + } + + func toBoolean() throws -> Bool { + try bool.orThrow { DecodeCsvError.wrongType(expected: Bool.self, actual: self.type) } + } + + func toArray() throws -> [JSON] { + try array.orThrow { DecodeCsvError.wrongType(expected: [Any].self, actual: self.type) } + } + + func toDictionary() throws -> [String:JSON] { + try dictionary.orThrow { DecodeCsvError.wrongType(expected: [String:Any].self, actual: self.type)} + } +} diff --git a/Sources/json-csv/Extensions/Optional.swift b/Sources/json-csv/Extensions/Optional.swift new file mode 100644 index 0000000..4c46d98 --- /dev/null +++ b/Sources/json-csv/Extensions/Optional.swift @@ -0,0 +1,16 @@ +// +// Created by Jakob Hain on 5/12/20. +// Copyright (c) 2020 Jakobeha. All rights reserved. +// + +import Foundation + +extension Optional { + func orThrow(_ getError: () -> Error) throws -> Wrapped { + if let wrapped = self { + return wrapped + } else { + throw getError() + } + } +} diff --git a/Sources/json-csv/Extensions/String.swift b/Sources/json-csv/Extensions/String.swift new file mode 100644 index 0000000..cd29f4f --- /dev/null +++ b/Sources/json-csv/Extensions/String.swift @@ -0,0 +1,90 @@ +// +// Created by Jakob Hain on 5/10/20. +// Copyright (c) 2020 Jakobeha. All rights reserved. +// + +import Foundation + +extension String { + // Copied from https://stackoverflow.com/questions/24318171/using-swift-to-unescape-unicode-characters-ie-u1234?noredirect=1&lq=1 + var unescaped: String { + let mutableString = NSMutableString(string: self) + CFStringTransform(mutableString, nil, "Any-Hex/Java" as NSString, true) + + return mutableString as String + } + + /// Capitalizes the first letter but leaves the rest of the string unchanged + var capitalizedCamelCase: String { + var result = self + let firstCharacterRange = .. "foo bar baz" + var camelCaseToSubSentenceCase: String { + let words = wordsInCamelCase + return words.joined(separator: " ") + } + + /// Example: "fooBarBaz" => "Foo bar baz" + var camelCaseToSentenceCase: String { + var words = wordsInCamelCase + words[0] = words[0].capitalized + return words.joined(separator: " ") + } + + /// Example: "fooBarBaz" => ["foo", "bar", "baz"] + var wordsInCamelCase: [String] { + var words: [String] = [] + + var remaining = self + while let nextSplitIndex = remaining.firstIndex(where: { character in character.isUppercase }) { + // Make the first character lowercase, since the resulting words are lowercase + if !remaining.isEmpty { + let firstCharacterRange = .. String { + String(repeatElement(character, count: newLength - self.count)) + self + } + + func strip(prefix: String) -> Substring? { + starts(with: prefix) ? self[index(startIndex, offsetBy: prefix.count)...] : nil + } + + mutating func modify(range: R, with transformer: (Substring) throws -> C) rethrows where R.Bound == Index, C.Element == Element { + replaceSubrange(range, with: try transformer(self[range])) + } +} diff --git a/Sources/json-csv/Extensions/URL.swift b/Sources/json-csv/Extensions/URL.swift new file mode 100644 index 0000000..ec91df9 --- /dev/null +++ b/Sources/json-csv/Extensions/URL.swift @@ -0,0 +1,29 @@ +// +// Created by Jakob Hain on 11/24/20. +// Copyright (c) 2020 Jakobeha. All rights reserved. +// + +import Foundation +import ArgumentParser + +extension URL { + var baseName: String { + deletingPathExtension().lastPathComponent + } +} + +extension URL: ExpressibleByArgument { + static let workingDirectory: URL = URL(fileURLWithPath: CommandLine.arguments[0]) + + public init?(argument: String) { + self.init(string: argument, relativeTo: URL.workingDirectory) + } + + public var defaultValueDescription: String { + relativePath + } + + public static var defaultCompletionKind: CompletionKind { + CompletionKind.file() + } +} \ No newline at end of file diff --git a/Sources/json-csv/Log.swift b/Sources/json-csv/Log.swift new file mode 100644 index 0000000..670d94d --- /dev/null +++ b/Sources/json-csv/Log.swift @@ -0,0 +1,14 @@ +// +// Created by Jakob Hain on 12/4/20. +// Copyright (c) 2020 Jakobeha. All rights reserved. +// + +import Foundation + +func log(warning: String) { + print("Warning: \(warning)") +} + +func log(error: Error) { + print("Failed: \(error.localizedDescription)", stderr) +} \ No newline at end of file diff --git a/Sources/json-csv/ObjectRow.swift b/Sources/json-csv/ObjectRow.swift new file mode 100644 index 0000000..091b923 --- /dev/null +++ b/Sources/json-csv/ObjectRow.swift @@ -0,0 +1,23 @@ +// +// Created by Jakob Hain on 12/4/20. +// Copyright (c) 2020 Jakobeha. All rights reserved. +// + +import Foundation + +struct ObjectRow { + var id: String + var fields: [String:String] = [:] + + func mergeWith(columns: inout [String:[String]], index: Int) { + for (fieldKey, _) in fields { + if !columns.keys.contains(fieldKey) { + columns[fieldKey] = [String](repeating: "", count: index) + } + } + for columnKey in columns.keys { + let nextColumn = fields[columnKey] ?? "" + columns[columnKey]!.append(nextColumn) + } + } +} \ No newline at end of file diff --git a/Sources/json-csv/SwiftyJSON.swift b/Sources/json-csv/SwiftyJSON.swift new file mode 100644 index 0000000..6e12ea2 --- /dev/null +++ b/Sources/json-csv/SwiftyJSON.swift @@ -0,0 +1,1401 @@ +// SwiftyJSON.swift +// +// Copyright (c) 2014 - 2017 Ruoyu Fu, Pinglin Tang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +// MARK: - Error +// swiftlint:disable line_length +public enum SwiftyJSONError: Int, Swift.Error { + case unsupportedType = 999 + case indexOutOfBounds = 900 + case elementTooDeep = 902 + case wrongType = 901 + case notExist = 500 + case invalidJSON = 490 +} + +extension SwiftyJSONError: CustomNSError { + + /// return the error domain of SwiftyJSONError + public static var errorDomain: String { return "com.swiftyjson.SwiftyJSON" } + + /// return the error code of SwiftyJSONError + public var errorCode: Int { return self.rawValue } + + /// return the userInfo of SwiftyJSONError + public var errorUserInfo: [String: Any] { + switch self { + case .unsupportedType: + return [NSLocalizedDescriptionKey: "It is an unsupported type."] + case .indexOutOfBounds: + return [NSLocalizedDescriptionKey: "Array Index is out of bounds."] + case .wrongType: + return [NSLocalizedDescriptionKey: "Couldn't merge, because the JSONs differ in type on top level."] + case .notExist: + return [NSLocalizedDescriptionKey: "Dictionary key does not exist."] + case .invalidJSON: + return [NSLocalizedDescriptionKey: "JSON is invalid."] + case .elementTooDeep: + return [NSLocalizedDescriptionKey: "Element too deep. Increase maxObjectDepth and make sure there is no reference loop."] + } + } +} + +// MARK: - JSON Type + +/** +JSON's type definitions. + +See http://www.json.org +*/ +public enum Type: Int { + case number + case string + case bool + case array + case dictionary + case null + case unknown +} + +// MARK: - JSON Base + +public struct JSON { + + /** + Creates a JSON using the data. + + - parameter data: The NSData used to convert to json.Top level object in data is an NSArray or NSDictionary + - parameter opt: The JSON serialization reading options. `[]` by default. + + - returns: The created JSON + */ + public init(data: Data, options opt: JSONSerialization.ReadingOptions = []) throws { + let object: Any = try JSONSerialization.jsonObject(with: data, options: opt) + self.init(jsonObject: object) + } + + /** + Creates a JSON object + - note: this does not parse a `String` into JSON, instead use `init(parseJSON: String)` + + - parameter object: the object + + - returns: the created JSON object + */ + public init(_ object: Any) { + switch object { + case let object as Data: + do { + try self.init(data: object) + } catch { + self.init(jsonObject: NSNull()) + } + default: + self.init(jsonObject: object) + } + } + + /** + Parses the JSON string into a JSON object + + - parameter json: the JSON string + + - returns: the created JSON object + */ + public init(parseJSON jsonString: String) { + if let data = jsonString.data(using: .utf8) { + self.init(data) + } else { + self.init(NSNull()) + } + } + + /** + Creates a JSON using the object. + + - parameter jsonObject: The object must have the following properties: All objects are NSString/String, NSNumber/Int/Float/Double/Bool, NSArray/Array, NSDictionary/Dictionary, or NSNull; All dictionary keys are NSStrings/String; NSNumbers are not NaN or infinity. + + - returns: The created JSON + */ + fileprivate init(jsonObject: Any) { + object = jsonObject + } + + /** + Merges another JSON into this JSON, whereas primitive values which are not present in this JSON are getting added, + present values getting overwritten, array values getting appended and nested JSONs getting merged the same way. + + - parameter other: The JSON which gets merged into this JSON + + - throws `ErrorWrongType` if the other JSONs differs in type on the top level. + */ + public mutating func merge(with other: JSON) throws { + try self.merge(with: other, typecheck: true) + } + + /** + Merges another JSON into this JSON and returns a new JSON, whereas primitive values which are not present in this JSON are getting added, + present values getting overwritten, array values getting appended and nested JSONS getting merged the same way. + + - parameter other: The JSON which gets merged into this JSON + + - throws `ErrorWrongType` if the other JSONs differs in type on the top level. + + - returns: New merged JSON + */ + public func merged(with other: JSON) throws -> JSON { + var merged = self + try merged.merge(with: other, typecheck: true) + return merged + } + + /** + Private woker function which does the actual merging + Typecheck is set to true for the first recursion level to prevent total override of the source JSON + */ + fileprivate mutating func merge(with other: JSON, typecheck: Bool) throws { + if type == other.type { + switch type { + case .dictionary: + for (key, _) in other { + try self[key].merge(with: other[key], typecheck: false) + } + case .array: + self = JSON(arrayValue + other.arrayValue) + default: + self = other + } + } else { + if typecheck { + throw SwiftyJSONError.wrongType + } else { + self = other + } + } + } + + /// Private object + fileprivate var rawArray: [Any] = [] + fileprivate var rawDictionary: [String: Any] = [:] + fileprivate var rawString: String = "" + fileprivate var rawNumber: NSNumber = 0 + fileprivate var rawNull: NSNull = NSNull() + fileprivate var rawBool: Bool = false + + /// JSON type, fileprivate setter + public fileprivate(set) var type: Type = .null + + /// Error in JSON, fileprivate setter + public fileprivate(set) var error: SwiftyJSONError? + + /// Object in JSON + public var object: Any { + get { + switch type { + case .array: return rawArray + case .dictionary: return rawDictionary + case .string: return rawString + case .number: return rawNumber + case .bool: return rawBool + default: return rawNull + } + } + set { + error = nil + switch unwrap(newValue) { + case let number as NSNumber: + if number.isBool { + type = .bool + rawBool = number.boolValue + } else { + type = .number + rawNumber = number + } + case let string as String: + type = .string + rawString = string + case _ as NSNull: + type = .null + case nil: + type = .null + case let array as [Any]: + type = .array + rawArray = array + case let dictionary as [String: Any]: + type = .dictionary + rawDictionary = dictionary + default: + type = .unknown + error = SwiftyJSONError.unsupportedType + } + } + } + + /// The static null JSON + @available(*, unavailable, renamed:"null") + public static var nullJSON: JSON { return null } + public static var null: JSON { return JSON(NSNull()) } +} + +/// Private method to unwarp an object recursively +private func unwrap(_ object: Any) -> Any { + switch object { + case let json as JSON: + return unwrap(json.object) + case let array as [Any]: + return array.map(unwrap) + case let dictionary as [String: Any]: + var d = dictionary + dictionary.forEach { pair in + d[pair.key] = unwrap(pair.value) + } + return d + default: + return object + } +} + +public enum Index: Comparable { + case array(Int) + case dictionary(DictionaryIndex) + case null + + static public func == (lhs: Index, rhs: Index) -> Bool { + switch (lhs, rhs) { + case (.array(let left), .array(let right)): return left == right + case (.dictionary(let left), .dictionary(let right)): return left == right + case (.null, .null): return true + default: return false + } + } + + static public func < (lhs: Index, rhs: Index) -> Bool { + switch (lhs, rhs) { + case (.array(let left), .array(let right)): return left < right + case (.dictionary(let left), .dictionary(let right)): return left < right + default: return false + } + } +} + +public typealias JSONIndex = Index +public typealias JSONRawIndex = Index + +extension JSON: Swift.Collection { + + public typealias Index = JSONRawIndex + + public var startIndex: Index { + switch type { + case .array: return .array(rawArray.startIndex) + case .dictionary: return .dictionary(rawDictionary.startIndex) + default: return .null + } + } + + public var endIndex: Index { + switch type { + case .array: return .array(rawArray.endIndex) + case .dictionary: return .dictionary(rawDictionary.endIndex) + default: return .null + } + } + + public func index(after i: Index) -> Index { + switch i { + case .array(let idx): return .array(rawArray.index(after: idx)) + case .dictionary(let idx): return .dictionary(rawDictionary.index(after: idx)) + default: return .null + } + } + + public subscript (position: Index) -> (String, JSON) { + switch position { + case .array(let idx): return (String(idx), JSON(rawArray[idx])) + case .dictionary(let idx): return (rawDictionary[idx].key, JSON(rawDictionary[idx].value)) + default: return ("", JSON.null) + } + } +} + +// MARK: - Subscript + +/** + * To mark both String and Int can be used in subscript. + */ +public enum JSONKey { + case index(Int) + case key(String) +} + +public protocol JSONSubscriptType { + var jsonKey: JSONKey { get } +} + +extension Int: JSONSubscriptType { + public var jsonKey: JSONKey { + return JSONKey.index(self) + } +} + +extension String: JSONSubscriptType { + public var jsonKey: JSONKey { + return JSONKey.key(self) + } +} + +extension JSON { + + /// If `type` is `.array`, return json whose object is `array[index]`, otherwise return null json with error. + fileprivate subscript(index index: Int) -> JSON { + get { + if type != .array { + var r = JSON.null + r.error = self.error ?? SwiftyJSONError.wrongType + return r + } else if rawArray.indices.contains(index) { + return JSON(rawArray[index]) + } else { + var r = JSON.null + r.error = SwiftyJSONError.indexOutOfBounds + return r + } + } + set { + if type == .array && + rawArray.indices.contains(index) && + newValue.error == nil { + rawArray[index] = newValue.object + } + } + } + + /// If `type` is `.dictionary`, return json whose object is `dictionary[key]` , otherwise return null json with error. + fileprivate subscript(key key: String) -> JSON { + get { + var r = JSON.null + if type == .dictionary { + if let o = rawDictionary[key] { + r = JSON(o) + } else { + r.error = SwiftyJSONError.notExist + } + } else { + r.error = self.error ?? SwiftyJSONError.wrongType + } + return r + } + set { + if type == .dictionary && newValue.error == nil { + rawDictionary[key] = newValue.object + } + } + } + + /// If `sub` is `Int`, return `subscript(index:)`; If `sub` is `String`, return `subscript(key:)`. + fileprivate subscript(sub sub: JSONSubscriptType) -> JSON { + get { + switch sub.jsonKey { + case .index(let index): return self[index: index] + case .key(let key): return self[key: key] + } + } + set { + switch sub.jsonKey { + case .index(let index): self[index: index] = newValue + case .key(let key): self[key: key] = newValue + } + } + } + + /** + Find a json in the complex data structures by using array of Int and/or String as path. + + Example: + + ``` + let json = JSON[data] + let path = [9,"list","person","name"] + let name = json[path] + ``` + + The same as: let name = json[9]["list"]["person"]["name"] + + - parameter path: The target json's path. + + - returns: Return a json found by the path or a null json with error + */ + public subscript(path: [JSONSubscriptType]) -> JSON { + get { + return path.reduce(self) { $0[sub: $1] } + } + set { + switch path.count { + case 0: return + case 1: self[sub:path[0]].object = newValue.object + default: + var aPath = path + aPath.remove(at: 0) + var nextJSON = self[sub: path[0]] + nextJSON[aPath] = newValue + self[sub: path[0]] = nextJSON + } + } + } + + /** + Find a json in the complex data structures by using array of Int and/or String as path. + + - parameter path: The target json's path. Example: + + let name = json[9,"list","person","name"] + + The same as: let name = json[9]["list"]["person"]["name"] + + - returns: Return a json found by the path or a null json with error + */ + public subscript(path: JSONSubscriptType...) -> JSON { + get { + return self[path] + } + set { + self[path] = newValue + } + } +} + +// MARK: - LiteralConvertible + +extension JSON: Swift.ExpressibleByStringLiteral { + + public init(stringLiteral value: StringLiteralType) { + self.init(value) + } + + public init(extendedGraphemeClusterLiteral value: StringLiteralType) { + self.init(value) + } + + public init(unicodeScalarLiteral value: StringLiteralType) { + self.init(value) + } +} + +extension JSON: Swift.ExpressibleByIntegerLiteral { + + public init(integerLiteral value: IntegerLiteralType) { + self.init(value) + } +} + +extension JSON: Swift.ExpressibleByBooleanLiteral { + + public init(booleanLiteral value: BooleanLiteralType) { + self.init(value) + } +} + +extension JSON: Swift.ExpressibleByFloatLiteral { + + public init(floatLiteral value: FloatLiteralType) { + self.init(value) + } +} + +extension JSON: Swift.ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (String, Any)...) { + let dictionary = elements.reduce(into: [String: Any](), { $0[$1.0] = $1.1}) + self.init(dictionary) + } +} + +extension JSON: Swift.ExpressibleByArrayLiteral { + + public init(arrayLiteral elements: Any...) { + self.init(elements) + } +} + +// MARK: - Raw + +extension JSON: Swift.RawRepresentable { + + public init?(rawValue: Any) { + if JSON(rawValue).type == .unknown { + return nil + } else { + self.init(rawValue) + } + } + + public var rawValue: Any { + object + } + + public func rawData(options opt: JSONSerialization.WritingOptions = JSONSerialization.WritingOptions(rawValue: 0)) throws -> Data { + guard JSONSerialization.isValidJSONObject(object) else { + throw SwiftyJSONError.invalidJSON + } + + return try JSONSerialization.data(withJSONObject: object, options: opt) + } + + public func rawString(_ encoding: String.Encoding = .utf8, options opt: JSONSerialization.WritingOptions = .prettyPrinted) -> String? { + do { + return try _rawString(encoding, options: [.jsonSerialization: opt]) + } catch { + print("Could not serialize object to JSON because:", error.localizedDescription) + return nil + } + } + + public func rawString(_ options: [writingOptionsKeys: Any]) -> String? { + let encoding = options[.encoding] as? String.Encoding ?? String.Encoding.utf8 + let maxObjectDepth = options[.maxObjextDepth] as? Int ?? 10 + do { + return try _rawString(encoding, options: options, maxObjectDepth: maxObjectDepth) + } catch { + print("Could not serialize object to JSON because:", error.localizedDescription) + return nil + } + } + + fileprivate func _rawString(_ encoding: String.Encoding = .utf8, options: [writingOptionsKeys: Any], maxObjectDepth: Int = 10) throws -> String? { + guard maxObjectDepth > 0 else { throw SwiftyJSONError.invalidJSON } + switch type { + case .dictionary: + do { + if !(options[.castNilToNSNull] as? Bool ?? false) { + let jsonOption = options[.jsonSerialization] as? JSONSerialization.WritingOptions ?? JSONSerialization.WritingOptions.prettyPrinted + let data = try rawData(options: jsonOption) + return String(data: data, encoding: encoding) + } + + guard let dict = object as? [String: Any?] else { + return nil + } + let body = try dict.keys.map { key throws -> String in + guard let value = dict[key] else { + return "\"\(key)\": null" + } + guard let unwrappedValue = value else { + return "\"\(key)\": null" + } + + let nestedValue = JSON(unwrappedValue) + guard let nestedString = try nestedValue._rawString(encoding, options: options, maxObjectDepth: maxObjectDepth - 1) else { + throw SwiftyJSONError.elementTooDeep + } + if nestedValue.type == .string { + return "\"\(key)\": \"\(nestedString.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\""))\"" + } else { + return "\"\(key)\": \(nestedString)" + } + } + + return "{\(body.joined(separator: ","))}" + } catch _ { + return nil + } + case .array: + do { + if !(options[.castNilToNSNull] as? Bool ?? false) { + let jsonOption = options[.jsonSerialization] as? JSONSerialization.WritingOptions ?? JSONSerialization.WritingOptions.prettyPrinted + let data = try rawData(options: jsonOption) + return String(data: data, encoding: encoding) + } + + guard let array = object as? [Any?] else { + return nil + } + let body = try array.map { value throws -> String in + guard let unwrappedValue = value else { + return "null" + } + + let nestedValue = JSON(unwrappedValue) + guard let nestedString = try nestedValue._rawString(encoding, options: options, maxObjectDepth: maxObjectDepth - 1) else { + throw SwiftyJSONError.invalidJSON + } + if nestedValue.type == .string { + return "\"\(nestedString.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\""))\"" + } else { + return nestedString + } + } + + return "[\(body.joined(separator: ","))]" + } catch _ { + return nil + } + case .string: return rawString + case .number: return rawNumber.stringValue + case .bool: return rawBool.description + case .null: return "null" + default: return nil + } + } +} + +// MARK: - Printable, DebugPrintable + +extension JSON: Swift.CustomStringConvertible, Swift.CustomDebugStringConvertible { + + public var description: String { + return rawString(options: .prettyPrinted) ?? "unknown" + } + + public var debugDescription: String { + return description + } +} + +// MARK: - Array + +extension JSON { + + //Optional [JSON] + public var array: [JSON]? { + return type == .array ? rawArray.map { JSON($0) } : nil + } + + //Non-optional [JSON] + public var arrayValue: [JSON] { + return self.array ?? [] + } + + //Optional [Any] + public var arrayObject: [Any]? { + get { + switch type { + case .array: return rawArray + default: return nil + } + } + set { + self.object = newValue ?? NSNull() + } + } +} + +// MARK: - Dictionary + +extension JSON { + + //Optional [String : JSON] + public var dictionary: [String: JSON]? { + if type == .dictionary { + var d = [String: JSON](minimumCapacity: rawDictionary.count) + rawDictionary.forEach { pair in + d[pair.key] = JSON(pair.value) + } + return d + } else { + return nil + } + } + + //Non-optional [String : JSON] + public var dictionaryValue: [String: JSON] { + return dictionary ?? [:] + } + + //Optional [String : Any] + + public var dictionaryObject: [String: Any]? { + get { + switch type { + case .dictionary: return rawDictionary + default: return nil + } + } + set { + object = newValue ?? NSNull() + } + } +} + +// MARK: - Bool + +extension JSON { // : Swift.Bool + + //Optional bool + public var bool: Bool? { + get { + switch type { + case .bool: return rawBool + default: return nil + } + } + set { + object = newValue ?? NSNull() + } + } + + //Non-optional bool + public var boolValue: Bool { + get { + switch type { + case .bool: return rawBool + case .number: return rawNumber.boolValue + case .string: return ["true", "y", "t", "yes", "1"].contains { rawString.caseInsensitiveCompare($0) == .orderedSame } + default: return false + } + } + set { + object = newValue + } + } +} + +// MARK: - String + +extension JSON { + + //Optional string + public var string: String? { + get { + switch type { + case .string: return object as? String + default: return nil + } + } + set { + object = newValue ?? NSNull() + } + } + + //Non-optional string + public var stringValue: String { + get { + switch type { + case .string: return object as? String ?? "" + case .number: return rawNumber.stringValue + case .bool: return (object as? Bool).map { String($0) } ?? "" + default: return "" + } + } + set { + object = newValue + } + } +} + +// MARK: - Number + +extension JSON { + + //Optional number + public var number: NSNumber? { + get { + switch type { + case .number: return rawNumber + case .bool: return NSNumber(value: rawBool ? 1 : 0) + default: return nil + } + } + set { + object = newValue ?? NSNull() + } + } + + //Non-optional number + public var numberValue: NSNumber { + get { + switch type { + case .string: + let decimal = NSDecimalNumber(string: object as? String) + return decimal == .notANumber ? .zero : decimal + case .number: return object as? NSNumber ?? NSNumber(value: 0) + case .bool: return NSNumber(value: rawBool ? 1 : 0) + default: return NSNumber(value: 0.0) + } + } + set { + object = newValue + } + } +} + +// MARK: - Null + +extension JSON { + + public var null: NSNull? { + set { + object = NSNull() + } + get { + switch type { + case .null: return rawNull + default: return nil + } + } + } + public func exists() -> Bool { + if let errorValue = error, (400...1000).contains(errorValue.errorCode) { + return false + } + return true + } +} + +// MARK: - URL + +extension JSON { + + //Optional URL + public var url: URL? { + get { + switch type { + case .string: + // Check for existing percent escapes first to prevent double-escaping of % character + if rawString.range(of: "%[0-9A-Fa-f]{2}", options: .regularExpression, range: nil, locale: nil) != nil { + return Foundation.URL(string: rawString) + } else if let encodedString_ = rawString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) { + // We have to use `Foundation.URL` otherwise it conflicts with the variable name. + return Foundation.URL(string: encodedString_) + } else { + return nil + } + default: + return nil + } + } + set { + object = newValue?.absoluteString ?? NSNull() + } + } +} + +// MARK: - Int, Double, Float, Int8, Int16, Int32, Int64 + +extension JSON { + + public var double: Double? { + get { + return number?.doubleValue + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var doubleValue: Double { + get { + return numberValue.doubleValue + } + set { + object = NSNumber(value: newValue) + } + } + + public var float: Float? { + get { + return number?.floatValue + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var floatValue: Float { + get { + return numberValue.floatValue + } + set { + object = NSNumber(value: newValue) + } + } + + public var int: Int? { + get { + return number?.intValue + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var intValue: Int { + get { + return numberValue.intValue + } + set { + object = NSNumber(value: newValue) + } + } + + public var uInt: UInt? { + get { + return number?.uintValue + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var uIntValue: UInt { + get { + return numberValue.uintValue + } + set { + object = NSNumber(value: newValue) + } + } + + public var int8: Int8? { + get { + return number?.int8Value + } + set { + if let newValue = newValue { + object = NSNumber(value: Int(newValue)) + } else { + object = NSNull() + } + } + } + + public var int8Value: Int8 { + get { + return numberValue.int8Value + } + set { + object = NSNumber(value: Int(newValue)) + } + } + + public var uInt8: UInt8? { + get { + return number?.uint8Value + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var uInt8Value: UInt8 { + get { + return numberValue.uint8Value + } + set { + object = NSNumber(value: newValue) + } + } + + public var int16: Int16? { + get { + return number?.int16Value + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var int16Value: Int16 { + get { + return numberValue.int16Value + } + set { + object = NSNumber(value: newValue) + } + } + + public var uInt16: UInt16? { + get { + return number?.uint16Value + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var uInt16Value: UInt16 { + get { + return numberValue.uint16Value + } + set { + object = NSNumber(value: newValue) + } + } + + public var int32: Int32? { + get { + return number?.int32Value + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var int32Value: Int32 { + get { + return numberValue.int32Value + } + set { + object = NSNumber(value: newValue) + } + } + + public var uInt32: UInt32? { + get { + return number?.uint32Value + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var uInt32Value: UInt32 { + get { + return numberValue.uint32Value + } + set { + object = NSNumber(value: newValue) + } + } + + public var int64: Int64? { + get { + return number?.int64Value + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var int64Value: Int64 { + get { + return numberValue.int64Value + } + set { + object = NSNumber(value: newValue) + } + } + + public var uInt64: UInt64? { + get { + return number?.uint64Value + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var uInt64Value: UInt64 { + get { + return numberValue.uint64Value + } + set { + object = NSNumber(value: newValue) + } + } +} + +// MARK: - Comparable + +extension JSON: Swift.Comparable {} + +public func == (lhs: JSON, rhs: JSON) -> Bool { + + switch (lhs.type, rhs.type) { + case (.number, .number): return lhs.rawNumber == rhs.rawNumber + case (.string, .string): return lhs.rawString == rhs.rawString + case (.bool, .bool): return lhs.rawBool == rhs.rawBool + case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray + case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary + case (.null, .null): return true + default: return false + } +} + +public func <= (lhs: JSON, rhs: JSON) -> Bool { + + switch (lhs.type, rhs.type) { + case (.number, .number): return lhs.rawNumber <= rhs.rawNumber + case (.string, .string): return lhs.rawString <= rhs.rawString + case (.bool, .bool): return lhs.rawBool == rhs.rawBool + case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray + case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary + case (.null, .null): return true + default: return false + } +} + +public func >= (lhs: JSON, rhs: JSON) -> Bool { + + switch (lhs.type, rhs.type) { + case (.number, .number): return lhs.rawNumber >= rhs.rawNumber + case (.string, .string): return lhs.rawString >= rhs.rawString + case (.bool, .bool): return lhs.rawBool == rhs.rawBool + case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray + case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary + case (.null, .null): return true + default: return false + } +} + +public func > (lhs: JSON, rhs: JSON) -> Bool { + + switch (lhs.type, rhs.type) { + case (.number, .number): return lhs.rawNumber > rhs.rawNumber + case (.string, .string): return lhs.rawString > rhs.rawString + default: return false + } +} + +public func < (lhs: JSON, rhs: JSON) -> Bool { + + switch (lhs.type, rhs.type) { + case (.number, .number): return lhs.rawNumber < rhs.rawNumber + case (.string, .string): return lhs.rawString < rhs.rawString + default: return false + } +} + +private let trueNumber = NSNumber(value: true) +private let falseNumber = NSNumber(value: false) +private let trueObjCType = String(cString: trueNumber.objCType) +private let falseObjCType = String(cString: falseNumber.objCType) + +// MARK: - NSNumber: Comparable + +extension NSNumber { + fileprivate var isBool: Bool { + let objCType = String(cString: self.objCType) + if (self.compare(trueNumber) == .orderedSame && objCType == trueObjCType) || (self.compare(falseNumber) == .orderedSame && objCType == falseObjCType) { + return true + } else { + return false + } + } +} + +func == (lhs: NSNumber, rhs: NSNumber) -> Bool { + switch (lhs.isBool, rhs.isBool) { + case (false, true): return false + case (true, false): return false + default: return lhs.compare(rhs) == .orderedSame + } +} + +func != (lhs: NSNumber, rhs: NSNumber) -> Bool { + return !(lhs == rhs) +} + +func < (lhs: NSNumber, rhs: NSNumber) -> Bool { + + switch (lhs.isBool, rhs.isBool) { + case (false, true): return false + case (true, false): return false + default: return lhs.compare(rhs) == .orderedAscending + } +} + +func > (lhs: NSNumber, rhs: NSNumber) -> Bool { + + switch (lhs.isBool, rhs.isBool) { + case (false, true): return false + case (true, false): return false + default: return lhs.compare(rhs) == ComparisonResult.orderedDescending + } +} + +func <= (lhs: NSNumber, rhs: NSNumber) -> Bool { + + switch (lhs.isBool, rhs.isBool) { + case (false, true): return false + case (true, false): return false + default: return lhs.compare(rhs) != .orderedDescending + } +} + +func >= (lhs: NSNumber, rhs: NSNumber) -> Bool { + + switch (lhs.isBool, rhs.isBool) { + case (false, true): return false + case (true, false): return false + default: return lhs.compare(rhs) != .orderedAscending + } +} + +public enum writingOptionsKeys { + case jsonSerialization + case castNilToNSNull + case maxObjextDepth + case encoding +} + +// MARK: - JSON: Codable +extension JSON: Codable { + private static var codableTypes: [Codable.Type] { + return [ + Bool.self, + Int.self, + Int8.self, + Int16.self, + Int32.self, + Int64.self, + UInt.self, + UInt8.self, + UInt16.self, + UInt32.self, + UInt64.self, + Double.self, + String.self, + [JSON].self, + [String: JSON].self + ] + } + public init(from decoder: Decoder) throws { + var object: Any? + + if let container = try? decoder.singleValueContainer(), !container.decodeNil() { + for type in JSON.codableTypes { + if object != nil { + break + } + // try to decode value + switch type { + case let boolType as Bool.Type: + object = try? container.decode(boolType) + case let intType as Int.Type: + object = try? container.decode(intType) + case let int8Type as Int8.Type: + object = try? container.decode(int8Type) + case let int32Type as Int32.Type: + object = try? container.decode(int32Type) + case let int64Type as Int64.Type: + object = try? container.decode(int64Type) + case let uintType as UInt.Type: + object = try? container.decode(uintType) + case let uint8Type as UInt8.Type: + object = try? container.decode(uint8Type) + case let uint16Type as UInt16.Type: + object = try? container.decode(uint16Type) + case let uint32Type as UInt32.Type: + object = try? container.decode(uint32Type) + case let uint64Type as UInt64.Type: + object = try? container.decode(uint64Type) + case let doubleType as Double.Type: + object = try? container.decode(doubleType) + case let stringType as String.Type: + object = try? container.decode(stringType) + case let jsonValueArrayType as [JSON].Type: + object = try? container.decode(jsonValueArrayType) + case let jsonValueDictType as [String: JSON].Type: + object = try? container.decode(jsonValueDictType) + default: + break + } + } + } + self.init(object ?? NSNull()) + } + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + if object is NSNull { + try container.encodeNil() + return + } + switch object { + case let intValue as Int: + try container.encode(intValue) + case let int8Value as Int8: + try container.encode(int8Value) + case let int32Value as Int32: + try container.encode(int32Value) + case let int64Value as Int64: + try container.encode(int64Value) + case let uintValue as UInt: + try container.encode(uintValue) + case let uint8Value as UInt8: + try container.encode(uint8Value) + case let uint16Value as UInt16: + try container.encode(uint16Value) + case let uint32Value as UInt32: + try container.encode(uint32Value) + case let uint64Value as UInt64: + try container.encode(uint64Value) + case let doubleValue as Double: + try container.encode(doubleValue) + case let boolValue as Bool: + try container.encode(boolValue) + case let stringValue as String: + try container.encode(stringValue) + case is [Any]: + let jsonValueArray = array ?? [] + try container.encode(jsonValueArray) + case is [String: Any]: + let jsonValueDictValue = dictionary ?? [:] + try container.encode(jsonValueDictValue) + default: + break + } + } +} diff --git a/Sources/json-csv/main.swift b/Sources/json-csv/main.swift new file mode 100644 index 0000000..a6a7661 --- /dev/null +++ b/Sources/json-csv/main.swift @@ -0,0 +1,449 @@ +// +// main.swift +// json-csv +// +// Created by Jakob Hain on 11/24/20. +// Copyright © 2020 Jakobeha. All rights reserved. +// + +import ArgumentParser +import Foundation + +struct JsonCsv: ParsableCommand { + static let configuration = CommandConfiguration( + abstract: "convert CSV from / to JSON", + version: "0.0.1", + subcommands: [JsonToCsvs.self, JsonToCsv.self, CsvsToJson.self, CsvsToJsonSplice.self, CsvToJsonSplice.self, CsvToJson.self] + ) + + struct JsonToCsvs: ParsableCommand { + static let configuration = CommandConfiguration(abstract: "convert a JSON to CSVs") + + @Option(name: .shortAndLong, help: "column format") + var columnFormat: ColumnFormat = .standard + + @Argument(help: "input JSON") + var source: URL + + @Argument(help: "output directory of CSVs") + var destination: URL + + mutating func run() { + do { + let json = try JSON(data: try! Data(contentsOf: source)) + let csvs = try JsonToCsvs.convert(json: json, columnFormat: columnFormat) + for csv in csvs { + // CSV that we create will always have a name + let csvDestination = destination.appendingPathComponent(csv.name!, isDirectory: false).appendingPathExtension("csv") + try csv.write(to: csvDestination, atomically: true) + } + } catch { + log(error: error) + } + } + + private static func convert(json: JSON, columnFormat: ColumnFormat) throws -> [Csv] { + try json.toDictionary().map { (key, elementJson) in + try JsonToCsv.convertObject(key: key, json: elementJson, columnFormat: columnFormat) + } + } + + } + + struct JsonToCsv: ParsableCommand { + static let configuration = CommandConfiguration(abstract: "convert a JSON to a CSV") + + @Option(name: .shortAndLong, help: "column format") + var columnFormat: ColumnFormat = .standard + + @Argument(help: "input JSON") + var source: URL + + @Argument(help: "output CSV") + var destination: URL + + mutating func run() { + do { + let json = try JSON(data: try! Data(contentsOf: source)) + let csv = try JsonToCsv.convertObject(key: destination.baseName, json: json, columnFormat: columnFormat) + try csv.write(to: destination, atomically: true) + } catch { + log(error: error) + } + } + + static func convertObject(key: String, json: JSON, columnFormat: ColumnFormat) throws -> Csv { + let rows: [ObjectRow] = try { + switch (columnFormat) { + case .standard: + return try convertDictOrArrayToDict(json: json).map { (id, objectJson) in + try convertObjectRow(id: id, json: objectJson) + } + case .array: + return try json.toArray().enumerated().flatMap { (index2, json2) in + try json2.toArray().enumerated().flatMap { (index1, json1) in + try json1.toArray().enumerated().map { (index0, objectJson) in + let id = "\(index0)-\(index1)-\(index2)" + return try convertObjectRow(id: id, json: objectJson) + } as [ObjectRow] + } + } + } + }() + + let name = key.capitalizedCamelCase + let rowHeaders = rows.map { $0.id } + var columns: [String:[String]] = [:] + for (index, row) in rows.enumerated() { + row.mergeWith(columns: &columns, index: index) + } + + return Csv(name: name, rowHeaders: rowHeaders, columns: columns) + } + + private static func convertObjectRow(id: String, json: JSON) throws -> ObjectRow { + var objectRow = ObjectRow(id: id) + try addTo(objectRow: &objectRow, prefix: nil, fieldJsons: try convertDictOrArrayToDict(json: json)) + return objectRow + } + + private static func addTo(objectRow: inout ObjectRow, prefix: String?, fieldJsons: [String:JSON]) throws { + for (fieldName, fieldJson) in fieldJsons { + let fullFieldName = prefix.map { "\($0).\(fieldName)" } ?? fieldName + switch fieldJson.type { + case .number: + objectRow.fields[fullFieldName] = fieldJson.number!.description + case .string: + objectRow.fields[fullFieldName] = fieldJson.string! + case .bool: + // In the CSV format, booleans are in uppercase + // This is how LibreOffice represents them + objectRow.fields[fullFieldName] = fieldJson.bool!.description.uppercased() + case .array: + let fieldJsonArray = fieldJson.array! + if fieldJsonArray.allSatisfy({ $0.type == .string && !$0.string!.contains(",") }) { + let fieldJsonArrayAsString = fieldJsonArray.map { $0.string! }.joined(separator: ", ") + objectRow.fields[fullFieldName] = fieldJsonArrayAsString + } else { + let fieldJsonArrayAsDictionary = [String: JSON](uniqueKeysWithValues: fieldJson.array!.enumerated().map { + ($0.offset.description, $0.element) + }) + try addTo(objectRow: &objectRow, prefix: fullFieldName, fieldJsons: fieldJsonArrayAsDictionary) + } + case .dictionary: + let fieldJsonDictionary = fieldJson.dictionary! + if fieldJsonDictionary.isEmpty { + objectRow.fields[fullFieldName] = "{}" + } else { + try addTo(objectRow: &objectRow, prefix: fullFieldName, fieldJsons: fieldJsonDictionary) + } + case .null: + objectRow.fields[fullFieldName] = "NULL" + case .unknown: + throw DecodeCsvError.encounteredLiteralUnknown + } + } + } + + private static func convertDictOrArrayToDict(json: JSON) throws -> [String:JSON] { + switch (json.type) { + case .array: + return [String:JSON](uniqueKeysWithValues: json.array!.enumerated().map { (index, objectJson) in + let id = index.description + return (id, objectJson) + }) + case .dictionary: + return json.dictionary! + default: + throw DecodeCsvError.wrongTypeMultipleOptions(expecteds: [.array, .dictionary], actual: json.type) + } + } + } + + struct CsvsToJson: ParsableCommand { + static let configuration = CommandConfiguration(abstract: "convert CSVs in a folder to a JSON") + + @Option(name: .shortAndLong, help: "column format") + var columnFormat: ColumnFormat = .standard + + @Argument(help: "input directory of CSVs") + var source: URL + + @Argument(help: "output JSON") + var destination: URL + + mutating func run() { + do { + let json = try CsvsToJson.convert(source: source, columnFormat: columnFormat) + try json.rawData(options: [.prettyPrinted]).write(to: destination, options: [.atomic]) + } catch { + log(error: error) + } + } + + static func convert(source: URL, columnFormat: ColumnFormat) throws -> JSON { + let sourceCsvs = try FileManager.default.contentsOfDirectory(at: source, includingPropertiesForKeys: []).filter { + $0.pathExtension == "csv" + } + + let jsonDict = Dictionary(uniqueKeysWithValues: try sourceCsvs.map { sourceCsv in + let csvName = sourceCsv.baseName.decapitalizedCamelCase + let csvData = try CsvToJson.convert(source: sourceCsv, columnFormat: columnFormat) + return (csvName, csvData) + }) + return JSON(jsonDict) + } + } + + struct CsvsToJsonSplice: ParsableCommand { + static let configuration = CommandConfiguration(abstract: "convert a folder of CSVs into a JSON, and insert as a top-level child of another JSON. Requires jq (https://stedolan.github.io/jq/)") + + @Option(name: .shortAndLong, help: "column format") + var columnFormat: ColumnFormat = .standard + + @Argument(help: "input folder of CSVs") + var source: URL + + @Argument(help: "JSON to insert splice") + var parentDestination: URL + + @Argument(help: "key in JSON to replace with output") + var spliceName: String + + mutating func run() { + do { + let spliceJson = try CsvsToJson.convert(source: source, columnFormat: columnFormat) + + try CsvToJsonSplice.splice(name: spliceName, json: spliceJson, into: parentDestination) + } catch { + log(error: error) + } + } + } + + struct CsvToJsonSplice: ParsableCommand { + static let configuration = CommandConfiguration(abstract: "convert a single CSV into a JSON, and insert as a top-level child of another JSON. Requires jq (https://stedolan.github.io/jq/)") + + private static let jqPath: String = "/usr/local/bin/jq" + + @Option(name: .shortAndLong, help: "column format") + var columnFormat: ColumnFormat = .standard + + @Argument(help: "input CSV") + var source: URL + + @Argument(help: "JSON to insert splice") + var parentDestination: URL + + @Argument(help: "key in JSON to replace with output") + var spliceName: String + + mutating func run() { + do { + let spliceJson = try CsvToJson.convert(source: source, columnFormat: columnFormat) + try CsvToJsonSplice.splice(name: spliceName, json: spliceJson, into: parentDestination) + } catch { + log(error: error) + } + } + + static func splice(name spliceName: String, json spliceJson: JSON, into parentDestination: URL) throws { + let parentInputHandle = try FileHandle(forReadingFrom: parentDestination) + let outputPipe = Pipe() + + let spliceCommand = Process() + if #available(macOS 10.13, *) { + spliceCommand.executableURL = URL(fileURLWithPath: jqPath) + } else { + spliceCommand.launchPath = jqPath + } + spliceCommand.arguments = [".\(spliceName) = \(spliceJson)"] + spliceCommand.standardInput = parentInputHandle + spliceCommand.standardOutput = outputPipe + + if #available(macOS 10.13, *) { + try spliceCommand.run() + } else { + spliceCommand.launch() + } + + let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile() + spliceCommand.waitUntilExit() + + if spliceCommand.terminationStatus != 0 { + print("splicing with jq failed: error code \(spliceCommand.terminationStatus), reason \(spliceCommand.terminationReason)") + } else { + try outputData.write(to: parentDestination, options: [.atomic]) + } + } + } + + struct CsvToJson: ParsableCommand { + static let configuration = CommandConfiguration(abstract: "convert a single CSV to a JSON") + + @Option(name: .shortAndLong, help: "column format") + var columnFormat: ColumnFormat = .standard + + @Argument(help: "input CSV") + var source: URL + + @Argument(help: "output JSON") + var destination: URL + + mutating func run() { + do { + let csv = Csv(try! String(contentsOf: source)) + let json = try! CsvToJson.convert(csv: csv, columnFormat: columnFormat) + try json.rawData(options: [.prettyPrinted]).write(to: destination, options: [.atomic]) + } catch { + log(error: error) + } + } + + static func convert(source: URL, columnFormat: ColumnFormat) throws -> JSON { + let csv = Csv(try! String(contentsOf: source)) + return try convert(csv: csv, columnFormat: columnFormat) + } + + private static func convert(csv: Csv, columnFormat: ColumnFormat) throws -> JSON { + switch (columnFormat) { + case .standard: + var jsonDict: [String:JSON] = [:] + for (columnIndex, header) in csv.headers.enumerated() { + if (Csv.shouldProcessEntry(header: header)) { + let columnFields = csv.fieldsAt(index: columnIndex) + let objectRow = ObjectRow(id: header, fields: columnFields) + + jsonDict[header] = try convert(objectRow: objectRow) + } + } + + return try convertToDictOrArray(jsonDict: jsonDict) + case .array: + var jsonArray: [[[JSON]]] = [[[]]] + for (columnIndex, header) in csv.headers.enumerated() { + if (Csv.shouldProcessEntry(header: header)) { + let headerComponents = header.split(separator: "-") + if headerComponents.count != 3 || headerComponents.contains(where: { + Int($0) == nil + }) { + throw DecodeJsonError.badColumnHeader(header) + } + + let columnFields = csv.fieldsAt(index: columnIndex) + let objectRow = ObjectRow(id: header, fields: columnFields) + + let index2 = Int(headerComponents[0])! + let index1 = Int(headerComponents[1])! + let index0 = Int(headerComponents[2])! + + jsonArray.padTo(minCount: index0 + 1, filler: []) + jsonArray[index0].padTo(minCount: index1 + 1, filler: []) + jsonArray[index0][index1].padTo(minCount: index2 + 1, filler: JSON.null) + + jsonArray[index0][index1][index2] = try convert(objectRow: objectRow) + } + } + + return JSON(jsonArray) + } + } + + private static func convert(objectRow: ObjectRow) throws -> JSON { + try convert(objectRowFields: objectRow.fields) + } + + private static func convert(objectRowFields: [String:String]) throws -> JSON { + var jsonDict: [String:JSON] = [:] + + let groupedFields = Dictionary.Element]>(grouping: objectRowFields) { (fieldPair: Dictionary.Element) -> String in + let fullFieldName = fieldPair.key as String + if fullFieldName.contains(".") { + return String(fullFieldName.split(separator: ".").first!) + } else { + return "" + } + }.mapValues { + Dictionary($0) { lhs, rhs in + log(warning: "Duplicate fields, their values are: \(lhs) and \(rhs)") + return "\(lhs), \(rhs)" + } + } + + for (fieldPrefix, subFields) in groupedFields { + let areFieldsTopLevel = fieldPrefix.isEmpty + if areFieldsTopLevel { + for (fieldName, field) in subFields { + if !field.isEmpty { + let fieldJson = try convert(datum: field) + jsonDict[fieldName] = fieldJson + } + } + } else { + if subFields.values.contains(where: { !$0.isEmpty }) { + let childFields = subFields.mapKeys { fieldName in + fieldName.split(separator: ".").dropFirst().joined(separator: ".") + } + let childJson = try convert(objectRowFields: childFields) + jsonDict[fieldPrefix] = childJson + } + } + } + + return try convertToDictOrArray(jsonDict: jsonDict) + } + + private static func convert(datum: String) throws -> JSON { + if datum == "NULL" { + return JSON.null + } else if datum == "{}" { + return JSON([:]) + } else if datum == "TRUE" { + return JSON(true) + } else if datum == "FALSE" { + return JSON(false) + } else if Int(datum) != nil && !datum.starts(with: "0x") { + return JSON(Int(datum)!) + } else if Double(datum) != nil && !datum.starts(with: "0x") { + // Conversion to float causes a lot of floating-point errors for some reason, + // conversion to double causes none as of writing this + return JSON(Double(datum)!) + } else if datum.hasPrefix("\"") && datum.hasSuffix("\"") { + return JSON(String(datum.dropFirst().dropLast(1)).unescaped) + } else if datum.hasPrefix("[") && datum.hasSuffix("]") { + return JSON(datum.dropFirst().dropLast(1).split(separator: ",", omittingEmptySubsequences: false).map { + $0.trimmingCharacters(in: .whitespaces) + }) + } else if datum.contains(", ") && !datum.replacingOccurrences(of: ", ", with: "").contains(" ") { + return JSON(datum.split(separator: ",", omittingEmptySubsequences: false).map { + $0.trimmingCharacters(in: .whitespaces) + }) + } else { + return JSON(datum) + } + } + + /// Will convert to an array if the keys are literal integers + private static func convertToDictOrArray(jsonDict: [String:JSON]) throws -> JSON { + if jsonDict.keys.allSatisfy({ Int($0) != nil }) { + let jsonDictWithIntKeys = jsonDict.mapKeys { Int($0)! } + + let jsonArrayCount = jsonDictWithIntKeys.count + var jsonArray = [JSON](repeating: JSON.null, count: jsonArrayCount) + for index in 0../dev/null 2>&1 && pwd)" +cd "$LOCAL_DIR" || exit 1 +EXAMPLES_DIR="$LOCAL_DIR/Examples" + +swift build +# shellcheck disable=SC2181 +if [[ $? -ne 0 ]]; then + echo "Failed to build" + exit 1 +fi + +echo "Standard test" +swift run json-csv json-to-csv "$EXAMPLES_DIR/statusEffects.json" "$EXAMPLES_DIR/statusEffects.csv" +swift run json-csv csv-to-json "$EXAMPLES_DIR/statusEffects.csv" "$EXAMPLES_DIR/statusEffects-roundabout.json" +diff <(jq -S . "$EXAMPLES_DIR/statusEffects.json") <(jq -S . "$EXAMPLES_DIR/statusEffects-roundabout.json") + +echo "Standard test 2" +swift run json-csv json-to-csv --column-format standard "$EXAMPLES_DIR/rounds.json" "$EXAMPLES_DIR/rounds.csv" +swift run json-csv csv-to-json --column-format standard "$EXAMPLES_DIR/rounds.csv" "$EXAMPLES_DIR/rounds-roundabout.json" +diff <(jq -S . "$EXAMPLES_DIR/rounds.json") <(jq -S . "$EXAMPLES_DIR/rounds-roundabout.json") + +echo "Array test" +swift run json-csv json-to-csvs --column-format array "$EXAMPLES_DIR/buildings.json" "$EXAMPLES_DIR/buildings" +swift run json-csv csvs-to-json --column-format array "$EXAMPLES_DIR/buildings" "$EXAMPLES_DIR/buildings-roundabout.json" +diff <(jq -S . "$EXAMPLES_DIR/buildings.json") <(jq -S . "$EXAMPLES_DIR/buildings-roundabout.json") + +echo "Tests passed iff there is no diff"