0.2.5-dev: community round 30 and follow-ups
- Stock: fixed trait gifts (缪尔赛思 / 变形者集群 变形同构体, 耶拉 不融冰, 蜜蜡 / 卡涅利安 迅捷作战粮, 诗怀雅 盟约之币) still occupy stock but are granted past the cap; an operator card already shown in the shop or a reward can be taken after the pool runs out and overdraws (GitHub #498, the owner's decision); item buys and item rewards keep the stock check. 洛洛 / 焰尾 sub-pools use the measured weights (PR #497 by @2321Robin; the occupancy idea of PR #496 by @2321Robin re-implemented differently). - Boss pairs are drawn at random once per match and fixed to battle start; the Hidden Core reuses them; a leaver does not split a full pair (the owner's decision). - 阿戈尔 in 联防 no longer re-applies the opening devour HP loss or a false revive; Touch's team medic stays for the match after its holder leaves (owner-approved, official lifetime unverified). - Auto-skill ranges: 司霆惊蛰 S2, 斩业星熊 S3 against air, 予愿安洁莉娜 S3; 自在's post-rebirth invulnerability is also untargetable (#510); Horn / Clementia / catapult / 忍冬 first hit (PR #502 by @Xinglan233); 不融冰 / tear gas trigger on real damage. - Bond layer-up sound in round start / draft / prep / battle (#482): one channel, a new cue replaces the old, no extra cooldown; a prep update that adds layers and activates a bond plays one cue. - Client: multi-swing fix (#506), refresh single-flight (PR #507 by @robll-v1), LP tower keeps the settled value with a −N tick (PR #504 by @robll-v1), export with only 潜能 / 练度 changes (PR #503 by @Xinglan233), spectator rosters list DIY members (PR #505 by @Xinglan233), banned members beside bond tooltips (PR #494 by @YuriRestia), recovery action above the away overlay (PR #511 by @RainoutOvO), the /dev/sim-check.html consistency page (PR #492 by @Cloudnyco), Korean (PR #490 by @D-Roote) and English Victoria wording (PR #499 by @YuriRestia). - golden: 45 of 287 scenarios move against 0.2.4 (roster 17, bonds 3, fields 6, matches 17, standins 2), each attributed in docs/history/0.2.5.md §30.9 / §30.16.
This commit is contained in:
1 parent
bc50cac01f
commit
221e6b8dd2
134 files changed
+4590
-1201
No files matched your search
+20
-23
@@ -6,8 +6,8 @@
|
||||
//
|
||||
// Driving (units.js calls these; the sim is authoritative, the actor only visualises):
|
||||
// setBase('idle'|'move'|'stun') the resting state from the snapshot anim code
|
||||
// attack(interval, once) one attack happened now (b.ev 'atk'): plays begin→loop, re-phases the loop
|
||||
// so its OnAttack frame lands now, timeScale = loopDuration / interval;
|
||||
// attack(interval, once) one attack happened now (b.ev 'atk'): consumes its wind-up or starts at
|
||||
// OnAttack, once; operator timeScale = loopDuration / interval;
|
||||
// `once` (a one-off cast, style.js PROJ[kind].once — 暴鸰's bomb drop): the
|
||||
// clip plays once at its own speed, then base; `clipPerAttack` (enemies,
|
||||
// GitHub #58): every attack plays the clip once — at its own speed, faster
|
||||
@@ -33,8 +33,7 @@
|
||||
// update(dt) advances the skeleton (autoUpdate is off: one clock for everything)
|
||||
// poseHeld() true while the pose cannot change (render/units.js then neither re-poses nor
|
||||
// redraws the model, only advances `clock`)
|
||||
// Attack mode lasts until ~1.4 attack intervals without a new attack (a `once` cast and every attack of a
|
||||
// `clipPerAttack` actor: to the end of its clip), then the end clip (if any) and base — except the attacks of a skill
|
||||
// Each attack plays its clip once, then the end clip (if any) and base — except the attacks of a skill
|
||||
// with its own idle clip, which go straight back to that idle: the skill's end clip closes the skill, not each spell of
|
||||
// attacks while it runs.
|
||||
|
||||
@@ -158,10 +157,10 @@ export class SpineActor {
|
||||
/**
|
||||
* Enemies (render/units.js, GitHub #58): each attack plays the attack clip once — at its own speed, faster only when
|
||||
* the attacks come quicker than the clip — and then the resting state; the sim stands the enemy for exactly that
|
||||
* clip (server/sim/ai.js attackStand). Off (operators): the clip loops over the attack rhythm.
|
||||
* clip (server/sim/ai.js attackStand). Off (operators): retain rhythm scaling, but still play each attack once.
|
||||
*/
|
||||
this.clipPerAttack = false;
|
||||
this.wound = false; // clipPerAttack: wound up for the coming attack (windUp → attack)
|
||||
this.wound = false; // wound up for the coming attack (windUp → attack)
|
||||
this._applied = false; // an update has posed the skeleton since its clip last changed (poseHeld)
|
||||
this._appliedTint = undefined; // the tint that update put on the slots (pixi-spine applies `tint` in update)
|
||||
this._play(this._idleName(), true);
|
||||
@@ -366,9 +365,8 @@ export class SpineActor {
|
||||
if (this.dead || this.mode === 'stun' || this.mode === 'die' || this.mode === 'change' || !(lead >= 0)) return false;
|
||||
const clip = this._attackClip();
|
||||
if (!clip) return false;
|
||||
const single = once || this.clipPerAttack;
|
||||
// in rhythm: attack() re-phases; a clip-per-attack actor starts each attack's clip anew unless already wound up for it
|
||||
if (this.mode === 'attack' && this.current === clip.loop && (!this.clipPerAttack || once || this.wound)) return false;
|
||||
// One queued attack owns one wind-up. A later attack may begin while the previous recovery is still playing.
|
||||
if (this.mode === 'attack' && this.current === clip.loop && (once || this.wound)) return false;
|
||||
const loopDur = this.dur(clip.loop);
|
||||
// a one-off cast plays at the clip's own speed (one loop per clip length), whatever the attack rhythm
|
||||
const iv = once ? loopDur : clampN(Number.isFinite(interval) && interval > 0 ? interval : this.interval, 0.08, 8);
|
||||
@@ -378,8 +376,8 @@ export class SpineActor {
|
||||
if (!(hit > 0) || lead * plan.ts > hit + 1e-6) return false;
|
||||
if (!once) this.interval = iv;
|
||||
this.mode = 'attack';
|
||||
this.attackUntil = this.clock + lead + (single ? Math.max(0, loopDur - hit) / plan.ts : Math.max(0.45, iv * 1.4));
|
||||
this._play(clip.loop, !single, { timeScale: plan.tsWind, start: plan.start, mix: 0.06 });
|
||||
this.attackUntil = this.clock + lead + Math.max(0, loopDur - hit) / plan.ts;
|
||||
this._play(clip.loop, false, { timeScale: plan.tsWind, start: plan.start, mix: 0.06 });
|
||||
this.windTs = plan.ts;
|
||||
this.windUntil = this.clock + lead;
|
||||
this.wound = true;
|
||||
@@ -397,14 +395,13 @@ export class SpineActor {
|
||||
if (!clip) return;
|
||||
const loopDur = this.dur(clip.loop);
|
||||
const per = this.clipPerAttack && !once;
|
||||
const single = once || per;
|
||||
// clip per attack: its own speed (ts 1), faster only when the attacks come quicker than the clip
|
||||
const ts = once ? 1 : per ? clampN(loopDur / Math.min(this.interval, loopDur), 1, 4) : clampN(loopDur / this.interval, 0.35, 4);
|
||||
const hit = this._hitTime(clip.loop, loopDur);
|
||||
// (a clip-per-attack actor still playing the previous attack's clip was not wound up for this one)
|
||||
const wasAttacking = this.mode === 'attack' && this.current === clip.loop && (!per || this.wound);
|
||||
const wasAttacking = this.mode === 'attack' && this.current === clip.loop && this.wound;
|
||||
this.mode = 'attack';
|
||||
this.attackUntil = this.clock + (single ? Math.max(0, loopDur - hit) / ts : Math.max(0.45, this.interval * 1.4));
|
||||
this.attackUntil = this.clock + Math.max(0, loopDur - hit) / ts;
|
||||
this.windUntil = null;
|
||||
this.wound = false;
|
||||
if (!wasAttacking) {
|
||||
@@ -414,19 +411,19 @@ export class SpineActor {
|
||||
// lifts before the slam; #246 by @TsangAsuna, accepted by the owner on 2026-10-07)
|
||||
const whole = per && this.interval >= loopDur;
|
||||
if (whole) this.attackUntil = this.clock + loopDur / ts;
|
||||
this._play(clip.loop, !single, { timeScale: ts, start: whole ? 0 : hit, mix: 0.06 });
|
||||
} else if (single) {
|
||||
const e = this.spine.state.tracks[0];
|
||||
if (e) e.timeScale = ts;
|
||||
this._play(clip.loop, false, { timeScale: ts, start: whole ? 0 : hit, mix: 0.06 });
|
||||
} else {
|
||||
const e = this.spine.state.tracks[0];
|
||||
if (e) {
|
||||
e.timeScale = ts;
|
||||
// re-phase gently so the strike frame lines up with this attack
|
||||
const t = e.trackTime % loopDur;
|
||||
let d = hit - t;
|
||||
if (d > loopDur / 2) d -= loopDur; else if (d < -loopDur / 2) d += loopDur;
|
||||
e.trackTime += d * 0.8;
|
||||
// Preserve operator hit-phase correction when a coarse/batched update crossed windUntil.
|
||||
// This adjusts the prepared single clip; it does not start another visual swing.
|
||||
if (!once && !per) {
|
||||
const t = e.trackTime % loopDur;
|
||||
let d = hit - t;
|
||||
if (d > loopDur / 2) d -= loopDur; else if (d < -loopDur / 2) d += loopDur;
|
||||
e.trackTime += d * 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user