polats Claude Opus 4.8 (1M context) commited on
Commit
c5ee685
·
1 Parent(s): 4dcaaae

Game: weaker early foes, no-kite ranged, C=character sheet, hide sidebar toggle

Browse files

- Early enemies weaker (HP*0.2, armor*0.25): most die in 2 Warrior basics, then ramp with
threat via hpMul. Verified Swordsman/Spearman/Knight=2 hits, Archer/Scout=1.
- Roam foes set noKite: ranged enemies hold ground + shoot instead of backing away.
- Character sheet: press C to open/close + a 'C' indicator on the menu button.
- Hide the app-sidebar toggle (it sat over the XP bar); show/hide the nav drawer with the
backtick/tilde key instead. Both guarded against text inputs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

web/classesSandbox.js CHANGED
@@ -1025,8 +1025,10 @@ function makeActor(unit, team, id, slot) {
1025
  alive: true,
1026
  mods: [],
1027
  kd: 0,
1028
- aggroRadius: unit.aggroRadius ?? null
1029
  // optional: idle until a foe is within this distance (else always engage)
 
 
1030
  };
1031
  }
1032
  function makeTeamBattle({ seed = 1, players = [], enemies = [], sandbox = false, respawnDummies = 0, freeCast = false, world = null, field = null } = {}) {
@@ -1567,7 +1569,8 @@ function moveActor(b, a, enemy, dt) {
1567
  const d = dist(a, enemy);
1568
  let toward = 0;
1569
  if (a.role === "ranged") {
1570
- if (d < (a.preferredRange ?? a.weapon.range * 0.7)) toward = -1;
 
1571
  else if (d > a.weapon.range) toward = 1;
1572
  } else if (edgeGap(a, enemy) > reachOf(a)) {
1573
  toward = 1;
 
1025
  alive: true,
1026
  mods: [],
1027
  kd: 0,
1028
+ aggroRadius: unit.aggroRadius ?? null,
1029
  // optional: idle until a foe is within this distance (else always engage)
1030
+ noKite: !!unit.noKite
1031
+ // ranged: hold ground in range instead of backing away (kiting)
1032
  };
1033
  }
1034
  function makeTeamBattle({ seed = 1, players = [], enemies = [], sandbox = false, respawnDummies = 0, freeCast = false, world = null, field = null } = {}) {
 
1569
  const d = dist(a, enemy);
1570
  let toward = 0;
1571
  if (a.role === "ranged") {
1572
+ if (a.noKite) toward = d > a.weapon.range ? 1 : 0;
1573
+ else if (d < (a.preferredRange ?? a.weapon.range * 0.7)) toward = -1;
1574
  else if (d > a.weapon.range) toward = 1;
1575
  } else if (edgeGap(a, enemy) > reachOf(a)) {
1576
  toward = 1;
web/comboBattler.js CHANGED
@@ -5109,8 +5109,10 @@ function makeActor(unit, team, id, slot) {
5109
  alive: true,
5110
  mods: [],
5111
  kd: 0,
5112
- aggroRadius: unit.aggroRadius ?? null
5113
  // optional: idle until a foe is within this distance (else always engage)
 
 
5114
  };
5115
  }
5116
  function makeTeamBattle({ seed = 1, players = [], enemies = [], sandbox = false, respawnDummies = 0, freeCast = false, world = null, field = null } = {}) {
@@ -5661,7 +5663,8 @@ function moveActor(b, a, enemy, dt) {
5661
  const d = dist2(a, enemy);
5662
  let toward = 0;
5663
  if (a.role === "ranged") {
5664
- if (d < (a.preferredRange ?? a.weapon.range * 0.7)) toward = -1;
 
5665
  else if (d > a.weapon.range) toward = 1;
5666
  } else if (edgeGap(a, enemy) > reachOf(a)) {
5667
  toward = 1;
 
5109
  alive: true,
5110
  mods: [],
5111
  kd: 0,
5112
+ aggroRadius: unit.aggroRadius ?? null,
5113
  // optional: idle until a foe is within this distance (else always engage)
5114
+ noKite: !!unit.noKite
5115
+ // ranged: hold ground in range instead of backing away (kiting)
5116
  };
5117
  }
5118
  function makeTeamBattle({ seed = 1, players = [], enemies = [], sandbox = false, respawnDummies = 0, freeCast = false, world = null, field = null } = {}) {
 
5663
  const d = dist2(a, enemy);
5664
  let toward = 0;
5665
  if (a.role === "ranged") {
5666
+ if (a.noKite) toward = d > a.weapon.range ? 1 : 0;
5667
+ else if (d < (a.preferredRange ?? a.weapon.range * 0.7)) toward = -1;
5668
  else if (d > a.weapon.range) toward = 1;
5669
  } else if (edgeGap(a, enemy) > reachOf(a)) {
5670
  toward = 1;
web/enemiesSandbox.js CHANGED
@@ -858,8 +858,10 @@ function makeActor(unit, team, id, slot) {
858
  alive: true,
859
  mods: [],
860
  kd: 0,
861
- aggroRadius: unit.aggroRadius ?? null
862
  // optional: idle until a foe is within this distance (else always engage)
 
 
863
  };
864
  }
865
  function makeTeamBattle({ seed = 1, players = [], enemies = [], sandbox = false, respawnDummies = 0, freeCast = false, world = null, field = null } = {}) {
@@ -1400,7 +1402,8 @@ function moveActor(b, a, enemy, dt) {
1400
  const d = dist(a, enemy);
1401
  let toward = 0;
1402
  if (a.role === "ranged") {
1403
- if (d < (a.preferredRange ?? a.weapon.range * 0.7)) toward = -1;
 
1404
  else if (d > a.weapon.range) toward = 1;
1405
  } else if (edgeGap(a, enemy) > reachOf(a)) {
1406
  toward = 1;
 
858
  alive: true,
859
  mods: [],
860
  kd: 0,
861
+ aggroRadius: unit.aggroRadius ?? null,
862
  // optional: idle until a foe is within this distance (else always engage)
863
+ noKite: !!unit.noKite
864
+ // ranged: hold ground in range instead of backing away (kiting)
865
  };
866
  }
867
  function makeTeamBattle({ seed = 1, players = [], enemies = [], sandbox = false, respawnDummies = 0, freeCast = false, world = null, field = null } = {}) {
 
1402
  const d = dist(a, enemy);
1403
  let toward = 0;
1404
  if (a.role === "ranged") {
1405
+ if (a.noKite) toward = d > a.weapon.range ? 1 : 0;
1406
+ else if (d < (a.preferredRange ?? a.weapon.range * 0.7)) toward = -1;
1407
  else if (d > a.weapon.range) toward = 1;
1408
  } else if (edgeGap(a, enemy) > reachOf(a)) {
1409
  toward = 1;
web/engine.js CHANGED
@@ -832,8 +832,10 @@ function makeActor(unit, team, id, slot) {
832
  alive: true,
833
  mods: [],
834
  kd: 0,
835
- aggroRadius: unit.aggroRadius ?? null
836
  // optional: idle until a foe is within this distance (else always engage)
 
 
837
  };
838
  }
839
  function makeTeamBattle({ seed = 1, players = [], enemies = [], sandbox = false, respawnDummies = 0, freeCast = false, world = null, field = null } = {}) {
@@ -1384,7 +1386,8 @@ function moveActor(b, a, enemy, dt) {
1384
  const d = dist(a, enemy);
1385
  let toward = 0;
1386
  if (a.role === "ranged") {
1387
- if (d < (a.preferredRange ?? a.weapon.range * 0.7)) toward = -1;
 
1388
  else if (d > a.weapon.range) toward = 1;
1389
  } else if (edgeGap(a, enemy) > reachOf(a)) {
1390
  toward = 1;
 
832
  alive: true,
833
  mods: [],
834
  kd: 0,
835
+ aggroRadius: unit.aggroRadius ?? null,
836
  // optional: idle until a foe is within this distance (else always engage)
837
+ noKite: !!unit.noKite
838
+ // ranged: hold ground in range instead of backing away (kiting)
839
  };
840
  }
841
  function makeTeamBattle({ seed = 1, players = [], enemies = [], sandbox = false, respawnDummies = 0, freeCast = false, world = null, field = null } = {}) {
 
1386
  const d = dist(a, enemy);
1387
  let toward = 0;
1388
  if (a.role === "ranged") {
1389
+ if (a.noKite) toward = d > a.weapon.range ? 1 : 0;
1390
+ else if (d < (a.preferredRange ?? a.weapon.range * 0.7)) toward = -1;
1391
  else if (d > a.weapon.range) toward = 1;
1392
  } else if (edgeGap(a, enemy) > reachOf(a)) {
1393
  toward = 1;
web/tiny.js CHANGED
@@ -65,6 +65,17 @@ function whenEl(id, cb) {
65
  o.observe(document.body, { childList: true, subtree: true })
66
  }
67
 
 
 
 
 
 
 
 
 
 
 
 
68
  // Gradio wraps each gr.HTML block in a `<div class="prose gradio-style …">` whose versioned rules
69
  // (.gradio-container-X .prose h2 / .prose * ; .gradio-container-X .gradio-style button) outrank the
70
  // shared component CSS — recolouring/resizing our headings and stripping pill/button borders +
@@ -226,6 +237,8 @@ const ENEMY_AGGRO = 220 // FIELD units (~8 tiles): enemy idles until the player
226
  // Ranged reach is authored in arena GW units (BOW_GW=1000 ≈ 37 roam tiles — whole-screen). The
227
  // roam map is far smaller, so scale ranged weapon range + kite distance down to ~8 tiles here.
228
  const ROAM_RANGE_MUL = 0.22
 
 
229
  // A persona → controllable hero (class → sheets + engine profession).
230
  const buildPlayer = (chars, p) => {
231
  const pc = chars[CLASS_SLUG[p?.unitClass]] || chars['true-heroes-iii-fighter']
@@ -354,7 +367,10 @@ whenEl('battle-stage', async (el) => {
354
  const buildRoster = (list) => list.map((e) => {
355
  const c = chars[e.slug]; if (!c) return null
356
  // aggroRadius null = always engage → foes advance toward the hero (waves), not idle until near.
357
- return { name: e.name, sheets: sheetsOf(c), unit: { name: e.name, stats: e.stats, attackType: e.attackType, skills: [], aggroRadius: null, rangeMul: ROAM_RANGE_MUL } }
 
 
 
358
  }).filter(Boolean)
359
  const rosters = Object.fromEntries(Object.entries(GAME_ROSTERS).map(([k, v]) => [k, buildRoster(v)]))
360
  // Mount with NO hero → the map shows and the player picks a persona from the bottom picker. The
@@ -447,8 +463,9 @@ whenEl('battle-stage', async (el) => {
447
 
448
  // ── Character sheet — a top-right button slides a hero panel in from the right (sidebar-style). ──
449
  let sheetOpen = false
450
- const sheetBtn = document.createElement('button'); sheetBtn.type = 'button'; sheetBtn.textContent = ''
451
- sheetBtn.style.cssText = 'position:absolute;top:14px;right:14px;z-index:7;width:44px;height:44px;border-radius:10px;border:1px solid #2a3340;background:rgba(20,24,33,.85);color:#e8e8e8;font:600 18px var(--tac-font,system-ui);cursor:pointer'
 
452
  const sheet = document.createElement('aside')
453
  sheet.style.cssText = 'position:absolute;top:0;right:0;bottom:0;width:min(300px,82vw);z-index:8;background:rgba(16,20,27,.97);border-left:1px solid #2a3340;box-shadow:-8px 0 24px rgba(0,0,0,.45);transform:translateX(100%);transition:transform .22s ease;overflow-y:auto;color:#e8e8e8;font:13px var(--tac-font,system-ui);box-sizing:border-box;padding:18px 16px'
454
  // A forged-skill detail bottom-sheet: big action illustration (lazy Klein render, cached),
@@ -701,6 +718,11 @@ whenEl('battle-stage', async (el) => {
701
  } else { sheetBackdrop?.remove(); sheetBackdrop = null }
702
  }
703
  sheetBtn.addEventListener('click', () => setSheet(!sheetOpen))
 
 
 
 
 
704
  el.append(sheetBtn, sheet)
705
 
706
  // ── XP / level HUD chip (top-left) — always visible while a hero is in play ──
 
65
  o.observe(document.body, { childList: true, subtree: true })
66
  }
67
 
68
+ // The app-sidebar toggle (‹/›) sits top-left over the XP bar — hide it; the nav drawer is now
69
+ // shown/hidden with the ` or ~ key instead. (The drawer starts collapsed via tac-collapsed.)
70
+ ;(function navDrawerKey() {
71
+ const css = document.createElement('style'); css.textContent = '.tac-toggle{display:none !important}'
72
+ document.head.appendChild(css)
73
+ window.addEventListener('keydown', (e) => {
74
+ const t = e.target; if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return
75
+ if (e.code === 'Backquote' || e.key === '`' || e.key === '~') { document.body.classList.toggle('tac-collapsed'); e.preventDefault() }
76
+ })
77
+ })()
78
+
79
  // Gradio wraps each gr.HTML block in a `<div class="prose gradio-style …">` whose versioned rules
80
  // (.gradio-container-X .prose h2 / .prose * ; .gradio-container-X .gradio-style button) outrank the
81
  // shared component CSS — recolouring/resizing our headings and stripping pill/button borders +
 
237
  // Ranged reach is authored in arena GW units (BOW_GW=1000 ≈ 37 roam tiles — whole-screen). The
238
  // roam map is far smaller, so scale ranged weapon range + kite distance down to ~8 tiles here.
239
  const ROAM_RANGE_MUL = 0.22
240
+ // Early foes should die in ~2 Warrior basics, then ramp with threat (hpMul). Lower base HP + armor.
241
+ const ENEMY_HP_SCALE = 0.2, ENEMY_ARMOR_SCALE = 0.25
242
  // A persona → controllable hero (class → sheets + engine profession).
243
  const buildPlayer = (chars, p) => {
244
  const pc = chars[CLASS_SLUG[p?.unitClass]] || chars['true-heroes-iii-fighter']
 
367
  const buildRoster = (list) => list.map((e) => {
368
  const c = chars[e.slug]; if (!c) return null
369
  // aggroRadius null = always engage → foes advance toward the hero (waves), not idle until near.
370
+ // Weaker base stats so early foes die in ~2 Warrior basics (they still ramp with threat via hpMul).
371
+ // noKite: ranged foes hold ground + shoot instead of backing away.
372
+ const stats = { ...e.stats, hp: Math.max(1, Math.round((e.stats.hp || 100) * ENEMY_HP_SCALE)), armor: Math.round((e.stats.armor || 0) * ENEMY_ARMOR_SCALE) }
373
+ return { name: e.name, sheets: sheetsOf(c), unit: { name: e.name, stats, attackType: e.attackType, skills: [], aggroRadius: null, rangeMul: ROAM_RANGE_MUL, noKite: true } }
374
  }).filter(Boolean)
375
  const rosters = Object.fromEntries(Object.entries(GAME_ROSTERS).map(([k, v]) => [k, buildRoster(v)]))
376
  // Mount with NO hero → the map shows and the player picks a persona from the bottom picker. The
 
463
 
464
  // ── Character sheet — a top-right button slides a hero panel in from the right (sidebar-style). ──
465
  let sheetOpen = false
466
+ const sheetBtn = document.createElement('button'); sheetBtn.type = 'button'; sheetBtn.title = 'Character (C)'
467
+ sheetBtn.innerHTML = '☰<span style="font:700 9px var(--tac-font,system-ui);display:block;margin-top:-2px;color:#9aa4b2;letter-spacing:.04em">C</span>'
468
+ sheetBtn.style.cssText = 'position:absolute;top:14px;right:14px;z-index:7;width:44px;height:46px;border-radius:10px;border:1px solid #2a3340;background:rgba(20,24,33,.85);color:#e8e8e8;font:600 17px var(--tac-font,system-ui);cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;line-height:1'
469
  const sheet = document.createElement('aside')
470
  sheet.style.cssText = 'position:absolute;top:0;right:0;bottom:0;width:min(300px,82vw);z-index:8;background:rgba(16,20,27,.97);border-left:1px solid #2a3340;box-shadow:-8px 0 24px rgba(0,0,0,.45);transform:translateX(100%);transition:transform .22s ease;overflow-y:auto;color:#e8e8e8;font:13px var(--tac-font,system-ui);box-sizing:border-box;padding:18px 16px'
471
  // A forged-skill detail bottom-sheet: big action illustration (lazy Klein render, cached),
 
718
  } else { sheetBackdrop?.remove(); sheetBackdrop = null }
719
  }
720
  sheetBtn.addEventListener('click', () => setSheet(!sheetOpen))
721
+ // Press C to open/close the character sheet (ignored while typing).
722
+ window.addEventListener('keydown', (e) => {
723
+ const t = e.target; if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return
724
+ if (e.key === 'c' || e.key === 'C') { setSheet(!sheetOpen); e.preventDefault() }
725
+ })
726
  el.append(sheetBtn, sheet)
727
 
728
  // ── XP / level HUD chip (top-left) — always visible while a hero is in play ──