1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2026-03-02 03:39:21 +00:00

Color blind mode

This commit is contained in:
tobspr
2020-06-22 14:32:24 +02:00
parent f204189fdb
commit 9a67115ba7
22 changed files with 518 additions and 11 deletions

View File

@@ -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]]) {

View File

@@ -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);

View File

@@ -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));

View File

@@ -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();
}
}
}

View File

@@ -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,
});
}

View File

@@ -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);
}
}

View File

@@ -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 = [];
}

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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)"
}
}

View File

@@ -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)"
}
}