195 lines
5.9 KiB
JavaScript
195 lines
5.9 KiB
JavaScript
import { LAYERS } from './defs.js';
|
|
import * as util from './util.js';
|
|
|
|
export class StoredCell extends Array {
|
|
constructor() {
|
|
super(LAYERS.MAX);
|
|
}
|
|
}
|
|
|
|
export class Replay {
|
|
constructor(initial_force_floor_direction, blob_seed, inputs = null, step_parity = null, tw_seed = 0) {
|
|
this.initial_force_floor_direction = initial_force_floor_direction;
|
|
this.blob_seed = blob_seed;
|
|
this.step_parity = step_parity;
|
|
this.tw_seed = tw_seed;
|
|
this.inputs = inputs ?? new Uint8Array;
|
|
this.duration = this.inputs.length;
|
|
this.cursor = 0;
|
|
}
|
|
|
|
configure_level(level) {
|
|
level.force_floor_direction = this.initial_force_floor_direction;
|
|
level._blob_modifier = this.blob_seed;
|
|
level._tw_rng = this.tw_seed;
|
|
if (this.step_parity !== null) {
|
|
level.step_parity = this.step_parity;
|
|
}
|
|
}
|
|
|
|
get(t) {
|
|
if (this.duration <= 0) {
|
|
return 0;
|
|
}
|
|
else if (t < this.duration) {
|
|
return this.inputs[t];
|
|
}
|
|
else {
|
|
// Last input is implicitly repeated indefinitely
|
|
return this.inputs[this.duration - 1];
|
|
}
|
|
}
|
|
|
|
set(t, input) {
|
|
if (t >= this.inputs.length) {
|
|
let new_inputs = new Uint8Array(this.inputs.length + 1024);
|
|
for (let i = 0; i < this.inputs.length; i++) {
|
|
new_inputs[i] = this.inputs[i];
|
|
}
|
|
this.inputs = new_inputs;
|
|
}
|
|
this.inputs[t] = input;
|
|
if (t >= this.duration) {
|
|
this.duration = t + 1;
|
|
}
|
|
}
|
|
|
|
clone() {
|
|
let new_inputs = new Uint8Array(this.duration);
|
|
for (let i = 0; i < this.duration; i++) {
|
|
new_inputs[i] = this.inputs[i];
|
|
}
|
|
return new this.constructor(this.initial_force_floor_direction, this.blob_seed, new_inputs);
|
|
}
|
|
}
|
|
|
|
// Small shared helper methods for navigating a StoredLevel or Level
|
|
export class LevelInterface {
|
|
// Expected attributes:
|
|
// .size_x
|
|
// .size_y
|
|
// .linear_cells
|
|
scalar_to_coords(n) {
|
|
return [n % this.size_x, Math.floor(n / this.size_x)];
|
|
}
|
|
|
|
coords_to_scalar(x, y) {
|
|
return x + y * this.size_x;
|
|
}
|
|
|
|
is_point_within_bounds(x, y) {
|
|
return (x >= 0 && x < this.size_x && y >= 0 && y < this.size_y);
|
|
}
|
|
|
|
cell(x, y) {
|
|
if (this.is_point_within_bounds(x, y)) {
|
|
return this.linear_cells[this.coords_to_scalar(x, y)];
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
export class StoredLevel extends LevelInterface {
|
|
constructor(number) {
|
|
super();
|
|
// TODO still not sure this belongs here
|
|
this.number = number; // one-based
|
|
this.title = '';
|
|
this.author = '';
|
|
this.password = null;
|
|
this.hint = '';
|
|
this.chips_required = 0;
|
|
this.time_limit = 0;
|
|
this.viewport_size = 9;
|
|
this.extra_chunks = [];
|
|
this.use_cc1_boots = false;
|
|
// What we were parsed from: 'ccl', 'c2m', or null
|
|
this.format = null;
|
|
// Whether we use LL features that don't exist in CC2; null means we don't know
|
|
this.uses_ll_extensions = null;
|
|
// 0 - deterministic (PRNG + simple convolution)
|
|
// 1 - 4 patterns (default; PRNG + rotating through 0-3)
|
|
// 2 - extra random (like deterministic, but initial seed is "actually" random)
|
|
this.blob_behavior = 1;
|
|
|
|
// Lazy-loading that allows for checking existence (see methods below)
|
|
// TODO this needs a better interface, these get accessed too much atm
|
|
this._replay = null;
|
|
this._replay_data = null;
|
|
this._replay_decoder = null;
|
|
|
|
this.size_x = 0;
|
|
this.size_y = 0;
|
|
this.linear_cells = [];
|
|
|
|
// Maps of button positions to trap/cloner positions, as scalar indexes
|
|
// in the linear cell list
|
|
// TODO merge these imo
|
|
this.has_custom_connections = false;
|
|
this.custom_trap_wiring = {};
|
|
this.custom_cloner_wiring = {};
|
|
|
|
// New LL feature: custom camera regions, as lists of {x, y, width, height}
|
|
this.camera_regions = [];
|
|
}
|
|
|
|
check() {
|
|
}
|
|
|
|
get has_replay() {
|
|
return this._replay || (this._replay_data && this._replay_decoder);
|
|
}
|
|
|
|
get replay() {
|
|
if (! this._replay) {
|
|
this._replay = this._replay_decoder(this._replay_data);
|
|
}
|
|
return this._replay;
|
|
}
|
|
}
|
|
|
|
export class StoredPack {
|
|
constructor(identifier, level_loader) {
|
|
this.identifier = identifier;
|
|
this.title = "";
|
|
this._level_loader = level_loader;
|
|
|
|
// Simple objects containing keys that are usually:
|
|
// title: level title
|
|
// index: level index, used internally only
|
|
// number: level number (may not match index due to C2G shenanigans)
|
|
// error: any error received while loading the level
|
|
// bytes: Uint8Array of the encoded level data
|
|
this.level_metadata = [];
|
|
|
|
// Sparse/optional array of Replays, generally from an ancillary file like a TWS
|
|
// TODO unclear if this is a good API for this
|
|
this.level_replays = [];
|
|
}
|
|
|
|
// TODO this may or may not work sensibly when correctly following a c2g
|
|
load_level(index) {
|
|
let meta = this.level_metadata[index];
|
|
if (! meta)
|
|
throw new util.LLError(`No such level number ${index}`);
|
|
if (meta.error)
|
|
throw meta.error;
|
|
|
|
if (meta.stored_level) {
|
|
// The editor stores inflated levels at times, so respect that
|
|
return meta.stored_level;
|
|
}
|
|
|
|
// Otherwise, attempt to load the level
|
|
let stored_level = this._level_loader(meta);
|
|
if (! stored_level.has_replay && this.level_replays[index]) {
|
|
stored_level._replay = this.level_replays[index];
|
|
}
|
|
return stored_level;
|
|
}
|
|
}
|
|
|
|
export const StoredGame = StoredPack;
|