zenno/race/gate: new calculator
This commit is contained in:
+31
-2
@@ -93,6 +93,20 @@ export function distance(raceLen: number): Distance {
|
||||
return Distance.Long;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distances at which each phase of a race ends.
|
||||
* @param raceLen Length of the race in meters
|
||||
* @returns Ends of each phase in meters
|
||||
*/
|
||||
export function phases(raceLen: number) {
|
||||
const sixth = raceLen / 6;
|
||||
return {
|
||||
[Phase.EarlyRace]: sixth,
|
||||
[Phase.MidRace]: 4 * sixth,
|
||||
[Phase.LateRace]: raceLen,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Race surfaces.
|
||||
*/
|
||||
@@ -372,7 +386,13 @@ export function fullSpurtHP(raceLen: number, spurtSpeed: number, hpRate: number)
|
||||
return spurtTime * hpRate;
|
||||
}
|
||||
|
||||
function baseSpeed(raceLen: number): number {
|
||||
/**
|
||||
* Calculate the base speed for a race.
|
||||
* Note that this is not the base target speed for a horse.
|
||||
* @param raceLen Race length in meters
|
||||
* @returns Base speed for the race
|
||||
*/
|
||||
export function baseSpeed(raceLen: number): number {
|
||||
return 20 - (raceLen - 2000) / 1000;
|
||||
}
|
||||
|
||||
@@ -748,13 +768,22 @@ export function duelAccelMod(gutsStat: number): number {
|
||||
* Calculate the speed modifier for running uphill.
|
||||
* Contrary to the race mechanics document, this is expressed as a negative number.
|
||||
* @param powerStat Final power stat
|
||||
* @param slopePer Slope percentage, generally one of 0.5, 1.0, 1.5, or 2.0
|
||||
* @param slopePer Slope percentage, one of 1.0, 1.5, or 2.0
|
||||
* @returns Speed modifier for running uphill, a negative value
|
||||
*/
|
||||
export function uphillMod(powerStat: number, slopePer: number): number {
|
||||
return (slopePer * -200) / powerStat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the speed modifier while running in downhill accel mode.
|
||||
* @param slopePer Slope percentage, one of -1.0, -1.5, or -2.0
|
||||
* @returns Speed modifier for downhill accel mode
|
||||
*/
|
||||
export function downhillAccelMod(slopePer: number): number {
|
||||
return 0.3 - slopePer/10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the forward speed boost given when moving lanewise while a skill
|
||||
* that grants a lane change speed boost is active.
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Instantaneous characteristics of a horse in motion.
|
||||
*/
|
||||
export interface Instant {
|
||||
/** Timestamp of the instant. */
|
||||
t: number;
|
||||
/** Forward position along the course. */
|
||||
x: number;
|
||||
/** Actual speed. */
|
||||
v: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward travel characteristics under the effects of acceleration.
|
||||
*/
|
||||
export interface Travel {
|
||||
start: Instant;
|
||||
end: Instant;
|
||||
/** Actual acceleration value applied between start and end. */
|
||||
a: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance a horse according to its forward motion characteristics.
|
||||
* @param start Motion at the beginning of the segment
|
||||
* @param targetSpeed Target speed to accelerate to
|
||||
* @param totalAccel Total (positive) acceleration including skill bonuses
|
||||
* @param maxDur Max duration of the acceleration, or infinite if not given
|
||||
* @param maxDist Max distance of the acceleration, or infinite if not given
|
||||
* @param decel Negative acceleration value for cases where target speed is below start speed
|
||||
* @returns Travel segment after reaching the first of the given limits
|
||||
*/
|
||||
export function travelSegment(start: Instant, targetSpeed: number, totalAccel: number, maxDur?: number, maxDist?: number, decel?: number): Travel {
|
||||
const a = start.v < targetSpeed ? totalAccel : decel ?? -1;
|
||||
const dvt = (targetSpeed - start.v) / a;
|
||||
const dxt = maxDist != null ? (-start.v + Math.sqrt(start.v*start.v + 4*a*maxDist)) / (2*a) : dvt;
|
||||
const time = maxDur != null ? Math.min(dvt, dxt, maxDur) : Math.min(dvt, dxt);
|
||||
// To address numerical imprecision, if accel time comes from velocity or distance,
|
||||
// set the reached values directly.
|
||||
const v = time === dvt ? targetSpeed : start.v + time * a;
|
||||
const dist = maxDist != null && time === dxt ? maxDist : (start.v + 0.5 * a * time) * time;
|
||||
const end = { t: start.t + time, x: start.x + dist, v };
|
||||
return {start, end, a};
|
||||
}
|
||||
|
||||
/**
|
||||
* Acceleration bonus from skills.
|
||||
*/
|
||||
export interface Skill {
|
||||
/**
|
||||
* Acceleration modifier in m/s².
|
||||
*/
|
||||
accel: number;
|
||||
/**
|
||||
* Remaining actual duration in seconds.
|
||||
*/
|
||||
dur: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accelerate up to a new target speed while under the effects of acceleration skills.
|
||||
* @param startSpeed Speed at the start of the acceleration in m/s
|
||||
* @param targetSpeed Speed at which the acceleration ends in m/s
|
||||
* @param baseAccel Base acceleration value to which skills add, in m/s²
|
||||
* @param skills Skills that are active for the acceleration period
|
||||
* @returns Acceleration segments and remainders of skills upon reaching the target speed
|
||||
*/
|
||||
export function up(start: Instant, targetSpeed: number, baseAccel: number, skills: Skill[]) {
|
||||
const segments: Travel[] = [];
|
||||
const sk = skills.map((s) => ({...s})).filter((s) => s.dur > 0);
|
||||
while (start.v < targetSpeed) {
|
||||
const active = sk.filter((s) => s.dur > 0);
|
||||
if (active.length === 0) {
|
||||
segments.push(travelSegment(start, targetSpeed, baseAccel));
|
||||
break;
|
||||
}
|
||||
const boost = active.reduce((a, s) => a + s.accel, 0);
|
||||
const time = Math.min(...active.map((s) => s.dur));
|
||||
const seg = travelSegment(start, targetSpeed, baseAccel + boost, time);
|
||||
segments.push(seg);
|
||||
start = seg.end
|
||||
for (const s of active) {
|
||||
s.dur -= seg.end.t - seg.start.t;
|
||||
if (s.dur < 1e-5) {
|
||||
s.dur = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
return {segments, skills: sk}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Fundamental stats of umas.
|
||||
*/
|
||||
export enum Stat {
|
||||
speed,
|
||||
stamina,
|
||||
power,
|
||||
guts,
|
||||
wit,
|
||||
}
|
||||
|
||||
/**
|
||||
* Stats as a list for easy iteration.
|
||||
*/
|
||||
export const LIST = [Stat.speed, Stat.stamina, Stat.power, Stat.guts, Stat.wit] as const;
|
||||
|
||||
declare const __stage: unique symbol;
|
||||
|
||||
export type Stage = 'raw' | 'base' | 'adjusted' | 'final' | 'skills';
|
||||
|
||||
export type StatValue<T extends Stage> = number & {[__stage]: T};
|
||||
|
||||
export interface Stats<T extends Stage> {
|
||||
speed: StatValue<T>;
|
||||
stamina: StatValue<T>;
|
||||
power: StatValue<T>;
|
||||
guts: StatValue<T>;
|
||||
wit: StatValue<T>;
|
||||
}
|
||||
Reference in New Issue
Block a user