diff --git a/index.html b/index.html
index 579c177..f06ca34 100644
--- a/index.html
+++ b/index.html
@@ -81,6 +81,16 @@
+
diff --git a/js/format-c2m.js b/js/format-c2m.js
index 5311925..da4dd46 100644
--- a/js/format-c2m.js
+++ b/js/format-c2m.js
@@ -138,8 +138,8 @@ const TILE_ENCODING = {
0x49: ['swivel_nw', 'swivel_floor'],
0x4a: ['swivel_ne', 'swivel_floor'],
0x4b: ['swivel_se', 'swivel_floor'],
- // 0x4c: Time bonus : '#next'
- // 0x4d: Stopwatch : '#next'
+ 0x4c: ['stopwatch_bonus', '#next'],
+ 0x4d: ['stopwatch_toggle', '#next'],
// 0x4e: Transmogrifier :
// 0x4f: Railroad track (Modifier required, see section below) :
// 0x50: Steel wall :
@@ -163,7 +163,7 @@ const TILE_ENCODING = {
// 0x66: Mirror Melinda : '#direction', '#next'
// 0x68: Bowling ball : '#next'
// 0x69: Rover : '#direction', '#next'
- // 0x6a: Time penalty : '#next'
+ 0x6a: ['stopwatch_penalty', '#next'],
0x6b: ['#mod8?', ['floor_custom_green', 'floor_custom_pink', 'floor_custom_yellow', 'floor_custom_blue']],
0x6d: ['#thinwall/canopy', '#next'],
// 0x6f: Railroad sign : '#next'
@@ -240,7 +240,7 @@ export function parse_level(buf) {
let level = new util.StoredLevel;
let full_view = new DataView(buf);
let next_section_start = 0;
- let extra_hints;
+ let extra_hints = [];
let hint_tiles = [];
while (next_section_start < buf.byteLength) {
// Read section header and length
diff --git a/js/game.js b/js/game.js
index 95be78a..d9b7e34 100644
--- a/js/game.js
+++ b/js/game.js
@@ -178,13 +178,16 @@ export class Level {
this.player = null;
this.actors = [];
this.chips_remaining = this.stored_level.chips_required;
+ this.bonus_points = 0;
+
+ // Time
if (this.stored_level.time_limit === 0) {
this.time_remaining = null;
}
else {
- this.time_remaining = this.stored_level.time_limit;
+ this.time_remaining = this.stored_level.time_limit * 20;
}
- this.bonus_points = 0;
+ this.timer_paused = false;
this.tic_counter = 0;
// 0 to 7, indicating the first tic that teeth can move on.
// 0 is equivalent to even step; 4 is equivalent to odd step.
@@ -551,10 +554,9 @@ export class Level {
// Advance the clock
let tic_counter = this.tic_counter;
- let time_remaining = this.time_remaining;
- this.tic_counter++;
- if (this.time_remaining !== null && this.tic_counter % 20 === 0) {
- // 20 tics means one second! Tic that time down
+ this.tic_counter += 1;
+ if (this.time_remaining !== null && ! this.timer_paused) {
+ let time_remaining = this.time_remaining;
this.pending_undo.push(() => {
this.tic_counter = tic_counter;
this.time_remaining = time_remaining;
@@ -834,6 +836,36 @@ export class Level {
}
}
+ adjust_bonus(add, mult = 1) {
+ let current = this.bonus_points;
+ this.pending_undo.push(() => this.bonus_points = current);
+ this.bonus_points = Math.ceil(this.bonus_points * mult) + add;
+ }
+
+ pause_timer() {
+ if (this.time_remaining === null)
+ return;
+
+ this.pending_undo.push(() => this.timer_paused = ! this.timer_paused);
+ this.timer_paused = ! this.timer_paused;
+ }
+
+ adjust_timer(dt) {
+ let current = this.time_remaining;
+ this.pending_undo.push(() => this.time_remaining = current);
+
+ // Untimed levels become timed levels with 0 seconds remaining
+ this.time_remaining = Math.max(0, (this.time_remaining ?? 0) + dt * 20);
+ if (this.time_remaining <= 0) {
+ if (this.timer_paused) {
+ this.time_remaining = 1;
+ }
+ else {
+ this.fail("Time's up!");
+ }
+ }
+ }
+
fail(message) {
this.pending_undo.push(() => {
this.state = 'playing';
diff --git a/js/main.js b/js/main.js
index 52580d6..775a657 100644
--- a/js/main.js
+++ b/js/main.js
@@ -508,7 +508,7 @@ class Player extends PrimaryView {
this.time_el.textContent = '---';
}
else {
- this.time_el.textContent = this.level.time_remaining;
+ this.time_el.textContent = Math.ceil(this.level.time_remaining / 20);
}
this.bonus_el.textContent = this.level.bonus_points;
this.message_el.textContent = this.level.hint_shown ?? "";
@@ -557,7 +557,7 @@ class Player extends PrimaryView {
else {
this.bummer_el.textContent = "";
let base = (this.conductor.level_index + 1) * 500;
- let time = (this.level.time_remaining || 0) * 10;
+ let time = Math.ceil((this.level.time_remaining ?? 0) / 20) * 10;
this.bummer_el.append(
mk('p', "go bit buster!"),
mk('dl.score-chart',
diff --git a/js/tileset.js b/js/tileset.js
index 442512b..968fa71 100644
--- a/js/tileset.js
+++ b/js/tileset.js
@@ -188,7 +188,7 @@ export const CC2_TILESET_LAYOUT = {
swivel_se: [12, 11],
swivel_floor: [13, 11],
// TODO some kinda four-edges thing again
- // TODO stopwatch with a - sign??
+ stopwatch_penalty: [15, 11],
paramecium: {
north: [[0, 12], [1, 12], [2, 12]],
east: [[3, 12], [4, 12], [5, 12]],
@@ -205,8 +205,8 @@ export const CC2_TILESET_LAYOUT = {
walker: [0, 13],
// TODO walker animations span multiple tiles, rgh
helmet: [0, 14],
- // 14: stopwatch
- // 15: stopwatch with +
+ stopwatch_toggle: [14, 14],
+ stopwatch_bonus: [15, 14],
blob: [0, 15],
// TODO blob animations also span multiple tiles
diff --git a/js/tiletypes.js b/js/tiletypes.js
index 1350651..13bc935 100644
--- a/js/tiletypes.js
+++ b/js/tiletypes.js
@@ -392,6 +392,9 @@ const TILE_TYPES = {
}
}
}
+ if (other.type.is_player) {
+ level.adjust_bonus(0, 0.5);
+ }
},
},
thief_keys: {
@@ -406,6 +409,9 @@ const TILE_TYPES = {
}
}
}
+ if (other.type.is_player) {
+ level.adjust_bonus(0, 0.5);
+ }
},
},
forbidden: {
@@ -595,6 +601,39 @@ const TILE_TYPES = {
}
},
},
+ // Time alternation
+ stopwatch_bonus: {
+ draw_layer: LAYER_ITEM,
+ blocks_monsters: true,
+ blocks_blocks: true,
+ on_arrive(me, level, other) {
+ if (other.type.is_player) {
+ level.remove_tile(me);
+ level.adjust_timer(+10);
+ }
+ },
+ },
+ stopwatch_penalty: {
+ draw_layer: LAYER_ITEM,
+ blocks_monsters: true,
+ blocks_blocks: true,
+ on_arrive(me, level, other) {
+ if (other.type.is_player) {
+ level.remove_tile(me);
+ level.adjust_timer(-10);
+ }
+ },
+ },
+ stopwatch_toggle: {
+ draw_layer: LAYER_ITEM,
+ blocks_monsters: true,
+ blocks_blocks: true,
+ on_arrive(me, level, other) {
+ if (other.type.is_player) {
+ level.pause_timer();
+ }
+ },
+ },
// Critters
bug: {
@@ -683,6 +722,7 @@ const TILE_TYPES = {
},
// Keys
+ // Note that red and blue keys do NOT block monsters, but yellow and green DO
key_red: {
draw_layer: LAYER_ITEM,
is_item: true,
@@ -697,11 +737,15 @@ const TILE_TYPES = {
draw_layer: LAYER_ITEM,
is_item: true,
is_key: true,
+ blocks_monsters: true,
+ blocks_blocks: true,
},
key_green: {
draw_layer: LAYER_ITEM,
is_item: true,
is_key: true,
+ blocks_monsters: true,
+ blocks_blocks: true,
},
// Tools
// TODO note: ms allows blocks to pass over tools
@@ -794,15 +838,47 @@ const TILE_TYPES = {
},
score_10: {
draw_layer: LAYER_ITEM,
+ blocks_monsters: true,
+ blocks_blocks: true,
+ on_arrive(me, level, other) {
+ if (other.type.is_player) {
+ level.adjust_bonus(10);
+ }
+ level.remove_tile(me);
+ },
},
score_100: {
draw_layer: LAYER_ITEM,
+ blocks_monsters: true,
+ blocks_blocks: true,
+ on_arrive(me, level, other) {
+ if (other.type.is_player) {
+ level.adjust_bonus(100);
+ }
+ level.remove_tile(me);
+ },
},
score_1000: {
draw_layer: LAYER_ITEM,
+ blocks_monsters: true,
+ blocks_blocks: true,
+ on_arrive(me, level, other) {
+ if (other.type.is_player) {
+ level.adjust_bonus(1000);
+ }
+ level.remove_tile(me);
+ },
},
score_2x: {
draw_layer: LAYER_ITEM,
+ blocks_monsters: true,
+ blocks_blocks: true,
+ on_arrive(me, level, other) {
+ if (other.type.is_player) {
+ level.adjust_bonus(0, 2);
+ }
+ level.remove_tile(me);
+ },
},
hint: {