polats Claude Opus 4.8 (1M context) commited on
Commit
1eb2325
·
1 Parent(s): 71a4292

Rebuild engine+comboBattler: dodge roll i-frames

Browse files

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

Files changed (2) hide show
  1. web/comboBattler.js +62 -3
  2. web/engine.js +4 -0
web/comboBattler.js CHANGED
@@ -5220,6 +5220,10 @@ function healActor(b, a, amount, empowered) {
5220
  }
5221
  function dealDamage(b, src, tgt, amount, label, opts = {}) {
5222
  if (!tgt.alive) return 0;
 
 
 
 
5223
  const { damageType = "physical", delivery = "spell", armorIgnoring = false, empowered = false } = opts;
5224
  const physical = delivery === "melee" || delivery === "projectile";
5225
  if (physical) {
@@ -6077,7 +6081,29 @@ function mountComboBattler(pixi, host, opts = {}) {
6077
  let offTick = null, keyHandlers = null, tapHandlers = null, controlsEl = null, alive = true;
6078
  const keys = { x: 0, y: 0 };
6079
  const joy = { x: 0, y: 0, active: false };
6080
- const req = { attack: false, skill: 0 };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6081
  let moveTarget = null;
6082
  let navTarget = null, navRoute = null;
6083
  let routePath = null, routeIdx = 0;
@@ -6116,6 +6142,11 @@ function mountComboBattler(pixi, host, opts = {}) {
6116
  e.preventDefault();
6117
  return;
6118
  }
 
 
 
 
 
6119
  if (e.key >= "1" && e.key <= "9") {
6120
  req.skill = +e.key;
6121
  e.preventDefault();
@@ -6218,6 +6249,7 @@ function mountComboBattler(pixi, host, opts = {}) {
6218
  sb.el.style.borderColor = ok ? "#c9a227" : "#20262e";
6219
  }
6220
  if (threatBadge) threatBadge.textContent = `\u2694 ${runState.kills} THREAT ${runState.diff.toFixed(1)}\xD7`;
 
6221
  }
6222
  function setHeroSkills(skills) {
6223
  const p = pa();
@@ -6300,9 +6332,16 @@ function mountComboBattler(pixi, host, opts = {}) {
6300
  adrWrap.append(adrLab, adrTrack);
6301
  pad.appendChild(adrWrap);
6302
  pad.appendChild(skills);
6303
- pad.appendChild(roundBtn("\u2694", () => {
 
 
 
 
 
 
6304
  req.attack = true;
6305
  }, true));
 
6306
  wrap.appendChild(pad);
6307
  threatBadge = document.createElement("div");
6308
  threatBadge.style.cssText = "position:absolute;top:14px;left:50%;transform:translateX(-50%);pointer-events:none;padding:5px 12px;border-radius:999px;background:rgba(20,24,33,.8);border:1px solid #2a3340;color:#e8e8e8;font:700 12px monospace;letter-spacing:.04em";
@@ -6326,12 +6365,29 @@ function mountComboBattler(pixi, host, opts = {}) {
6326
  if (!battle || !R) return;
6327
  const dtMS = ticker.deltaMS, dt = dtMS / 1e3;
6328
  const p = pa();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6329
  const clearNav = () => {
6330
  moveTarget = null;
6331
  navTarget = navRoute = routePath = null;
6332
  routeIdx = 0;
6333
  };
6334
- if (joy.active) {
 
 
6335
  clearNav();
6336
  setInput(battle, "P0", { moveX: joy.x, moveY: joy.y });
6337
  } else if (keys.x || keys.y) {
@@ -6541,7 +6597,10 @@ function mountComboBattler(pixi, host, opts = {}) {
6541
  skillRowEl = null;
6542
  adrFill = null;
6543
  threatBadge = null;
 
6544
  skillBtns.length = 0;
 
 
6545
  try {
6546
  markers?.destroy();
6547
  } catch {
 
5220
  }
5221
  function dealDamage(b, src, tgt, amount, label, opts = {}) {
5222
  if (!tgt.alive) return 0;
5223
+ if (tgt.invulnUntil && b.t < tgt.invulnUntil) {
5224
+ log(b, "miss", tgt, { name: label });
5225
+ return 0;
5226
+ }
5227
  const { damageType = "physical", delivery = "spell", armorIgnoring = false, empowered = false } = opts;
5228
  const physical = delivery === "melee" || delivery === "projectile";
5229
  if (physical) {
 
6081
  let offTick = null, keyHandlers = null, tapHandlers = null, controlsEl = null, alive = true;
6082
  const keys = { x: 0, y: 0 };
6083
  const joy = { x: 0, y: 0, active: false };
6084
+ const req = { attack: false, skill: 0, dodge: false };
6085
+ let dash = null, dodgeCdUntil = 0, dodgeBtn = null;
6086
+ const DODGE_IFRAMES = 0.4, DODGE_DASH = 0.16, DODGE_CD = 1.1, DODGE_BOOST = 3.4;
6087
+ const norm = (x, y) => {
6088
+ const m = Math.hypot(x, y) || 1;
6089
+ return { x: x / m, y: y / m };
6090
+ };
6091
+ const moveDir = () => {
6092
+ if (joy.active && (joy.x || joy.y)) return norm(joy.x, joy.y);
6093
+ if (keys.x || keys.y) return norm(keys.x, keys.y);
6094
+ const p = pa();
6095
+ return p ? norm(p.faceX || 1, p.faceY || 0) : { x: 1, y: 0 };
6096
+ };
6097
+ function tryDodge() {
6098
+ const p = pa();
6099
+ if (!p || !p.alive || !battle || battle.t < dodgeCdUntil) return;
6100
+ const dir = moveDir();
6101
+ p._dashBase = p.moveSpeed;
6102
+ p.moveSpeed = p._dashBase * DODGE_BOOST;
6103
+ p.invulnUntil = battle.t + DODGE_IFRAMES;
6104
+ dash = { until: battle.t + DODGE_DASH, x: dir.x, y: dir.y };
6105
+ dodgeCdUntil = battle.t + DODGE_CD;
6106
+ }
6107
  let moveTarget = null;
6108
  let navTarget = null, navRoute = null;
6109
  let routePath = null, routeIdx = 0;
 
6142
  e.preventDefault();
6143
  return;
6144
  }
6145
+ if (e.key === "Shift" || e.code === "ShiftLeft" || e.code === "ShiftRight") {
6146
+ req.dodge = true;
6147
+ e.preventDefault();
6148
+ return;
6149
+ }
6150
  if (e.key >= "1" && e.key <= "9") {
6151
  req.skill = +e.key;
6152
  e.preventDefault();
 
6249
  sb.el.style.borderColor = ok ? "#c9a227" : "#20262e";
6250
  }
6251
  if (threatBadge) threatBadge.textContent = `\u2694 ${runState.kills} THREAT ${runState.diff.toFixed(1)}\xD7`;
6252
+ if (dodgeBtn) dodgeBtn.style.opacity = battle && battle.t >= dodgeCdUntil ? "1" : "0.4";
6253
  }
6254
  function setHeroSkills(skills) {
6255
  const p = pa();
 
6332
  adrWrap.append(adrLab, adrTrack);
6333
  pad.appendChild(adrWrap);
6334
  pad.appendChild(skills);
6335
+ const actionRow = document.createElement("div");
6336
+ actionRow.style.cssText = "display:flex;gap:10px;align-items:flex-end";
6337
+ dodgeBtn = roundBtn("\u27F2", () => {
6338
+ req.dodge = true;
6339
+ }, false);
6340
+ dodgeBtn.title = "Dodge roll (Shift)";
6341
+ actionRow.append(dodgeBtn, roundBtn("\u2694", () => {
6342
  req.attack = true;
6343
  }, true));
6344
+ pad.appendChild(actionRow);
6345
  wrap.appendChild(pad);
6346
  threatBadge = document.createElement("div");
6347
  threatBadge.style.cssText = "position:absolute;top:14px;left:50%;transform:translateX(-50%);pointer-events:none;padding:5px 12px;border-radius:999px;background:rgba(20,24,33,.8);border:1px solid #2a3340;color:#e8e8e8;font:700 12px monospace;letter-spacing:.04em";
 
6365
  if (!battle || !R) return;
6366
  const dtMS = ticker.deltaMS, dt = dtMS / 1e3;
6367
  const p = pa();
6368
+ if (req.dodge) {
6369
+ req.dodge = false;
6370
+ tryDodge();
6371
+ }
6372
+ let dashing = false;
6373
+ if (dash) {
6374
+ if (p && p.alive && battle.t < dash.until) dashing = true;
6375
+ else {
6376
+ if (p && p._dashBase) {
6377
+ p.moveSpeed = p._dashBase;
6378
+ p._dashBase = 0;
6379
+ }
6380
+ dash = null;
6381
+ }
6382
+ }
6383
  const clearNav = () => {
6384
  moveTarget = null;
6385
  navTarget = navRoute = routePath = null;
6386
  routeIdx = 0;
6387
  };
6388
+ if (dashing) {
6389
+ setInput(battle, "P0", { moveX: dash.x, moveY: dash.y });
6390
+ } else if (joy.active) {
6391
  clearNav();
6392
  setInput(battle, "P0", { moveX: joy.x, moveY: joy.y });
6393
  } else if (keys.x || keys.y) {
 
6597
  skillRowEl = null;
6598
  adrFill = null;
6599
  threatBadge = null;
6600
+ dodgeBtn = null;
6601
  skillBtns.length = 0;
6602
+ dash = null;
6603
+ dodgeCdUntil = 0;
6604
  try {
6605
  markers?.destroy();
6606
  } catch {
web/engine.js CHANGED
@@ -943,6 +943,10 @@ function healActor(b, a, amount, empowered) {
943
  }
944
  function dealDamage(b, src, tgt, amount, label, opts = {}) {
945
  if (!tgt.alive) return 0;
 
 
 
 
946
  const { damageType = "physical", delivery = "spell", armorIgnoring = false, empowered = false } = opts;
947
  const physical = delivery === "melee" || delivery === "projectile";
948
  if (physical) {
 
943
  }
944
  function dealDamage(b, src, tgt, amount, label, opts = {}) {
945
  if (!tgt.alive) return 0;
946
+ if (tgt.invulnUntil && b.t < tgt.invulnUntil) {
947
+ log(b, "miss", tgt, { name: label });
948
+ return 0;
949
+ }
950
  const { damageType = "physical", delivery = "spell", armorIgnoring = false, empowered = false } = opts;
951
  const physical = delivery === "melee" || delivery === "projectile";
952
  if (physical) {