360 lines
20 KiB
JSON
360 lines
20 KiB
JSON
{
|
||
"_comment": "COMBAT — all combat tuning, one file. Two contexts share the numbers: the standalone range (combat.html / the dev server's /combat — the WASD arena, js/combat/CombatScene.js) and the MAIN GAME (index.html?combat — js/combat/CombatSystem.js drops the pack into the normal game: click-to-fly, the deck swaps for the combat bar, the ship auto-fires at the nearest hostile). Pure rules (armor damage, orbit steering) are js/combat/Combat.js (tested: dev/combat.test.mjs).",
|
||
"enabled": true,
|
||
|
||
"game": {
|
||
"_comment": "COMBAT IN THE MAIN GAME (the ?combat boot). spawnCount = hostiles that appear around the ship when the player starts. SPAWNING: hostiles materialize OFF-SCREEN — the furthest of the (spawnRadius band, the camera's visible edge + spawnLead) — and FLY IN on their own chase AI (standoff ring, then lances): nothing pops into view mid-fight. spawnRadius px = the minimum materialization band around the ship; spawnLead px = how far PAST the camera's visible edge they materialize (the fly-in distance — bigger = a longer stream across the screen). autoFire = the ship's guns — while a hostile is within range the ship fires at the NEAREST one (the mouse steers, the cannon tracks; the cannon's own numbers are player.weapon below). ON-SCREEN RULE (js/combat/CombatSystem.js): a hostile must be VISUALLY ON SCREEN (at least partly in the camera's view) to fire or be fired upon — off-screen craft hold their lances, the cannon won't engage them, and their tracers pass straight through; they join the fight the moment they fly into view.",
|
||
"spawnCount": 3,
|
||
"spawnRadius": 340,
|
||
"spawnLead": 140,
|
||
"autoFire": {
|
||
"enabled": true,
|
||
"range": 800
|
||
}
|
||
},
|
||
|
||
"energy": {
|
||
"_comment": "The player's ENERGY — the skill resource (the combat bar's right ORB — the main game's and the range's). The eight skills (keys 1-8, the bar's middle seats) will spend it; for now it just sits full. regen = energy/s recovered (0 = static).",
|
||
"max": 100,
|
||
"regen": 0
|
||
},
|
||
|
||
"player": {
|
||
"_comment": "The combat feel of YOUR ship in the range (the art, hull stats, and ship name still come from data/ship.json). WASD/arrows fly, SPACE/J fires (hold = auto-fire). The flight feel (js/combat/Combat.js → handleFeel, tested): thrust px/s^2 at full throttle, maxSpeed px/s cap, drag 1/s (exponential coast decay — the range OVERRIDES ship.drag with this, so data/combat.json is the one tuning surface), brake = the S/Down reverse-throttle as a fraction of FULL thrust (instant — a responsive stop). rotSpeed rad/s = the SUSTAINED keyboard turn rate; rotApproach 1/s = how fast the turn rate builds (a tap is a nudge, a hold is a full spin); thrustRamp 1/s = how fast the throttle reaches full on W; thrustDecay 1/s = how fast the throttle cuts when W lets go (no lingering kick). weapon.* = the player's cannon: cooldown s between shots, bulletSpeed px/s, damage per hit, spread rad of aim jitter, bulletLife s before a shot fizzles, color/size the tracer's glow; boltLength/boltWidth px + muzzle px are the laser's VISUALS (the bolt's length × thickness and the muzzle flash's size — js/combat/Bullet.js + Fx.muzzle).",
|
||
"thrust": 2000,
|
||
"maxSpeed": 560,
|
||
"drag": 1.8,
|
||
"rotSpeed": 6.4,
|
||
"rotApproach": 20,
|
||
"thrustRamp": 18,
|
||
"thrustDecay": 30,
|
||
"brake": 0.9,
|
||
"weapon": {
|
||
"key": "space",
|
||
"cooldown": 0.16,
|
||
"bulletSpeed": 680,
|
||
"damage": 10,
|
||
"spread": 0.03,
|
||
"bulletLife": 1.15,
|
||
"color": "#00e5ff",
|
||
"size": 16,
|
||
"boltLength": 46,
|
||
"boltWidth": 10,
|
||
"muzzle": 46
|
||
}
|
||
},
|
||
|
||
"enemy": {
|
||
"_comment": "The hostile craft. texture = spritesheet of frameWidth×frameHeight frames (frame picks which one — frame 0 = the red triple-boost fighter, art facing north, filling its frame; the sheet is 10×10 of 128px frames). size = hull length in world px (render = size × the art's footprint), hitRadius = the collision circle in world px (bullets + rams). count = hostiles in the arena at once. speed px/s + turn (1/s velocity-approach rate) = the chase feel. orbitRadius px = the standoff ring they keep around the player; orbitSpeed rad/s = how fast they circle it (direction alternates per craft). hull/shields = their armor (shields absorb first — js/combat/Combat.js). xp = the EXP a kill of one is worth (the XP bank's currency — js/combat/XpManager.js; the curve is data/progression.json, and the base value is 10, so five kills of the base craft = level 1). weapon.* = their lance: range px, cooldown s, bulletSpeed px/s, damage, spread rad, bulletLife s; boltLength/boltWidth px + muzzle px are the lance's VISUALS (the bolt's length × thickness and the muzzle flash's size — js/combat/Bullet.js + Fx.muzzle). ramDamage = hull damage a COLLISION deals you; ramDamageSelf = what the collision deals THEM (per hit, ramGrace s apart). respawn = the pack reforms (respawnDelay s) after a kill, so the range stays stocked while you tune.",
|
||
"texture": "assets/images/ships-enemy.png",
|
||
"frameWidth": 128,
|
||
"frameHeight": 128,
|
||
"frame": 0,
|
||
"artFacing": "north",
|
||
"size": 54,
|
||
"hitRadius": 20,
|
||
"count": 3,
|
||
"speed": 165,
|
||
"turn": 3.2,
|
||
"orbitRadius": 300,
|
||
"orbitSpeed": 0.6,
|
||
"hull": 60,
|
||
"shields": 0,
|
||
"xp": 10,
|
||
"weapon": {
|
||
"range": 560,
|
||
"cooldown": 1.4,
|
||
"bulletSpeed": 330,
|
||
"damage": 8,
|
||
"spread": 0.08,
|
||
"bulletLife": 2.4,
|
||
"color": "#ff2d6f",
|
||
"size": 18,
|
||
"boltLength": 54,
|
||
"boltWidth": 12,
|
||
"muzzle": 50
|
||
},
|
||
"ramDamage": 10,
|
||
"ramDamageSelf": 22,
|
||
"ramGrace": 0.8,
|
||
"respawn": true,
|
||
"respawnDelay": 2.5
|
||
},
|
||
|
||
"enemyTypes": {
|
||
"_comment": "The hostile roster a WAVE (combat.waves.scripts) can mix — js/combat/EnemyTypes.js deep-merges a type's overrides onto the base `enemy` above, so a type declares only what differs. `raider` is the base craft under a named id (frame 0). Frames 1–7 are the SEVEN new hostiles, each a distinct craft with a distinct WEAPON PATTERN (weapon.pattern) and a MODERATE difficulty step-up: hull/shields (armor to soak) climb monotonically bomber → warden, so each is a little harder to defeat than the last. PATTERNS (js/combat/Weapons.js): 'lance' (the default — one aimed bolt, the base behavior), 'spray' (a FAN of `bolts` spread over `fan` rad), 'burst' (a TIGHT `bolts`-shot stinger — staggered speeds so it fans over distance), 'rapid' (single bolts on a short cooldown — a machine gun), 'ring' (`bolts` fired RADIAL around the craft — break the orbit), 'missile' (`missiles` HEAT-SEEKING shots that home with a CAPPED turn-rate (missileTurn rad/s) so the player can out-turn them), 'combo' (a heavy lance + a missile, the elite). Add a craft here (a new `frame` or its own `texture` + stat overrides) and name it in a wave's roster; EnemyTypes.enemySheetList() is the preload seam for its textures. Each type's `xp` = what a kill of it is worth (the rule of thumb is (hull+shields)/5, rounded, so a tougher craft is worth more of the XP bank).",
|
||
"raider": {
|
||
"frame": 0
|
||
},
|
||
"bomber": {
|
||
"_comment": "frame 1 — the LONG LANCE: slow, sits back, one heavy bolt on a long fuse. The easy end of the new ramp.",
|
||
"frame": 1,
|
||
"speed": 105,
|
||
"turn": 2.2,
|
||
"hull": 80,
|
||
"shields": 10,
|
||
"xp": 18,
|
||
"weapon": {
|
||
"pattern": "lance",
|
||
"range": 760,
|
||
"cooldown": 2.0,
|
||
"bulletSpeed": 250,
|
||
"damage": 10,
|
||
"spread": 0.04,
|
||
"bulletLife": 3.0
|
||
}
|
||
},
|
||
"spitter": {
|
||
"_comment": "frame 2 — the SPRAY: a fan of cheap bolts covering an arc; low damage per bolt, wide cone.",
|
||
"frame": 2,
|
||
"speed": 130,
|
||
"turn": 3.0,
|
||
"hull": 90,
|
||
"shields": 20,
|
||
"xp": 22,
|
||
"weapon": {
|
||
"pattern": "spray",
|
||
"range": 480,
|
||
"cooldown": 1.9,
|
||
"bulletSpeed": 300,
|
||
"damage": 11,
|
||
"spread": 0.03,
|
||
"bulletLife": 2.2,
|
||
"bolts": 5,
|
||
"fan": 1.1,
|
||
"color": "#ff7a3d"
|
||
}
|
||
},
|
||
"stinger": {
|
||
"_comment": "frame 3 — the BURST: a fast, tight 3-shot stinger at close range (staggered speeds fan it out over distance).",
|
||
"frame": 3,
|
||
"speed": 185,
|
||
"turn": 4.0,
|
||
"hull": 100,
|
||
"shields": 25,
|
||
"xp": 25,
|
||
"weapon": {
|
||
"pattern": "burst",
|
||
"range": 400,
|
||
"cooldown": 1.5,
|
||
"bulletSpeed": 420,
|
||
"damage": 12,
|
||
"spread": 0.02,
|
||
"bulletLife": 1.8,
|
||
"bolts": 3,
|
||
"burstFan": 0.10,
|
||
"color": "#ffe14d"
|
||
}
|
||
},
|
||
"hunter": {
|
||
"_comment": "frame 4 — the HUNTER: launches a HEAT-SEEKING missile that homes with a capped turn-rate (missileTurn) — dodgeable by out-turning it.",
|
||
"frame": 4,
|
||
"speed": 150,
|
||
"turn": 3.2,
|
||
"hull": 110,
|
||
"shields": 30,
|
||
"xp": 28,
|
||
"weapon": {
|
||
"pattern": "missile",
|
||
"range": 620,
|
||
"cooldown": 2.4,
|
||
"missiles": 1,
|
||
"missileSpeed": 190,
|
||
"missileTurn": 2.2,
|
||
"missileDamage": 13,
|
||
"missileLife": 4.0,
|
||
"missileColor": "#ff5d2d",
|
||
"missileSize": 18,
|
||
"color": "#ff5d2d"
|
||
}
|
||
},
|
||
"gunner": {
|
||
"_comment": "frame 5 — the GUNNER: rapid single bolts on a short cooldown (a machine gun); solid per-bolt punch at a high rate.",
|
||
"frame": 5,
|
||
"speed": 190,
|
||
"turn": 4.2,
|
||
"hull": 120,
|
||
"shields": 40,
|
||
"xp": 32,
|
||
"weapon": {
|
||
"pattern": "rapid",
|
||
"range": 440,
|
||
"cooldown": 0.5,
|
||
"bulletSpeed": 380,
|
||
"damage": 14,
|
||
"spread": 0.06,
|
||
"bulletLife": 1.6,
|
||
"color": "#c8ff3d"
|
||
}
|
||
},
|
||
"barrage": {
|
||
"_comment": "frame 6 — the BARRAGE: a RADIAL ring of bolts (break the orbit); slow and armored, pokes a hole in the ring.",
|
||
"frame": 6,
|
||
"speed": 100,
|
||
"turn": 2.0,
|
||
"hull": 135,
|
||
"shields": 50,
|
||
"xp": 37,
|
||
"weapon": {
|
||
"pattern": "ring",
|
||
"range": 360,
|
||
"cooldown": 2.2,
|
||
"bulletSpeed": 260,
|
||
"damage": 15,
|
||
"spread": 0.0,
|
||
"bulletLife": 2.6,
|
||
"bolts": 8,
|
||
"color": "#b44dff"
|
||
}
|
||
},
|
||
"warden": {
|
||
"_comment": "frame 7 — the WARDEN (elite): a heavy lance PLUS a homing missile on every volley; the hardest of the seven.",
|
||
"frame": 7,
|
||
"speed": 90,
|
||
"turn": 1.8,
|
||
"hull": 150,
|
||
"shields": 60,
|
||
"xp": 42,
|
||
"weapon": {
|
||
"pattern": "combo",
|
||
"range": 700,
|
||
"cooldown": 2.6,
|
||
"bulletSpeed": 240,
|
||
"damage": 18,
|
||
"spread": 0.02,
|
||
"bulletLife": 3.2,
|
||
"size": 20,
|
||
"boltLength": 64,
|
||
"boltWidth": 14,
|
||
"missiles": 1,
|
||
"missileSpeed": 170,
|
||
"missileTurn": 2.4,
|
||
"missileDamage": 20,
|
||
"missileLife": 4.2,
|
||
"missileColor": "#ff2d6f",
|
||
"missileSize": 20,
|
||
"color": "#ff2d6f"
|
||
}
|
||
}
|
||
},
|
||
|
||
"waves": {
|
||
"_comment": "WAVE SCRIPTS — the quest-step combat (js/combat/Waves.js + CombatSystem.startWaves; the quest hook that FIRES one lands later, random encounters too — for now the console drives it: window.__ORBIT_COMBAT__.waves). A script is an ordered list of waves. KINDS: 'rolling' = maxConcurrent on screen at a time, each kill sending in replacePerKill more until the force is spent; 'fixed' = the whole force at once, no replacements until exhausted. COMPOSITION: `roster` = enemyTypes id → weight (the mix); `total` = the units deployed (default = the weight sum); `type` + `count` is the single-type shorthand. lead = s before wave 1; gap = s between waves (per-wave `gap` s overrides). The quests reference scripts BY NAME (e.g. {\"combat\": \"raid\"}) when the hook lands.",
|
||
"lead": 1.5,
|
||
"gap": 2.0,
|
||
"defaults": {
|
||
"maxConcurrent": 3,
|
||
"replacePerKill": 1
|
||
},
|
||
"scripts": {
|
||
"probe": [
|
||
{ "kind": "fixed", "type": "raider", "count": 3 }
|
||
],
|
||
"skirmish": [
|
||
{ "kind": "rolling", "roster": { "raider": 2 }, "total": 8, "maxConcurrent": 3, "replacePerKill": 1 },
|
||
{ "kind": "fixed", "roster": { "raider": 3, "bomber": 1 }, "total": 4 }
|
||
],
|
||
"raid": [
|
||
{ "kind": "fixed", "roster": { "raider": 4, "bomber": 2 }, "total": 6 },
|
||
{ "kind": "rolling", "roster": { "raider": 3, "bomber": 1 }, "total": 12, "maxConcurrent": 4, "replacePerKill": 1 }
|
||
],
|
||
"pirate_ambush": [
|
||
{
|
||
"$comment": "'Pirate Problems' (data/quests.json → pirate_problems) — the quest's combat step ('Defend your ship from pirates'): ONE rolling wave of SIX frame-0 raiders (EnemyTypes: raider = the sheet's frame 0). Fired when the player ENTERS the Neutral Capital's system with the quest live (GameScene._maybeStartPirateAmbush — create() and the grant's start beats); the wave ships materialize OFF-SCREEN around the player, each from a random direction (CombatSystem._spawnWaveEnemy — the same off-screen materialization as the ?combat pack), streaming in on their own chase AI; three at a time (maxConcurrent), each kill sending in a replacement (replacePerKill) until all six are down; the completion marks the 'combatDone' ledger (GameScene._recordCombatDone — the step's monotonic witness, SaveData-captured).",
|
||
"kind": "rolling",
|
||
"type": "raider",
|
||
"count": 6,
|
||
"maxConcurrent": 3,
|
||
"replacePerKill": 1
|
||
}
|
||
],
|
||
"combat_demo": [
|
||
{
|
||
"$comment": "?COMBAT DEMO (the tuning sandbox's boot — GameScene._wantsCombatDemo): a SEQUENCE of rolling waves, ONE per new hostile type in DIFFICULTY ORDER (bomber → warden, frames 1–7). Each wave is SIX of that single type, THREE on screen at a time (maxConcurrent), each kill sending in a replacement (replacePerKill) until the six are down — then the NEXT type's wave. This is the showcase for the seven new weapons (js/combat/Weapons.js).",
|
||
"kind": "rolling",
|
||
"type": "bomber",
|
||
"count": 6,
|
||
"maxConcurrent": 3,
|
||
"replacePerKill": 1
|
||
},
|
||
{ "kind": "rolling", "type": "spitter", "count": 6, "maxConcurrent": 3, "replacePerKill": 1 },
|
||
{ "kind": "rolling", "type": "stinger", "count": 6, "maxConcurrent": 3, "replacePerKill": 1 },
|
||
{ "kind": "rolling", "type": "hunter", "count": 6, "maxConcurrent": 3, "replacePerKill": 1 },
|
||
{ "kind": "rolling", "type": "gunner", "count": 6, "maxConcurrent": 3, "replacePerKill": 1 },
|
||
{ "kind": "rolling", "type": "barrage", "count": 6, "maxConcurrent": 3, "replacePerKill": 1 },
|
||
{ "kind": "rolling", "type": "warden", "count": 6, "maxConcurrent": 3, "replacePerKill": 1 }
|
||
]
|
||
}
|
||
},
|
||
|
||
"playerShip": {
|
||
"_comment": "Your armor (both contexts — the real ship's stats.hullIntegrity/shields replace this when combat ships for real). shields absorb before the hull; after shieldRegenDelay s without a hit, shields regrow at shieldRegenRate/s up to their max. respawn = when your hull breaks you come back (respawnDelay s) instead of the run ending — a stand-in until the real death flow lands.",
|
||
"hull": 100,
|
||
"shields": 40,
|
||
"shieldRegenDelay": 4,
|
||
"shieldRegenRate": 14,
|
||
"respawn": true,
|
||
"respawnDelay": 2.0
|
||
},
|
||
"fx": {
|
||
"_comment": "The LOOK of combat (both contexts — js/combat/Fx.js + js/combat/CamShake.js). shake = the camera kick on the WORLD pass (the separate UI pass keeps the bar + feed still): each value is Phaser's shake intensity (a FRACTION of the viewport in px) and ms = the duration. hit = you take a hit, kill = an enemy goes down, break = the shield-collapse beat. shield = the shield-sphere impact read: radius px = the sphere's world radius around the ship (the impact point sits this far off the ship), arcSpan rad = the bright arc's angular width (the highlighted hit region), ripple px = the shockwave that grows from the impact point, decayMs = how long the sphere flash lingers.",
|
||
"shake": { "hit": 0.0045, "kill": 0.007, "break": 0.011, "ms": 230 },
|
||
"shield": { "radius": 40, "arcSpan": 1.9, "ripple": 90, "decayMs": 520 }
|
||
},
|
||
|
||
"arena": {
|
||
"_comment": "The range walls. inset = px between the screen edge and the bounce wall; bounce = the fraction of speed a ship keeps off a wall (1 = full mirror bounce). wallColor = the rim line, wallGlow = its neon edge.",
|
||
"inset": 26,
|
||
"bounce": 0.9,
|
||
"wallColor": "#1b2c4a",
|
||
"wallGlow": "#00e5ff"
|
||
},
|
||
|
||
"sandbox": {
|
||
"_comment": "Sandbox conveniences — none of this ships with the real game. resetKey rebuilds the whole range (a fresh scene); pauseKey freezes the sim (handy for eyeballing positions via window.__COMBAT__ in the console); respawn = the player comes back (respawnDelay s) instead of dying permanently.",
|
||
"resetKey": "R",
|
||
"pauseKey": "P",
|
||
"respawn": true,
|
||
"respawnDelay": 1.6
|
||
},
|
||
|
||
"readability": {
|
||
"_comment": "The READABILITY LAYER (js/combat/CombatHud.js) — the 'what do I need to see' side of combat, shared by both contexts. reticle = the target/LOCK indicator: rotating corner brackets on the current target (the one your guns are on — the range's nearest hostile, the main game's auto-fire target; the shared rule is Combat.js → pickTarget). margin = px outside the target's hitRadius, spin = rad/s bracket rotation, color = the lock's amber. threat = the hostile cue: a small pulsing marker above EVERY enemy (size px, pulse 1/s, above = px over the ship's center, color = hot red). offscreen = the edge ARROWS for hostiles outside the camera's view (main game — the range's arena is one screen, so they rarely fire there): pad = px inside the screen edge, size = arrow px, max = the most arrows drawn (nearest hostiles win), color. feed = the KILL FEED, top-right (entries = max lines, life ms per entry, gap px between lines, size px font, xInset = px in from the screen's RIGHT edge, y0 = px DOWN from the top (the first line), ink + hit colors). barFlash = the combat bar's GAUGE FLASH when YOU take a hit: life ms, alpha = the white overlay's start (the shield gauge flashes on shield hits, the hull gauge on hull hits — both when the shields pop through).",
|
||
"reticle": { "enabled": true, "margin": 16, "spin": 1.6, "color": "#ffd84d" },
|
||
"threat": { "enabled": false, "size": 8, "pulse": 2.2, "above": 36, "color": "#ff2d6f" },
|
||
"offscreen": { "enabled": true, "pad": 28, "size": 13, "max": 4, "color": "#ff2d6f" },
|
||
"feed": { "enabled": true, "entries": 5, "life": 3000, "gap": 19, "size": 13, "xInset": 18, "y0": 64, "ink": "#cfe9ff", "kill": "#ffd84d" },
|
||
"barFlash": { "life": 340, "alpha": 0.85 }
|
||
},
|
||
|
||
"hud": {
|
||
"_comment": "The range console chrome (colors fall back to data/theme.json). title = the corner label; hints = the control line (top-left — the bottom-center is the combat bar's now).",
|
||
"title": "COMBAT // SANDBOX",
|
||
"hints": "WASD / arrows — fly · space — fire · R — reset range · P — pause"
|
||
},
|
||
|
||
"bar": {
|
||
"_comment": "The COMBAT BAR (js/ui/CombatBar.js) — shown in place of the action deck while combat is live (the deck hides, the bar takes its place; when the pack is wiped it's the other way around). geometry = a BOTTOM-ALIGNED, CENTERED strip sized to its contents (assets/bar.png is the storyboard — not a color ref). Left end = the hull ORB (red) with the SHIELD orb (cyan, semi-transparent) overlapping it — shields in front of hull; middle = slot.count skill seats wearing keys 1-8 (the skills land later — for now they are empty seats); right end = the ENERGY orb (yellow, the skills' resource). The orbs fill like a Diablo orb: the liquid drains as the value drops. circle.radius = the hull/energy orb radius; circle.shield.scale = the shield orb's radius as a fraction of the hull radius — the shield orb shares the hull's CENTER (offsetX/offsetY default 0) and its liquid is semi-transparent over the hull's; orbGap = orb-to-slot spacing; margin.edge = the strip's overhang past the orbs. Colors fall back to data/theme.json where sensible.",
|
||
"strip": { "height": 64 },
|
||
"margin": { "bottom": 26, "edge": 10 },
|
||
"orbGap": 24,
|
||
"circle": {
|
||
"radius": 46,
|
||
"hull": { "fillAlpha": 0.95 },
|
||
"shield": { "scale": 1.12, "offsetX": 0, "offsetY": 0, "fillAlpha": 0.55 }
|
||
},
|
||
"colors": {
|
||
"bg": "#070d18",
|
||
"bgAlpha": 0.92,
|
||
"edge": "#22405f",
|
||
"shield": "#00e5ff",
|
||
"hull": "#ff2d6f",
|
||
"energy": "#ffd84d",
|
||
"slotBg": "#0b1322",
|
||
"slotBorder": "#22405f",
|
||
"key": "#5c7196"
|
||
},
|
||
"slot": { "count": 8, "size": 44, "gap": 10, "keySize": 12 }
|
||
}
|
||
}
|