Implement all three blob modes and fix up some minor details
This makes the replays from the Steam copies of Blobnet and Nice Day play back correctly! Neato!
This commit is contained in:
parent
a07e10218e
commit
0c9a7e3d07
@ -16,6 +16,10 @@ export class StoredLevel {
|
|||||||
this.extra_chunks = [];
|
this.extra_chunks = [];
|
||||||
this.use_cc1_boots = false;
|
this.use_cc1_boots = false;
|
||||||
this.use_ccl_compat = false;
|
this.use_ccl_compat = false;
|
||||||
|
// 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;
|
||||||
|
|
||||||
this.size_x = 0;
|
this.size_x = 0;
|
||||||
this.size_y = 0;
|
this.size_y = 0;
|
||||||
|
|||||||
@ -835,7 +835,7 @@ export function parse_level(buf, number = 1) {
|
|||||||
|
|
||||||
if (view.byteLength <= 24)
|
if (view.byteLength <= 24)
|
||||||
continue;
|
continue;
|
||||||
//level.blob_behavior = view.getUint8(24, true);
|
level.blob_behavior = view.getUint8(24, true);
|
||||||
}
|
}
|
||||||
else if (type === 'MAP ' || type === 'PACK') {
|
else if (type === 'MAP ' || type === 'PACK') {
|
||||||
if (type === 'PACK') {
|
if (type === 'PACK') {
|
||||||
|
|||||||
61
js/game.js
61
js/game.js
@ -215,6 +215,13 @@ export class Level {
|
|||||||
// PRNG is initialized to zero
|
// PRNG is initialized to zero
|
||||||
this._rng1 = 0;
|
this._rng1 = 0;
|
||||||
this._rng2 = 0;
|
this._rng2 = 0;
|
||||||
|
if (this.stored_level.blob_behavior === 0) {
|
||||||
|
this._blob_modifier = 0x55;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// The other two modes are initialized to a random seed
|
||||||
|
this._blob_modifier = Math.floor(Math.random() * 256);
|
||||||
|
}
|
||||||
|
|
||||||
this.undo_stack = [];
|
this.undo_stack = [];
|
||||||
this.pending_undo = [];
|
this.pending_undo = [];
|
||||||
@ -348,11 +355,34 @@ export class Level {
|
|||||||
if (!(this._rng1 & 0x02)) --n;
|
if (!(this._rng1 & 0x02)) --n;
|
||||||
this._rng1 = (this._rng1 >> 1) | (this._rng2 & 0x80);
|
this._rng1 = (this._rng1 >> 1) | (this._rng2 & 0x80);
|
||||||
this._rng2 = (this._rng2 << 1) | (n & 0x01);
|
this._rng2 = (this._rng2 << 1) | (n & 0x01);
|
||||||
let ret = (this._rng1 ^ this._rng2) & 0xFF;
|
let ret = (this._rng1 ^ this._rng2) & 0xff;
|
||||||
console.log(ret);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Weird thing done by CC2 to make blobs... more... random
|
||||||
|
get_blob_modifier() {
|
||||||
|
let mod = this._blob_modifier;
|
||||||
|
this.pending_undo.push(() => this._blob_modifier = mod);
|
||||||
|
|
||||||
|
if (this.stored_level.blob_behavior === 1) {
|
||||||
|
// "4 patterns" just increments by 1 every time (but /after/ returning)
|
||||||
|
//this._blob_modifier = (this._blob_modifier + 1) % 4;
|
||||||
|
mod = (mod + 1) % 4;
|
||||||
|
this._blob_modifier = mod;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Other modes do this curious operation
|
||||||
|
mod *= 2;
|
||||||
|
if (mod < 255) {
|
||||||
|
mod ^= 0x1d;
|
||||||
|
}
|
||||||
|
mod &= 0xff;
|
||||||
|
this._blob_modifier = mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
// Move the game state forwards by one tic
|
// Move the game state forwards by one tic
|
||||||
advance_tic(p1_primary_direction, p1_secondary_direction) {
|
advance_tic(p1_primary_direction, p1_secondary_direction) {
|
||||||
if (this.state !== 'playing') {
|
if (this.state !== 'playing') {
|
||||||
@ -554,11 +584,9 @@ export class Level {
|
|||||||
direction_preference = [actor.direction, d.opposite];
|
direction_preference = [actor.direction, d.opposite];
|
||||||
}
|
}
|
||||||
else if (actor.type.movement_mode === 'bounce-random') {
|
else if (actor.type.movement_mode === 'bounce-random') {
|
||||||
// walker behavior: preserve current direction; if that
|
// walker behavior: preserve current direction; if that doesn't work, pick a random
|
||||||
// doesn't work, pick a random direction, even the one we
|
// direction, even the one we failed to move in (but ONLY then)
|
||||||
// failed to move in
|
direction_preference = [actor.direction, 'WALKER'];
|
||||||
// TODO unclear if this is right in cc2 as well. definitely not in ms, which chooses a legal move
|
|
||||||
direction_preference = [actor.direction, ['north', 'south', 'east', 'west'][Math.floor(Math.random() * 4)]];
|
|
||||||
}
|
}
|
||||||
else if (actor.type.movement_mode === 'pursue') {
|
else if (actor.type.movement_mode === 'pursue') {
|
||||||
// teeth behavior: always move towards the player
|
// teeth behavior: always move towards the player
|
||||||
@ -597,14 +625,27 @@ export class Level {
|
|||||||
}
|
}
|
||||||
else if (actor.type.movement_mode === 'random') {
|
else if (actor.type.movement_mode === 'random') {
|
||||||
// blob behavior: move completely at random
|
// blob behavior: move completely at random
|
||||||
// TODO cc2 has twiddles for how this works per-level, as well as the initial seed for demo playback
|
let modifier = this.get_blob_modifier();
|
||||||
direction_preference = [['north', 'south', 'east', 'west'][Math.floor(Math.random() * 4)]];
|
direction_preference = [['north', 'east', 'south', 'west'][(this.prng() + modifier) % 4]];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check which of those directions we *can*, probably, move in
|
// Check which of those directions we *can*, probably, move in
|
||||||
// TODO i think player on force floor will still have some issues here
|
// TODO i think player on force floor will still have some issues here
|
||||||
|
// FIXME probably bail earlier for stuck actors so the prng isn't advanced? what is the
|
||||||
|
// lynx behavior? also i hear something about blobs on cloners??
|
||||||
if (direction_preference && ! actor.stuck) {
|
if (direction_preference && ! actor.stuck) {
|
||||||
|
let fallback_direction;
|
||||||
for (let direction of direction_preference) {
|
for (let direction of direction_preference) {
|
||||||
|
if (direction === 'WALKER') {
|
||||||
|
// Walkers roll a random direction ONLY if their first attempt was blocked
|
||||||
|
direction = actor.direction;
|
||||||
|
let num_turns = this.prng() % 4;
|
||||||
|
for (let i = 0; i < num_turns; i++) {
|
||||||
|
direction = DIRECTIONS[direction].right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fallback_direction = direction;
|
||||||
|
|
||||||
let dest_cell = this.get_neighboring_cell(actor.cell, direction);
|
let dest_cell = this.get_neighboring_cell(actor.cell, direction);
|
||||||
if (! dest_cell)
|
if (! dest_cell)
|
||||||
continue;
|
continue;
|
||||||
@ -621,7 +662,7 @@ export class Level {
|
|||||||
// If all the decisions are blocked, actors still try the last one (and might even
|
// If all the decisions are blocked, actors still try the last one (and might even
|
||||||
// be able to move that way by the time their turn comes around!)
|
// be able to move that way by the time their turn comes around!)
|
||||||
if (actor.decision === null) {
|
if (actor.decision === null) {
|
||||||
actor.decision = direction_preference[direction_preference.length - 1];
|
actor.decision = fallback_direction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -666,8 +666,10 @@ class Player extends PrimaryView {
|
|||||||
|
|
||||||
play_demo() {
|
play_demo() {
|
||||||
this.restart_level();
|
this.restart_level();
|
||||||
this.demo_faucet = this.level.stored_level.demo[Symbol.iterator]();
|
let demo = this.level.stored_level.demo;
|
||||||
this.level.force_floor_direction = this.level.stored_level.demo.initial_force_floor_direction;
|
this.demo_faucet = demo[Symbol.iterator]();
|
||||||
|
this.level.force_floor_direction = demo.initial_force_floor_direction;
|
||||||
|
this.level._blob_modifier = demo.blob_seed;
|
||||||
// FIXME should probably start playback on first real input
|
// FIXME should probably start playback on first real input
|
||||||
this.set_state('playing');
|
this.set_state('playing');
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user