Color blind mode

pull/258/head
tobspr 4 years ago
parent f204189fdb
commit 9a67115ba7

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1dc8775fdf5155097d6e1d60a436f48916af56eec14fb9034e71b32ad3b6f1b0
size 358896

@ -0,0 +1,18 @@
#ingame_HUD_ColorBlindBelowTileHelper {
position: absolute;
@include SuperSmallText;
color: #fff;
background: $ingameHudBg;
@include S(padding, 5px);
@include S(top, 20px);
left: 50%;
transform: translateX(-50%);
text-transform: uppercase;
&:not(.visible) {
display: none;
}
@include DarkThemeInvert;
}

@ -72,6 +72,24 @@
grid-row: 1 / 2;
}
> .infoButton {
@include S(width, 8px);
@include S(height, 8px);
background: uiResource("icons/info_button.png") center center / 95% no-repeat;
position: absolute;
opacity: 0.7;
@include S(top, 13px);
@include S(left, -7px);
@include DarkThemeInvert;
@include IncreasedClickArea(2px);
transition: opacity 0.12s ease-in-out;
z-index: 100;
&:hover {
opacity: 0.8;
}
}
&.goal,
&.blueprint {
.amountLabel::after {

@ -0,0 +1,131 @@
#ingame_HUD_ShapeViewer {
.dialogInner {
@include S(width, 160px);
}
.content {
display: flex;
flex-direction: column;
width: 100%;
align-items: center;
justify-items: center;
.seperator {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
.layer {
position: relative;
background: #eee;
@include DarkThemeOverride {
background: rgba(0, 10, 20, 0.2);
}
@include S(width, 150px);
@include S(height, 100px);
display: flex;
align-items: center;
justify-content: center;
> canvas {
@include S(width, 50px);
@include S(height, 50px);
}
.quad {
position: absolute;
width: 50%;
height: 50%;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
$arrowDims: 23px;
$spacing: 9px;
@include S(padding, 6px);
.colorLabel {
text-transform: uppercase;
@include SuperSmallText;
@include S(font-size, 9px);
}
.emptyLabel {
text-transform: uppercase;
@include SuperSmallText;
@include S(font-size, 9px);
}
&::after {
content: " ";
background: rgba(0, 10, 20, 0.5);
@include S(width, $arrowDims);
@include S(height, 1px);
position: absolute;
transform: rotate(45deg);
transform-origin: 50% 50%;
}
@include DarkThemeOverride {
&::after {
background: rgba(255, 255, 255, 0.5);
}
}
&.quad-0 {
right: 0;
top: 0;
align-items: flex-start;
justify-content: flex-end;
&::after {
@include S(left, $spacing);
@include S(bottom, $arrowDims / 2 + $spacing);
transform: rotate(-45deg);
}
}
&.quad-1 {
bottom: 0;
right: 0;
align-items: flex-end;
justify-content: flex-end;
&::after {
@include S(left, $spacing);
@include S(top, $arrowDims / 2 + $spacing);
transform: rotate(45deg);
}
}
&.quad-2 {
bottom: 0;
left: 0;
align-items: flex-end;
justify-content: flex-start;
&::after {
@include S(right, $spacing);
@include S(top, $arrowDims / 2 + $spacing);
transform: rotate(135deg);
}
}
&.quad-3 {
top: 0;
left: 0;
align-items: flex-start;
justify-content: flex-start;
&::after {
@include S(right, $spacing);
@include S(bottom, $arrowDims / 2 + $spacing);
transform: rotate(225deg);
}
}
}
}
}
}

@ -195,6 +195,25 @@
}
}
button.showInfo {
@include S(width, 11px);
@include S(height, 11px);
background: uiResource("icons/info_button.png") center center / 95% no-repeat;
position: absolute;
@include S(top, 17px);
@include S(right, 2.5px);
opacity: 0.5;
cursor: pointer;
pointer-events: all;
@include IncreasedClickArea(5px);
transition: opacity 0.12s ease-in-out;
@include DarkThemeInvert;
&:hover {
opacity: 0.6;
}
}
canvas {
@include S(width, 40px);
@include S(height, 40px);
@ -241,7 +260,7 @@
&.complete {
background-color: $colorGreenBright;
@include DarkThemeOverride {
background-color: $colorGreenBright;
}

@ -49,6 +49,8 @@
@import "ingame_hud/blueprint_placer";
@import "ingame_hud/waypoints";
@import "ingame_hud/interactive_tutorial";
@import "ingame_hud/color_blind_helper";
@import "ingame_hud/shape_viewer";
// prettier-ignore
$elements:
@ -74,6 +76,7 @@ ingame_HUD_buildings_toolbar,
ingame_HUD_BlueprintPlacer,
ingame_HUD_Waypoints_Hint,
ingame_HUD_Watermark,
ingame_HUD_ColorBlindBelowTileHelper,
// Overlays
ingame_HUD_BetaOverlay,
@ -81,6 +84,7 @@ ingame_HUD_BetaOverlay,
// Dialogs
ingame_HUD_Shop,
ingame_HUD_Statistics,
ingame_HUD_ShapeViewer,
ingame_HUD_UnlockNotification,
ingame_HUD_SettingsMenu,
ingame_HUD_ModalDialogs;

@ -3,6 +3,8 @@ export const CHANGELOG = [
version: "1.1.17",
date: "unreleased",
entries: [
"Color blind mode! You can now activate it in the settings and it will show you which color is below your cursor (Either resource or on the belt)",
"Add info buttons to all shapes so you can figure out how they are built! (And also, which colors they have)",
"Allow configuring autosave interval and disabling it in the settings",
"The smart-tunnel placement has been reworked to properly replace belts. Thus the setting has been turned on again by default",
"The soundtrack now has a higher quality on the standalone version than the web version",
@ -10,6 +12,8 @@ export const CHANGELOG = [
"Fix bug where belts in blueprints don't orient correctly (by hexy)",
"Fix camera moving weird after dragging and holding (by hexy)",
"Fix keybinding for pipette showing while pasting blueprints",
"Improve visibility of shape background in dark mode",
"Added sound when destroying a building",
"Update tutorial image for tier 2 tunnels to explain mix/match (by jimmyshadow1)",
"Prevent default actions on all keybindings in the web version so you don't accidentally use builtin browser shortcuts",
],

@ -34,7 +34,8 @@ import { HUDPartTutorialHints } from "./parts/tutorial_hints";
import { HUDWaypoints } from "./parts/waypoints";
import { HUDInteractiveTutorial } from "./parts/interactive_tutorial";
import { HUDScreenshotExporter } from "./parts/screenshot_exporter";
import { Entity } from "../entity";
import { HUDColorBlindHelper } from "./parts/color_blind_helper";
import { HUDShapeViewer } from "./parts/shape_viewer";
export class GameHUD {
/**
@ -68,6 +69,7 @@ export class GameHUD {
debugInfo: new HUDDebugInfo(this.root),
dialogs: new HUDModalDialogs(this.root),
screenshotExporter: new HUDScreenshotExporter(this.root),
shapeViewer: new HUDShapeViewer(this.root),
};
this.signals = {
@ -76,7 +78,8 @@ export class GameHUD {
shapeUnpinRequested: /** @type {TypedSignal<[string]>} */ (new Signal()),
notification: /** @type {TypedSignal<[string, enumNotificationType]>} */ (new Signal()),
buildingsSelectedForCopy: /** @type {TypedSignal<[Array<number>]>} */ (new Signal()),
pasteBlueprintRequested: new Signal(),
pasteBlueprintRequested: /** @type {TypedSignal<[]>} */ (new Signal()),
viewShapeDetailsRequested: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()),
};
if (!IS_MOBILE) {
@ -100,6 +103,10 @@ export class GameHUD {
this.parts.vignetteOverlay = new HUDVignetteOverlay(this.root);
}
if (this.root.app.settings.getAllSettings().enableColorBlindHelper) {
this.parts.colorBlindHelper = new HUDColorBlindHelper(this.root);
}
const frag = document.createDocumentFragment();
for (const key in this.parts) {
this.parts[key].createElements(frag);
@ -208,7 +215,13 @@ export class GameHUD {
* @param {DrawParameters} parameters
*/
draw(parameters) {
const partsOrder = ["waypoints", "massSelector", "buildingPlacer", "blueprintPlacer"];
const partsOrder = [
"waypoints",
"massSelector",
"buildingPlacer",
"blueprintPlacer",
"colorBlindHelper",
];
for (let i = 0; i < partsOrder.length; ++i) {
if (this.parts[partsOrder[i]]) {

@ -10,6 +10,7 @@ import { blueprintShape } from "../../upgrades";
import { BaseHUDPart } from "../base_hud_part";
import { DynamicDomAttach } from "../dynamic_dom_attach";
import { Blueprint } from "./blueprint";
import { SOUNDS } from "../../../platform/sound";
export class HUDBlueprintPlacer extends BaseHUDPart {
createElements(parent) {
@ -103,7 +104,7 @@ export class HUDBlueprintPlacer extends BaseHUDPart {
if (blueprint.tryPlace(this.root, tile)) {
const cost = blueprint.getCost();
this.root.hubGoals.takeShapeByKey(blueprintShape, cost);
this.root.soundProxy.playUi(SOUNDS.placeBuilding);
// This actually feels weird
// if (!this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placeMultiple).pressed) {
// this.currentBlueprint.set(null);

@ -10,6 +10,7 @@ import { Entity } from "../../entity";
import { KEYMAPPINGS } from "../../key_action_mapper";
import { defaultBuildingVariant, MetaBuilding } from "../../meta_building";
import { BaseHUDPart } from "../base_hud_part";
import { SOUNDS } from "../../../platform/sound";
/**
* Contains all logic for the building placer - this doesn't include the rendering
@ -215,6 +216,7 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
const contents = this.root.map.getTileContent(tile);
if (contents) {
this.root.logic.tryDeleteBuilding(contents);
this.root.soundProxy.playUi(SOUNDS.destroyBuilding);
}
}
@ -650,6 +652,7 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
const contents = this.root.map.getTileContentXY(x0, y0);
if (contents && !contents.queuedForDestroy && !contents.destroyed) {
this.root.logic.tryDeleteBuilding(contents);
this.root.soundProxy.playUi(SOUNDS.destroyBuilding);
}
} else {
this.tryPlaceCurrentBuildingAt(new Vector(x0, y0));

@ -0,0 +1,106 @@
import { BaseHUDPart } from "../base_hud_part";
import { makeDiv } from "../../../core/utils";
import { TrackedState } from "../../../core/tracked_state";
import { enumColors } from "../../colors";
import { ColorItem } from "../../items/color_item";
import { DrawParameters } from "../../../core/draw_parameters";
import { THEME } from "../../theme";
import { globalConfig } from "../../../core/config";
import { T } from "../../../translations";
export class HUDColorBlindHelper extends BaseHUDPart {
createElements(parent) {
this.belowTileIndicator = makeDiv(parent, "ingame_HUD_ColorBlindBelowTileHelper", []);
}
initialize() {
this.trackedColorBelowTile = new TrackedState(this.onColorBelowTileChanged, this);
}
/**
* Called when the color below the current tile changed
* @param {enumColors|null} color
*/
onColorBelowTileChanged(color) {
this.belowTileIndicator.classList.toggle("visible", !!color);
if (color) {
this.belowTileIndicator.innerText = T.ingame.colors[color];
}
}
/**
* Computes the color below the current tile
* @returns {enumColors}
*/
computeColorBelowTile() {
const mousePosition = this.root.app.mousePosition;
if (!mousePosition) {
// Not on screen
return null;
}
const worldPos = this.root.camera.screenToWorld(mousePosition);
const tile = worldPos.toTileSpace();
const contents = this.root.map.getTileContent(tile);
if (contents && !contents.components.Miner) {
const beltComp = contents.components.Belt;
// Check if the belt has a color item
if (beltComp) {
const firstItem = beltComp.sortedItems[0];
if (firstItem && firstItem[1] instanceof ColorItem) {
return firstItem[1].color;
}
}
// Check if we are ejecting an item, if so use that color
const ejectorComp = contents.components.ItemEjector;
if (ejectorComp) {
for (let i = 0; i < ejectorComp.slots.length; ++i) {
const slot = ejectorComp.slots[i];
if (slot.item && slot.item instanceof ColorItem) {
return slot.item.color;
}
}
}
} else {
// We hovered a lower layer, show the color there
const lowerLayer = this.root.map.getLowerLayerContentXY(tile.x, tile.y);
if (lowerLayer && lowerLayer instanceof ColorItem) {
return lowerLayer.color;
}
}
return null;
}
update() {
this.trackedColorBelowTile.set(this.computeColorBelowTile());
}
/**
* Draws the currently selected tile
* @param {DrawParameters} parameters
*/
draw(parameters) {
const mousePosition = this.root.app.mousePosition;
if (!mousePosition) {
// Not on screen
return null;
}
const below = this.computeColorBelowTile();
if (below) {
// We have something below our tile
const worldPos = this.root.camera.screenToWorld(mousePosition);
const tile = worldPos.toTileSpace().toWorldSpace();
parameters.context.strokeStyle = THEME.map.colorBlindPickerTile;
parameters.context.lineWidth = 1;
parameters.context.beginPath();
parameters.context.rect(tile.x, tile.y, globalConfig.tileSize, globalConfig.tileSize);
parameters.context.stroke();
}
}
}

@ -26,7 +26,8 @@ export class HUDPinnedShapes extends BaseHUDPart {
* amountLabel: HTMLElement,
* lastRenderedValue: string,
* element: HTMLElement,
* detector?: ClickDetector
* detector?: ClickDetector,
* infoDetector?: ClickDetector
* }>}
*/
this.handles = [];
@ -155,6 +156,10 @@ export class HUDPinnedShapes extends BaseHUDPart {
if (detector) {
detector.cleanup();
}
const infoDetector = this.handles[i].infoDetector;
if (infoDetector) {
infoDetector.cleanup();
}
}
this.handles = [];
@ -198,12 +203,24 @@ export class HUDPinnedShapes extends BaseHUDPart {
detector = new ClickDetector(element, {
consumeEvents: true,
preventDefault: true,
targetOnly: true,
});
detector.click.add(() => this.unpinShape(key));
} else {
element.classList.add("marked");
}
// Show small info icon
const infoButton = document.createElement("button");
infoButton.classList.add("infoButton");
element.appendChild(infoButton);
const infoDetector = new ClickDetector(infoButton, {
consumeEvents: true,
preventDefault: true,
targetOnly: true,
});
infoDetector.click.add(() => this.root.hud.signals.viewShapeDetailsRequested.dispatch(definition));
const amountLabel = makeDiv(element, null, ["amountLabel"], "");
const goal = this.findGoalValueForShape(key);
@ -216,6 +233,8 @@ export class HUDPinnedShapes extends BaseHUDPart {
element,
amountLabel,
lastRenderedValue: "",
detector,
infoDetector,
});
}

@ -0,0 +1,109 @@
import { BaseHUDPart } from "../base_hud_part";
import { makeDiv, removeAllChildren } from "../../../core/utils";
import { T } from "../../../translations";
import { defaultBuildingVariant } from "../../meta_building";
import { ShapeDefinition } from "../../shape_definition";
import { KEYMAPPINGS, KeyActionMapper } from "../../key_action_mapper";
import { InputReceiver } from "../../../core/input_receiver";
import { DynamicDomAttach } from "../dynamic_dom_attach";
export class HUDShapeViewer extends BaseHUDPart {
createElements(parent) {
this.background = makeDiv(parent, "ingame_HUD_ShapeViewer", ["ingameDialog"]);
// DIALOG Inner / Wrapper
this.dialogInner = makeDiv(this.background, null, ["dialogInner"]);
this.title = makeDiv(this.dialogInner, null, ["title"], T.ingame.shapeViewer.title);
this.closeButton = makeDiv(this.title, null, ["closeButton"]);
this.trackClicks(this.closeButton, this.close);
this.contentDiv = makeDiv(this.dialogInner, null, ["content"]);
}
initialize() {
this.root.hud.signals.viewShapeDetailsRequested.add(this.renderForShape, this);
this.domAttach = new DynamicDomAttach(this.root, this.background, {
attachClass: "visible",
});
this.inputReciever = new InputReceiver("shape_viewer");
this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever);
this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this);
this.close();
}
/**
* Closes the dialog
*/
close() {
this.visible = false;
document.body.classList.remove("ingameDialogOpen");
this.root.app.inputMgr.makeSureDetached(this.inputReciever);
this.update();
}
/**
* Shows the viewer for a given definition
* @param {ShapeDefinition} definition
*/
renderForShape(definition) {
this.visible = true;
document.body.classList.add("ingameDialogOpen");
this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever);
removeAllChildren(this.contentDiv);
const layers = definition.layers;
for (let i = 0; i < layers.length; ++i) {
const layerElem = makeDiv(this.contentDiv, null, ["layer", "layer-" + i]);
let fakeLayers = [];
for (let k = 0; k < i; ++k) {
fakeLayers.push([null, null, null, null]);
}
fakeLayers.push(layers[i]);
const thisLayerOnly = new ShapeDefinition({ layers: fakeLayers });
const thisLayerCanvas = thisLayerOnly.generateAsCanvas(160);
layerElem.appendChild(thisLayerCanvas);
for (let quad = 0; quad < 4; ++quad) {
const quadElem = makeDiv(layerElem, null, ["quad", "quad-" + quad]);
const contents = layers[i][quad];
if (contents) {
const colorLabelElem = makeDiv(
quadElem,
null,
["colorLabel"],
T.ingame.colors[contents.color]
);
} else {
const emptyLabelElem = makeDiv(
quadElem,
null,
["emptyLabel"],
T.ingame.shapeViewer.empty
);
}
}
if (i < layers.length - 1) {
makeDiv(this.contentDiv, null, ["seperator"], "+");
}
}
}
/**
* Cleans up everything
*/
cleanup() {
document.body.classList.remove("ingameDialogOpen");
}
update() {
this.domAttach.update(this.visible);
}
}

@ -79,6 +79,7 @@ export class HUDShop extends BaseHUDPart {
const requiredHandle = handle.requireIndexToElement[i];
requiredHandle.container.remove();
requiredHandle.pinDetector.cleanup();
requiredHandle.infoDetector.cleanup();
}
// Cleanup
@ -122,6 +123,10 @@ export class HUDShop extends BaseHUDPart {
pinButton.classList.add("pin");
container.appendChild(pinButton);
const viewInfoButton = document.createElement("button");
viewInfoButton.classList.add("showInfo");
container.appendChild(viewInfoButton);
const currentGoalShape = this.root.hubGoals.currentGoal.definition.getHash();
if (shape === currentGoalShape) {
pinButton.classList.add("isGoal");
@ -145,6 +150,14 @@ export class HUDShop extends BaseHUDPart {
}
});
const infoDetector = new ClickDetector(viewInfoButton, {
consumeEvents: true,
preventDefault: true,
});
infoDetector.click.add(() =>
this.root.hud.signals.viewShapeDetailsRequested.dispatch(shapeDef)
);
handle.requireIndexToElement.push({
container,
progressLabel,
@ -152,6 +165,7 @@ export class HUDShop extends BaseHUDPart {
definition: shapeDef,
required: amount,
pinDetector,
infoDetector,
});
});
}
@ -202,6 +216,7 @@ export class HUDShop extends BaseHUDPart {
const requiredHandle = handle.requireIndexToElement[i];
requiredHandle.container.remove();
requiredHandle.pinDetector.cleanup();
requiredHandle.infoDetector.cleanup();
}
handle.requireIndexToElement = [];
}

@ -333,7 +333,7 @@ export class ShapeDefinition extends BasicSerializableObject {
const quadrantSize = 10;
const quadrantHalfSize = quadrantSize / 2;
context.fillStyle = "rgba(40, 50, 65, 0.1)";
context.fillStyle = THEME.items.circleBackground;
context.beginCircle(0, 0, quadrantSize * 1.15);
context.fill();

@ -8,7 +8,7 @@ import { SOUNDS } from "../platform/sound";
const avgSoundDurationSeconds = 0.25;
const maxOngoingSounds = 2;
const maxOngoingUiSounds = 25;
const maxOngoingUiSounds = 10;
// Proxy to the application sound instance
export class SoundProxy {

@ -12,6 +12,8 @@
"directionLock": "rgb(74, 237, 134)",
"directionLockTrack": "rgba(74, 237, 134, 0.2)",
"colorBlindPickerTile": "rgba(255, 255, 255, 0.5)",
"resources": {
"shape": "#3d3f4a",
"red": "#4a3d3f",
@ -26,6 +28,7 @@
"items": {
"outline": "#111418",
"outlineWidth": 0.75
"outlineWidth": 0.75,
"circleBackground": "rgba(20, 30, 40, 0.3)"
}
}

@ -12,6 +12,8 @@
"directionLock": "rgb(74, 237, 134)",
"directionLockTrack": "rgba(74, 237, 134, 0.2)",
"colorBlindPickerTile": "rgba(50, 50, 50, 0.4)",
"resources": {
"shape": "#eaebec",
"red": "#ffbfc1",
@ -27,6 +29,7 @@
"items": {
"outline": "#55575a",
"outlineWidth": 0.75
"outlineWidth": 0.75,
"circleBackground": "rgba(40, 50, 65, 0.1)"
}
}

@ -22,6 +22,7 @@ export const SOUNDS = {
levelComplete: "level_complete",
destroyBuilding: "destroy_building",
placeBuilding: "place_building",
placeBelt: "place_belt",
};

@ -172,6 +172,15 @@ export const allApplicationSettings = [
(app, value) => app.sound.setMusicMuted(value)
),
new BoolSetting(
"enableColorBlindHelper",
categoryApp,
/**
* @param {Application} app
*/
(app, value) => null
),
// GAME
new BoolSetting("offerHints", categoryGame, (app, value) => {}),
@ -269,6 +278,8 @@ class SettingsStorage {
this.compactBuildingInfo = false;
this.disableCutDeleteWarnings = false;
this.enableColorBlindHelper = false;
/**
* @type {Object.<string, number>}
*/
@ -468,7 +479,7 @@ export class ApplicationSettings extends ReadWriteProxy {
}
getCurrentVersion() {
return 16;
return 17;
}
/** @param {{settings: SettingsStorage, version: number}} data */
@ -536,6 +547,11 @@ export class ApplicationSettings extends ReadWriteProxy {
data.version = 16;
}
if (data.version < 17) {
data.settings.enableColorBlindHelper = false;
data.version = 17;
}
return ExplainedResult.good();
}
}

@ -290,6 +290,17 @@ ingame:
clearSelection: Clear Selection
pipette: Pipette
# Names of the colors, used for the color blind mode
colors:
red: Red
green: Green
blue: Blue
yellow: Yellow
purple: Purple
cyan: Cyan
white: White
uncolored: No color
# Everything related to placing buildings (I.e. as soon as you selected a building
# from the toolbar)
buildingPlacement:
@ -384,6 +395,11 @@ ingame:
description: Left-click a marker to jump to it, right-click to delete it.<br><br>Press <keybinding> to create a marker from the current view, or <strong>right-click</strong> to create a marker at the selected location.
creationSuccessNotification: Marker has been created.
# Shape viewer
shapeViewer:
title: Layers
empty: Empty
# Interactive tutorial
interactiveTutorial:
title: Tutorial
@ -652,6 +668,11 @@ settings:
description: >-
Change the language. All translations are user contributed and might be incomplete!
enableColorBlindHelper:
title: Color Blind Mode
description: >-
Enables various tools which allow to play the game if you are color blind.
fullscreen:
title: Fullscreen
description: >-

Loading…
Cancel
Save