File size: 6,597 Bytes
c7b3e0f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | // engine.js - Core constants & physics ported from Ikemen-GO (MIT, Suehiro & contributors)
// Values mirror src/bytecode.go, src/char.go (CharVelocity/CharMovement init) and src/stage.go.
// ---- State types (src/bytecode.go ST_*) ----
export const ST_S = 1, ST_C = 2, ST_A = 4, ST_L = 8, ST_N = 16, ST_U = 32;
export const ST_MASK = 63;
export const ST_SCA = ST_S | ST_C | ST_A;
// Move types
export const MT_I = 0, MT_U = 1, MT_H = 2;
// ---- Attack types (src/bytecode.go AT_*) ----
export const AT_NA = 1 << 6, AT_NT = 1 << 7, AT_NP = 1 << 8;
export const AT_SA = 1 << 9, AT_ST = 1 << 10, AT_SP = 1 << 11;
export const AT_HA = 1 << 12, AT_HT = 1 << 13, AT_HP = 1 << 14;
export const AT_AA = AT_NA | AT_SA | AT_HA;
export const AT_AT = AT_NT | AT_ST | AT_HT;
export const AT_AP = AT_NP | AT_SP | AT_HP;
export const AT_ALL = AT_AA | AT_AT | AT_AP;
// ---- Character flags ----
export const CF_HITSTOP = 1 << 0;
export const CF_UNGUARDED = 1 << 1;
export const CF_GUARDED = 1 << 2;
export const CF_DIZZY = 1 << 3;
export const CF_KO = 1 << 4;
export const CF_JUGGLE = 1 << 5;
// ---- Global constants ----
export const MaxPlayerNo = 6;
export const GRAVITY_DEFAULT = 0.44; // cm.yaccel
export const STAND_FRICTION = 0.85; // cm.stand.friction
export const STAND_FRICTION_THRESHOLD = 2.0;
export const CROUCH_FRICTION = 0.82;
export const CROUCH_FRICTION_THRESHOLD = 0.0;
export const DOWN_FRICTION_THRESHOLD = 0.05;
// air.gethit (CharVelocity.init)
export const AIR_GETHIT_GROUNDRECOVER = [-0.15, -3.5];
export const AIR_GETHIT_AIRRECOVER_MUL = [0.5, 0.2];
export const AIR_GETHIT_AIRRECOVER_ADD = [0.0, -4.5];
export const AIR_GETHIT_AIRRECOVER_BACK = -1.0;
export const AIR_GETHIT_AIRRECOVER_FWD = 0.0;
export const AIR_GETHIT_AIRRECOVER_UP = -2.0;
export const AIR_GETHIT_AIRRECOVER_DOWN = 1.5;
export const AIR_GETHIT_KO_ADD = [-2.5, -2];
export const AIR_GETHIT_KO_YMIN = -3;
export const GROUND_GETHIT_KO_XMUL = 0.66;
export const GROUND_GETHIT_KO_ADD = [-2.5, -2];
export const GROUND_GETHIT_KO_YMIN = -6;
// movement (CharMovement.init)
export const AIR_GETHIT_GROUNDRECOVER_GROUND_THRESHOLD = -20.0;
export const AIR_GETHIT_GROUNDRECOVER_GROUNDLEVEL = 10.0;
export const AIR_GETHIT_AIRRECOVER_THRESHOLD = -1.0;
export const AIR_GETHIT_AIRRECOVER_YACCEL = 0.35;
// stage defaults (newStage)
export const STAGE_LEFTBOUND = -1000;
export const STAGE_RIGHTBOUND = 1000;
export const STAGE_SCREENLEFT = 15;
export const STAGE_SCREENRIGHT = 15;
export const STAGE_TOPBOUND = -60;
export const STAGE_BOTBOUND = 80;
// Canvas stage geometry (MUGEN-style local + camera)
export const STAGE_LOCALWIDTH = 1280;
export const STAGE_LOCALHEIGHT = 720;
export const STAGE_FLOORY = 640; // where char.pos[1] === 0 sits in local coords
export const STAGE_CAMERA_ZOOM = 1.0;
export const ROUND_TIME = 99 * 60; // 99-second round timer (MUGEN default)
export const MAX_JUGGLE = 8;
export const MAX_GUARD_JUGGLE = 10;
export const MAX_CHAIN = 10;
export const clamp = (v, lo, hi) => v < lo ? lo : (v > hi ? hi : v);
export const sign = v => v > 0 ? 1 : (v < 0 ? -1 : 0);
export const abs = Math.abs;
export const Btoi = v => (v > 0 ? Math.floor(v) : Math.ceil(v));
export const roundTo = Math.round;
/**
* Rect intersection test using MUGEN box semantics.
* Box = [x1, y1, x2, y2] in character local space (y negative = up).
*/
export function boxOverlap(b1, b2) {
return b1[0] < b2[2] && b1[2] > b2[0] && b1[1] < b2[3] && b1[3] > b2[1];
}
export function rectWidth(b) { return b[2] - b[0]; }
export function rectHeight(b) { return b[3] - b[1]; }
/**
* Physics integration ported from Ikemen-GO's char physics step.
* Called once per active frame while the char is not paused.
*/
export function physicsStep(c) {
const d = c.data;
const air = c.pos[1] < 0 || c.stateType === ST_A;
// horizontal velocity
c.vel[0] += c.accel[0];
// friction (src/char.go uses stand/crouch friction w/ thresholds)
const fric = c.stateType === ST_C ? CROUCH_FRICTION : STAND_FRICTION;
const fricTh = c.stateType === ST_C ? CROUCH_FRICTION_THRESHOLD : STAND_FRICTION_THRESHOLD;
if (Math.abs(c.vel[0]) > fricTh) {
c.vel[0] -= sign(c.vel[0]) * fric;
if (sign(c.vel[0]) !== sign(c.vel[0] - sign(c.vel[0]) * fric)) c.vel[0] = 0;
}
// vertical: yaccel only while airborne (MUGEN: vel[1] += yaccel down)
if (air && !c.velFreezeY) {
c.vel[1] += (c.yaccelOverride || d.yaccel);
}
// integrate
c.pos[0] += c.vel[0];
c.pos[1] += c.vel[1];
c.pos[2] += c.vel[2];
// stage bounds
if (c.pos[0] < d.leftbound) { c.pos[0] = d.leftbound; c.vel[0] = 0; }
if (c.pos[0] > d.rightbound) { c.pos[0] = d.rightbound; c.vel[0] = 0; }
if (c.pos[1] < d.topbound) { c.pos[1] = d.topbound; c.vel[1] = 0; }
}
/** Ground contact test: pos[1] === 0 in MUGEN space (y is negative up) */
export function onGround(c) {
return c.pos[1] >= 0 && !c.airborne;
}
/**
* Compute get-hit velocity, ported from Ikemen-GO hit velocity resolution.
* Mirrors air.gethit.airrecover mul/add semantics.
*/
export function getHitVelocity(victim, attacker, hit) {
const facing = attacker.pos[0] < victim.pos[0] ? 1 : -1;
let vx = 0, vy = 0;
if (victim.stateType === ST_A) {
// airborne recovery
if (hit.grounded) {
vx = AIR_GETHIT_GROUNDRECOVER[0] * facing;
vy = AIR_GETHIT_GROUNDRECOVER[1];
} else {
vx = (AIR_GETHIT_AIRRECOVER_BACK * facing) * Math.abs(hit.vel[0]) * 0.5;
vy = AIR_GETHIT_AIRRECOVER_UP;
// juggle scaling: upward component decays with juggle count
vy *= Math.max(0.35, 1 - victim.juggle * 0.075);
if (victim.vel[1] > 0) vy += AIR_GETHIT_AIRRECOVER_DOWN * 0.4;
}
// apply hitdef velocity multiplier/add
vx = (hit.vel[0] * facing) + vx * 0.5;
vy = (hit.vel[1]) + vy * 0.5;
} else {
// grounded recovery: horizontal push scaled by xmul
vx = (hit.vel[0] * facing) * GROUND_GETHIT_KO_XMUL;
vy = hit.vel[1];
if (hit.guard) {
// blocking: much weaker pushback
vx = (hit.guard.vel[0] * facing);
vy = hit.guard.vel[1];
}
}
return [vx, vy];
}
/** Debug helper to render attr strings like Ikemen's attrString() */
export function attrString(attr) {
if (!attr) return '';
let b = '';
if (attr & ST_S) b += 'S';
if (attr & ST_C) b += 'C';
if (attr & ST_A) b += 'A';
const pairs = [[AT_NA, 'NA'], [AT_NT, 'NT'], [AT_NP, 'NP'],
[AT_SA, 'SA'], [AT_ST, 'ST'], [AT_SP, 'SP'],
[AT_HA, 'HA'], [AT_HT, 'HT'], [AT_HP, 'HP']];
for (const [bit, label] of pairs) if (attr & bit) b += ', ' + label;
return b;
} |