Arrange the compat flags into categories & show compat icon in main UI
This commit is contained in:
parent
0efbefb999
commit
5a17b9022d
@ -135,7 +135,7 @@
|
||||
<h1><a href="https://github.com/eevee/lexys-labyrinth">Lexy's Labyrinth</a></h1>
|
||||
<p>— an <a href="https://github.com/eevee/lexys-labyrinth">open source</a> game by <a href="https://eev.ee/">eevee</a></p>
|
||||
<nav>
|
||||
<button id="main-compat" type="button">mode: <output>lexy</output></button>
|
||||
<button id="main-compat" type="button"><img src="icons/compat-lexy.png" alt=""> <output>lexy</output></button>
|
||||
<button id="main-options" type="button">options</button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
238
js/defs.js
238
js/defs.js
@ -135,132 +135,126 @@ export const COMPAT_RULESET_LABELS = {
|
||||
};
|
||||
export const COMPAT_RULESET_ORDER = ['lexy', 'steam', 'steam-strict', 'lynx', 'ms', 'custom'];
|
||||
// FIXME some of the names of the flags themselves kinda suck
|
||||
export const COMPAT_FLAGS = [
|
||||
// Level loading
|
||||
// TODO? /strictly/ speaking, these should be turned on for lynx+ms/lynx respectively, but then i'd
|
||||
// have to also alter the behavior of the corresponding terrain, which seems kind of silly
|
||||
{
|
||||
// TODO some ms compat things that wouldn't be too hard to add:
|
||||
// - walkers choose a random /unblocked/ direction, not just a random direction
|
||||
// - (boosting) player cooldown is /zero/ after ending a slide
|
||||
// - cleats allow walking through ice corner walls while standing on them
|
||||
// - blocks can be pushed through thin walls + ice corners
|
||||
export const COMPAT_FLAG_CATEGORIES = [{
|
||||
title: "Level loading",
|
||||
flags: [{
|
||||
key: 'no_auto_convert_ccl_popwalls',
|
||||
label: "Recessed walls under actors in CCL levels are left alone",
|
||||
label: "Recessed walls under actors are not auto-converted in CCL levels",
|
||||
rulesets: new Set(['steam-strict', 'lynx', 'ms']),
|
||||
}, {
|
||||
key: 'no_auto_convert_ccl_blue_walls',
|
||||
label: "Blue walls under blocks in CCL levels are left alone",
|
||||
label: "Blue walls under blocks are not auto-converted in CCL levels",
|
||||
rulesets: new Set(['steam-strict', 'lynx', 'ms']),
|
||||
},
|
||||
|
||||
// Core
|
||||
{
|
||||
key: 'allow_double_cooldowns',
|
||||
label: "Actors may cooldown twice in one tic",
|
||||
rulesets: new Set(['steam', 'steam-strict', 'lynx']),
|
||||
}],
|
||||
}, {
|
||||
title: "Actor behavior",
|
||||
flags: [{
|
||||
key: 'emulate_60fps',
|
||||
label: "Actors update at 60 FPS",
|
||||
rulesets: new Set(['steam', 'steam-strict']),
|
||||
}, {
|
||||
key: 'no_separate_idle_phase',
|
||||
label: "Actors teleport immediately after moving",
|
||||
rulesets: new Set(['steam', 'steam-strict']),
|
||||
}, {
|
||||
key: 'allow_double_cooldowns',
|
||||
label: "Actors may move forwards twice in one tic",
|
||||
rulesets: new Set(['steam', 'steam-strict', 'lynx']),
|
||||
}, {
|
||||
key: 'player_moves_last',
|
||||
label: "Players always move last",
|
||||
rulesets: new Set(['lynx', 'ms']),
|
||||
label: "Players always update last",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'reuse_actor_slots',
|
||||
label: "New actors reuse slots in the actor list",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'player_protected_by_items',
|
||||
label: "Players can't be trampled when standing on items",
|
||||
label: "Players can't be trampled while standing on items",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'force_lynx_animation_lengths',
|
||||
label: "Animations play at their slower Lynx duration",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
// Note that this requires no_early_push as well
|
||||
key: 'player_safe_at_decision_time',
|
||||
label: "Players can't be trampled at decision time",
|
||||
rulesets: new Set(['lynx']),
|
||||
rulesets: new Set(['lynx', 'ms']),
|
||||
}, {
|
||||
key: 'emulate_60fps',
|
||||
label: "Game runs at 60 FPS",
|
||||
rulesets: new Set(['steam', 'steam-strict']),
|
||||
}, {
|
||||
key: 'reuse_actor_slots',
|
||||
label: "Game reuses slots in the actor list",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'force_lynx_animation_lengths',
|
||||
label: "Animations use Lynx duration",
|
||||
rulesets: new Set(['lynx']),
|
||||
key: 'bonking_isnt_instant',
|
||||
label: "Bonking while sliding doesn't apply instantly",
|
||||
rulesets: new Set(['lynx', 'ms']),
|
||||
}, {
|
||||
key: 'actors_move_instantly',
|
||||
label: "Movement happens instantly",
|
||||
rulesets: new Set(['ms']),
|
||||
},
|
||||
|
||||
// Tiles
|
||||
{
|
||||
key: 'rff_actually_random',
|
||||
label: "Random force floors are actually random",
|
||||
label: "Movement is instant",
|
||||
rulesets: new Set(['ms']),
|
||||
}],
|
||||
}, {
|
||||
key: 'no_backwards_override',
|
||||
label: "Players can't override backwards on a force floor",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'traps_like_lynx',
|
||||
label: "Traps eject faster, and even when already open",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'popwalls_pop_on_arrive',
|
||||
label: "Recessed walls activate when stepped on",
|
||||
rulesets: new Set(['lynx', 'ms']),
|
||||
}, {
|
||||
key: 'blue_floors_vanish_on_arrive',
|
||||
label: "Fake blue walls vanish when stepped on",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'green_teleports_can_fail',
|
||||
label: "Green teleporters sometimes fail",
|
||||
rulesets: new Set(['steam-strict']),
|
||||
},
|
||||
|
||||
// Items
|
||||
{
|
||||
key: 'bombs_detonate_on_arrive',
|
||||
label: "Mines detonate only when stepped on",
|
||||
rulesets: new Set(['lynx', 'ms']),
|
||||
}, {
|
||||
key: 'bombs_immediately_detonate_under_players',
|
||||
label: "Mines under players detonate at level start",
|
||||
title: "Monsters",
|
||||
flags: [{
|
||||
// TODO ms needs "player doesn't block monsters", but tbh that's kind of how it should work
|
||||
// anyway, especially in combination with the ankh
|
||||
// TODO? in lynx they ignore the button while in motion too
|
||||
// TODO what about in a trap, in every game??
|
||||
// TODO what does ms do when a tank is on ice or a ff? wiki's description is wacky
|
||||
// TODO yellow tanks seem to have memory too??
|
||||
key: 'tanks_always_obey_button',
|
||||
label: "Blue tanks obey blue buttons even on clone machines",
|
||||
rulesets: new Set(['steam-strict']),
|
||||
}, {
|
||||
key: 'cloned_bowling_balls_can_be_lost',
|
||||
label: "Bowling balls on cloners are destroyed when fired at point blank",
|
||||
key: 'tanks_ignore_button_while_moving',
|
||||
label: "Blue tanks ignore blue buttons while moving",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'blobs_use_tw_prng',
|
||||
label: "Blobs use the Lynx RNG",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'teeth_target_internal_position',
|
||||
label: "Teeth pursue the cell the player is moving into",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'rff_blocks_monsters',
|
||||
label: "Monsters cannot step on random force floors",
|
||||
rulesets: new Set(['ms']),
|
||||
}, {
|
||||
key: 'fire_allows_most_monsters',
|
||||
label: "Monsters can walk into fire, except for bugs and walkers",
|
||||
rulesets: new Set(['ms']),
|
||||
}],
|
||||
}, {
|
||||
title: "Blocks",
|
||||
flags: [{
|
||||
key: 'use_legacy_hooking',
|
||||
label: "Pulling blocks with the hook happens earlier, and may prevent moving",
|
||||
rulesets: new Set(['steam', 'steam-strict']),
|
||||
}, {
|
||||
key: 'no_directly_pushing_sliding_blocks',
|
||||
label: "Pushing sliding blocks queues a move, rather than moving them right away",
|
||||
rulesets: new Set(['steam', 'steam-strict']),
|
||||
}, {
|
||||
key: 'emulate_spring_mining',
|
||||
label: "Pushing a block off a recessed wall may cause you to move into the resulting wall",
|
||||
rulesets: new Set(['steam-strict']),
|
||||
}, {
|
||||
key: 'monsters_ignore_keys',
|
||||
label: "Monsters completely ignore keys",
|
||||
rulesets: new Set(['ms']),
|
||||
},
|
||||
|
||||
// Blocks
|
||||
{
|
||||
key: 'no_early_push',
|
||||
label: "Pushing blocks happens at move time (block slapping is disabled)",
|
||||
// XXX wait but the DEFAULT behavior allows block slapping, which lynx has, so why is lynx listed here?
|
||||
rulesets: new Set(['lynx', 'ms']),
|
||||
}, {
|
||||
key: 'use_legacy_hooking',
|
||||
label: "Pulling blocks with the hook happens at decision time",
|
||||
rulesets: new Set(['steam', 'steam-strict']),
|
||||
}, {
|
||||
key: 'no_directly_pushing_sliding_blocks',
|
||||
label: "Don't directly push sliding blocks",
|
||||
rulesets: new Set(['steam', 'steam-strict']),
|
||||
}, {
|
||||
key: 'use_pgchip_ice_blocks',
|
||||
label: "Ice blocks emulate pgchip rules",
|
||||
label: "Ice blocks use pgchip rules",
|
||||
rulesets: new Set(['ms']),
|
||||
}, {
|
||||
key: 'allow_pushing_blocks_off_faux_walls',
|
||||
label: "Blocks may be pushed off of blue (fake), invisible, and revealing walls",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'emulate_spring_mining',
|
||||
label: "Spring mining is possible",
|
||||
rulesets: new Set(['steam-strict']),
|
||||
}, {
|
||||
key: 'block_splashes_dont_block',
|
||||
label: "Block splashes don't block the player",
|
||||
@ -271,50 +265,64 @@ export const COMPAT_FLAGS = [
|
||||
label: "Flicking is possible",
|
||||
rulesets: new Set(['ms']),
|
||||
*/
|
||||
},
|
||||
|
||||
// Monsters
|
||||
{
|
||||
// TODO? in lynx they ignore the button while in motion too
|
||||
// TODO what about in a trap, in every game??
|
||||
// TODO what does ms do when a tank is on ice or a ff? wiki's description is wacky
|
||||
// TODO yellow tanks seem to have memory too??
|
||||
key: 'tanks_always_obey_button',
|
||||
label: "Blue tanks on cloners obey blue buttons",
|
||||
}],
|
||||
}, {
|
||||
title: "Terrain",
|
||||
flags: [{
|
||||
key: 'green_teleports_can_fail',
|
||||
label: "Green teleporters sometimes fail",
|
||||
rulesets: new Set(['steam-strict']),
|
||||
}, {
|
||||
key: 'tanks_ignore_button_while_moving',
|
||||
label: "Blue tanks ignore blue buttons while moving",
|
||||
key: 'no_backwards_override',
|
||||
label: "Players can't override backwards on a force floor",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'blobs_use_tw_prng',
|
||||
label: "Blobs use the Tile World RNG",
|
||||
key: 'traps_like_lynx',
|
||||
label: "Traps eject faster, and eject when already open",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'teeth_target_internal_position',
|
||||
label: "Teeth target the player's internal position",
|
||||
key: 'blue_floors_vanish_on_arrive',
|
||||
label: "Fake blue walls vanish when stepped on",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'rff_blocks_monsters',
|
||||
label: "Random force floors block monsters",
|
||||
rulesets: new Set(['ms']),
|
||||
}, {
|
||||
key: 'bonking_isnt_instant',
|
||||
label: "Bonking while sliding doesn't apply instantly",
|
||||
key: 'popwalls_pop_on_arrive',
|
||||
label: "Recessed walls activate when stepped on",
|
||||
rulesets: new Set(['lynx', 'ms']),
|
||||
}, {
|
||||
key: 'fire_allows_most_monsters',
|
||||
label: "Fire doesn't block monsters, except bugs and walkers",
|
||||
key: 'rff_actually_random',
|
||||
label: "Random force floors are actually random",
|
||||
rulesets: new Set(['ms']),
|
||||
},
|
||||
];
|
||||
}],
|
||||
}, {
|
||||
title: "Items",
|
||||
flags: [{
|
||||
key: 'cloned_bowling_balls_can_be_lost',
|
||||
label: "Bowling balls on cloners are destroyed when fired at point blank",
|
||||
rulesets: new Set(['steam-strict']),
|
||||
}, {
|
||||
key: 'bombs_immediately_detonate_under_players',
|
||||
label: "Mines under players detonate when the level starts",
|
||||
rulesets: new Set(['steam-strict']),
|
||||
}, {
|
||||
key: 'bombs_detonate_on_arrive',
|
||||
label: "Mines detonate only when stepped on",
|
||||
rulesets: new Set(['lynx', 'ms']),
|
||||
}, {
|
||||
key: 'monsters_ignore_keys',
|
||||
label: "Monsters completely ignore keys",
|
||||
rulesets: new Set(['ms']),
|
||||
}],
|
||||
}];
|
||||
|
||||
|
||||
export function compat_flags_for_ruleset(ruleset) {
|
||||
let compat = {};
|
||||
for (let compatdef of COMPAT_FLAGS) {
|
||||
for (let category of COMPAT_FLAG_CATEGORIES) {
|
||||
for (let compatdef of category.flags) {
|
||||
if (compatdef.rulesets.has(ruleset)) {
|
||||
compat[compatdef.key] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return compat;
|
||||
}
|
||||
|
||||
24
js/main.js
24
js/main.js
@ -2,7 +2,7 @@
|
||||
// - steam: if a player character starts on a force floor they won't be able to make any voluntary movements until they are no longer on a force floor
|
||||
import * as fflate from './vendor/fflate.js';
|
||||
|
||||
import { COMPAT_FLAGS, COMPAT_RULESET_LABELS, COMPAT_RULESET_ORDER, INPUT_BITS, TICS_PER_SECOND, compat_flags_for_ruleset } from './defs.js';
|
||||
import { COMPAT_FLAG_CATEGORIES, COMPAT_RULESET_LABELS, COMPAT_RULESET_ORDER, INPUT_BITS, TICS_PER_SECOND, compat_flags_for_ruleset } from './defs.js';
|
||||
import * as c2g from './format-c2g.js';
|
||||
import * as dat from './format-dat.js';
|
||||
import * as format_base from './format-base.js';
|
||||
@ -3301,8 +3301,8 @@ class CompatOverlay extends DialogOverlay {
|
||||
"These are more technical settings, and as such are documented in full on ",
|
||||
mk('a', {href: 'https://github.com/eevee/lexys-labyrinth/wiki/Compatibility'}, "the project wiki"),
|
||||
"."),
|
||||
mk('p', "The short version is: Lexy mode is fine 99% of the time. If a level doesn't seem to work, try the mode for the game it's designed for. Microsoft mode is best-effort and nothing is guaranteed."),
|
||||
mk('p', "Changes won't take effect until you restart the level or change levels."),
|
||||
mk('p', "Lexy mode should be fine 99% of the time. If a level doesn't seem to work, try the mode for the game it's designed for."),
|
||||
mk('p', "Changes take effect when a level starts."),
|
||||
);
|
||||
|
||||
let button_set = mk('div.radio-faux-button-set');
|
||||
@ -3321,7 +3321,7 @@ class CompatOverlay extends DialogOverlay {
|
||||
if (ruleset === 'custom')
|
||||
return;
|
||||
|
||||
for (let compat of COMPAT_FLAGS) {
|
||||
for (let compat of this.all_compat_flags) {
|
||||
this.set(compat.key, compat.rulesets.has(ruleset));
|
||||
}
|
||||
});
|
||||
@ -3329,7 +3329,12 @@ class CompatOverlay extends DialogOverlay {
|
||||
|
||||
// TODO include the section dividers, somehow
|
||||
let list = mk('ul.compat-flags');
|
||||
for (let compat of COMPAT_FLAGS) {
|
||||
this.all_compat_flags = [];
|
||||
for (let category of COMPAT_FLAG_CATEGORIES) {
|
||||
this.all_compat_flags.push(...category.flags);
|
||||
list.append(mk('h2', category.title));
|
||||
|
||||
for (let compat of category.flags) {
|
||||
let label = mk('label',
|
||||
mk('input', {type: 'checkbox', name: compat.key}),
|
||||
mk('span.-desc', compat.label),
|
||||
@ -3347,12 +3352,13 @@ class CompatOverlay extends DialogOverlay {
|
||||
}
|
||||
list.append(mk('li', label));
|
||||
}
|
||||
}
|
||||
list.addEventListener('change', ev => {
|
||||
// If the current set of flags exactly matches one of the presets, highlight that button
|
||||
let selected_ruleset = 'custom';
|
||||
for (let ruleset of COMPAT_RULESET_ORDER) {
|
||||
let ok = true;
|
||||
for (let compat of COMPAT_FLAGS) {
|
||||
for (let compat of this.all_compat_flags) {
|
||||
if (this.root.elements[compat.key].checked !== compat.rulesets.has(ruleset)) {
|
||||
ok = false;
|
||||
break;
|
||||
@ -3372,7 +3378,7 @@ class CompatOverlay extends DialogOverlay {
|
||||
|
||||
// Populate everything to match the current settings
|
||||
this.root.elements['__ruleset__'].value = this.conductor._compat_ruleset ?? 'custom';
|
||||
for (let compat of COMPAT_FLAGS) {
|
||||
for (let compat of this.all_compat_flags) {
|
||||
this.set(compat.key, !! this.conductor.compat[compat.key]);
|
||||
}
|
||||
|
||||
@ -3397,7 +3403,7 @@ class CompatOverlay extends DialogOverlay {
|
||||
|
||||
save(permanent) {
|
||||
let flags = {};
|
||||
for (let compat of COMPAT_FLAGS) {
|
||||
for (let compat of this.all_compat_flags) {
|
||||
if (this.root.elements[compat.key].checked) {
|
||||
flags[compat.key] = true;
|
||||
}
|
||||
@ -3915,7 +3921,6 @@ class Conductor {
|
||||
document.querySelector('#main-compat').addEventListener('click', () => {
|
||||
new CompatOverlay(this).open();
|
||||
});
|
||||
document.querySelector('#main-compat output').textContent = COMPAT_RULESET_LABELS[this._compat_ruleset ?? 'custom'];
|
||||
|
||||
// Bind to the navigation headers, which list the current level pack
|
||||
// and level
|
||||
@ -4266,6 +4271,7 @@ class Conductor {
|
||||
this._compat_ruleset = ruleset;
|
||||
}
|
||||
|
||||
document.querySelector('#main-compat img').src = `icons/compat-${ruleset}.png`;
|
||||
document.querySelector('#main-compat output').textContent = COMPAT_RULESET_LABELS[ruleset];
|
||||
|
||||
this.compat = flags;
|
||||
|
||||
18
style.css
18
style.css
@ -327,6 +327,16 @@ button.--pressed,
|
||||
.dialog a:visited {
|
||||
color: hsl(255, 50%, 50%);
|
||||
}
|
||||
.dialog code {
|
||||
color: hsl(var(--main-hue), 50%, 30%);
|
||||
}
|
||||
.dialog h2 {
|
||||
color: hsl(var(--main-hue), 75%, 25%);
|
||||
border-bottom: 1px dotted hsl(var(--main-hue), 50%, 40%);
|
||||
}
|
||||
.dialog h2:nth-child(n+1) {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
dl.formgrid {
|
||||
display: grid;
|
||||
grid: auto-flow min-content / 1fr 4fr;
|
||||
@ -525,6 +535,9 @@ ul.compat-flags > li > label {
|
||||
align-items: center;
|
||||
gap: 0.25em;
|
||||
}
|
||||
ul.compat-flags > li > label > input[type=check] {
|
||||
margin: 0.25em;
|
||||
}
|
||||
ul.compat-flags > li > label > span.-desc {
|
||||
flex: 1;
|
||||
}
|
||||
@ -727,6 +740,11 @@ pre.stack-trace {
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
#main-compat > img {
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-width: 800px), (orientation: landscape) and (max-height: 600px) {
|
||||
body > header {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user