1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-12-13 10:11:50 +00:00

Add notification blocks mod example

This commit is contained in:
tobspr 2022-01-18 10:13:25 +01:00
parent 2ac34665b3
commit 27db6fe693
11 changed files with 522 additions and 187 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -61,7 +61,8 @@ $buildingsAndVariants: belt, balancer, underground_belt, underground_belt-tier2,
background-image: uiResource("res/ui/building_tutorials/virtual_processor-cutter.png") !important;
}
$icons: notification_saved, notification_success, notification_upgrade;
$icons: notification_saved, notification_success, notification_upgrade, notification_info,
notification_warning, notification_error;
@each $icon in $icons {
[data-icon="icons/#{$icon}.png"] {
/* @load-async */

View File

@ -90,6 +90,8 @@ export class GameHUD {
this.parts[partId] = new part(this.root);
}
MOD_SIGNALS.hudInitializer.dispatch(this.root);
const frag = document.createDocumentFragment();
for (const key in this.parts) {
MOD_SIGNALS.hudElementInitialized.dispatch(this.parts[key]);

View File

@ -1,7 +1,19 @@
import { THIRDPARTY_URLS } from "../../../core/config";
import { DialogWithForm } from "../../../core/modal_dialog_elements";
import { FormElementInput, FormElementItemChooser } from "../../../core/modal_dialog_forms";
import { STOP_PROPAGATION } from "../../../core/signal";
import { fillInLinkIntoTranslation } from "../../../core/utils";
import { Vector } from "../../../core/vector";
import { T } from "../../../translations";
import { BaseItem } from "../../base_item";
import { enumMouseButton } from "../../camera";
import { Entity } from "../../entity";
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../../items/boolean_item";
import { COLOR_ITEM_SINGLETONS } from "../../items/color_item";
import { BaseHUDPart } from "../base_hud_part";
import trim from "trim";
import { enumColors } from "../../colors";
import { ShapeDefinition } from "../../shape_definition";
export class HUDConstantSignalEdit extends BaseHUDPart {
initialize() {
@ -23,7 +35,7 @@ export class HUDConstantSignalEdit extends BaseHUDPart {
const constantComp = contents.components.ConstantSignal;
if (constantComp) {
if (button === enumMouseButton.left) {
this.root.systemMgr.systems.constantSignal.editConstantSignal(contents, {
this.editConstantSignal(contents, {
deleteOnCancel: false,
});
return STOP_PROPAGATION;
@ -31,4 +43,171 @@ export class HUDConstantSignalEdit extends BaseHUDPart {
}
}
}
/**
* Asks the entity to enter a valid signal code
* @param {Entity} entity
* @param {object} param0
* @param {boolean=} param0.deleteOnCancel
*/
editConstantSignal(entity, { deleteOnCancel = true }) {
if (!entity.components.ConstantSignal) {
return;
}
// Ok, query, but also save the uid because it could get stale
const uid = entity.uid;
const signal = entity.components.ConstantSignal.signal;
const signalValueInput = new FormElementInput({
id: "signalValue",
label: fillInLinkIntoTranslation(T.dialogs.editSignal.descShortKey, THIRDPARTY_URLS.shapeViewer),
placeholder: "",
defaultValue: signal ? signal.getAsCopyableKey() : "",
validator: val => this.parseSignalCode(entity, val),
});
const items = [...Object.values(COLOR_ITEM_SINGLETONS)];
if (entity.components.WiredPins) {
items.unshift(BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON);
items.push(
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(
this.root.gameMode.getBlueprintShapeKey()
)
);
} else {
// producer which can produce virtually anything
const shapes = ["CuCuCuCu", "RuRuRuRu", "WuWuWuWu", "SuSuSuSu"];
items.unshift(
...shapes.reverse().map(key => this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key))
);
}
if (this.root.gameMode.hasHub()) {
items.push(
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(
this.root.hubGoals.currentGoal.definition
)
);
}
if (this.root.hud.parts.pinnedShapes) {
items.push(
...this.root.hud.parts.pinnedShapes.pinnedShapes.map(key =>
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key)
)
);
}
const itemInput = new FormElementItemChooser({
id: "signalItem",
label: null,
items,
});
const dialog = new DialogWithForm({
app: this.root.app,
title: T.dialogs.editConstantProducer.title,
desc: T.dialogs.editSignal.descItems,
formElements: [itemInput, signalValueInput],
buttons: ["cancel:bad:escape", "ok:good:enter"],
closeButton: false,
});
this.root.hud.parts.dialogs.internalShowDialog(dialog);
// When confirmed, set the signal
const closeHandler = () => {
if (!this.root || !this.root.entityMgr) {
// Game got stopped
return;
}
const entityRef = this.root.entityMgr.findByUid(uid, false);
if (!entityRef) {
// outdated
return;
}
const constantComp = entityRef.components.ConstantSignal;
if (!constantComp) {
// no longer interesting
return;
}
if (itemInput.chosenItem) {
constantComp.signal = itemInput.chosenItem;
} else {
constantComp.signal = this.parseSignalCode(entity, signalValueInput.getValue());
}
};
dialog.buttonSignals.ok.add(() => {
closeHandler();
});
dialog.valueChosen.add(() => {
dialog.closeRequested.dispatch();
closeHandler();
});
// When cancelled, destroy the entity again
if (deleteOnCancel) {
dialog.buttonSignals.cancel.add(() => {
if (!this.root || !this.root.entityMgr) {
// Game got stopped
return;
}
const entityRef = this.root.entityMgr.findByUid(uid, false);
if (!entityRef) {
// outdated
return;
}
const constantComp = entityRef.components.ConstantSignal;
if (!constantComp) {
// no longer interesting
return;
}
this.root.logic.tryDeleteBuilding(entityRef);
});
}
}
/**
* Tries to parse a signal code
* @param {Entity} entity
* @param {string} code
* @returns {BaseItem}
*/
parseSignalCode(entity, code) {
if (!this.root || !this.root.shapeDefinitionMgr) {
// Stale reference
return null;
}
code = trim(code);
const codeLower = code.toLowerCase();
if (enumColors[codeLower]) {
return COLOR_ITEM_SINGLETONS[codeLower];
}
if (entity.components.WiredPins) {
if (code === "1" || codeLower === "true") {
return BOOL_TRUE_SINGLETON;
}
if (code === "0" || codeLower === "false") {
return BOOL_FALSE_SINGLETON;
}
}
if (ShapeDefinition.isValidShortKey(code)) {
return this.root.shapeDefinitionMgr.getShapeItemFromShortKey(code);
}
return null;
}
}

View File

@ -7,6 +7,9 @@ export const enumNotificationType = {
saved: "saved",
upgrade: "upgrade",
success: "success",
info: "info",
warning: "warning",
error: "error",
};
const notificationDuration = 3;
@ -17,14 +20,14 @@ export class HUDNotifications extends BaseHUDPart {
}
initialize() {
this.root.hud.signals.notification.add(this.onNotification, this);
this.root.hud.signals.notification.add(this.internalShowNotification, this);
/** @type {Array<{ element: HTMLElement, expireAt: number}>} */
this.notificationElements = [];
// Automatic notifications
this.root.signals.gameSaved.add(() =>
this.onNotification(T.ingame.notifications.gameSaved, enumNotificationType.saved)
this.internalShowNotification(T.ingame.notifications.gameSaved, enumNotificationType.saved)
);
}
@ -32,7 +35,7 @@ export class HUDNotifications extends BaseHUDPart {
* @param {string} message
* @param {enumNotificationType} type
*/
onNotification(message, type) {
internalShowNotification(message, type) {
const element = makeDiv(this.element, null, ["notification", "type-" + type], message);
element.setAttribute("data-icon", "icons/notification_" + type + ".png");

View File

@ -1,25 +1,16 @@
import trim from "trim";
import { THIRDPARTY_URLS } from "../../core/config";
import { DialogWithForm } from "../../core/modal_dialog_elements";
import { FormElementInput, FormElementItemChooser } from "../../core/modal_dialog_forms";
import { fillInLinkIntoTranslation } from "../../core/utils";
import { T } from "../../translations";
import { BaseItem } from "../base_item";
import { enumColors } from "../colors";
import { ConstantSignalComponent } from "../components/constant_signal";
import { Entity } from "../entity";
import { GameSystemWithFilter } from "../game_system_with_filter";
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item";
import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
import { ShapeDefinition } from "../shape_definition";
export class ConstantSignalSystem extends GameSystemWithFilter {
constructor(root) {
super(root, [ConstantSignalComponent]);
this.root.signals.entityManuallyPlaced.add(entity =>
this.editConstantSignal(entity, { deleteOnCancel: true })
);
this.root.signals.entityManuallyPlaced.add(entity => {
const editorHud = this.root.hud.parts.constantSignalEdit;
if (editorHud) {
editorHud.editConstantSignal(entity, { deleteOnCancel: true });
}
});
}
update() {
@ -34,171 +25,4 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
}
}
}
/**
* Asks the entity to enter a valid signal code
* @param {Entity} entity
* @param {object} param0
* @param {boolean=} param0.deleteOnCancel
*/
editConstantSignal(entity, { deleteOnCancel = true }) {
if (!entity.components.ConstantSignal) {
return;
}
// Ok, query, but also save the uid because it could get stale
const uid = entity.uid;
const signal = entity.components.ConstantSignal.signal;
const signalValueInput = new FormElementInput({
id: "signalValue",
label: fillInLinkIntoTranslation(T.dialogs.editSignal.descShortKey, THIRDPARTY_URLS.shapeViewer),
placeholder: "",
defaultValue: signal ? signal.getAsCopyableKey() : "",
validator: val => this.parseSignalCode(entity, val),
});
const items = [...Object.values(COLOR_ITEM_SINGLETONS)];
if (entity.components.WiredPins) {
items.unshift(BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON);
items.push(
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(
this.root.gameMode.getBlueprintShapeKey()
)
);
} else {
// producer which can produce virtually anything
const shapes = ["CuCuCuCu", "RuRuRuRu", "WuWuWuWu", "SuSuSuSu"];
items.unshift(
...shapes.reverse().map(key => this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key))
);
}
if (this.root.gameMode.hasHub()) {
items.push(
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(
this.root.hubGoals.currentGoal.definition
)
);
}
if (this.root.hud.parts.pinnedShapes) {
items.push(
...this.root.hud.parts.pinnedShapes.pinnedShapes.map(key =>
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key)
)
);
}
const itemInput = new FormElementItemChooser({
id: "signalItem",
label: null,
items,
});
const dialog = new DialogWithForm({
app: this.root.app,
title: T.dialogs.editConstantProducer.title,
desc: T.dialogs.editSignal.descItems,
formElements: [itemInput, signalValueInput],
buttons: ["cancel:bad:escape", "ok:good:enter"],
closeButton: false,
});
this.root.hud.parts.dialogs.internalShowDialog(dialog);
// When confirmed, set the signal
const closeHandler = () => {
if (!this.root || !this.root.entityMgr) {
// Game got stopped
return;
}
const entityRef = this.root.entityMgr.findByUid(uid, false);
if (!entityRef) {
// outdated
return;
}
const constantComp = entityRef.components.ConstantSignal;
if (!constantComp) {
// no longer interesting
return;
}
if (itemInput.chosenItem) {
constantComp.signal = itemInput.chosenItem;
} else {
constantComp.signal = this.parseSignalCode(entity, signalValueInput.getValue());
}
};
dialog.buttonSignals.ok.add(() => {
closeHandler();
});
dialog.valueChosen.add(() => {
dialog.closeRequested.dispatch();
closeHandler();
});
// When cancelled, destroy the entity again
if (deleteOnCancel) {
dialog.buttonSignals.cancel.add(() => {
if (!this.root || !this.root.entityMgr) {
// Game got stopped
return;
}
const entityRef = this.root.entityMgr.findByUid(uid, false);
if (!entityRef) {
// outdated
return;
}
const constantComp = entityRef.components.ConstantSignal;
if (!constantComp) {
// no longer interesting
return;
}
this.root.logic.tryDeleteBuilding(entityRef);
});
}
}
/**
* Tries to parse a signal code
* @param {Entity} entity
* @param {string} code
* @returns {BaseItem}
*/
parseSignalCode(entity, code) {
if (!this.root || !this.root.shapeDefinitionMgr) {
// Stale reference
return null;
}
code = trim(code);
const codeLower = code.toLowerCase();
if (enumColors[codeLower]) {
return COLOR_ITEM_SINGLETONS[codeLower];
}
if (entity.components.WiredPins) {
if (code === "1" || codeLower === "true") {
return BOOL_TRUE_SINGLETON;
}
if (code === "0" || codeLower === "false") {
return BOOL_FALSE_SINGLETON;
}
}
if (ShapeDefinition.isValidShortKey(code)) {
return this.root.shapeDefinitionMgr.getShapeItemFromShortKey(code);
}
return null;
}
}

View File

@ -25,6 +25,7 @@ import { KEYMAPPINGS } from "../game/key_action_mapper";
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
import { THEMES } from "../game/theme";
import { ModMetaBuilding } from "./mod_meta_building";
import { BaseHUDPart } from "../game/hud/base_hud_part";
export class ModInterface {
/**
@ -416,4 +417,15 @@ export class ModInterface {
extendClass(classHandle, extender) {
this.extendObject(classHandle.prototype, extender);
}
/**
*
* @param {string} id
* @param {new (...args) => BaseHUDPart} element
*/
registerHudElement(id, element) {
this.modLoader.signals.hudInitializer.add(root => {
root.hud.parts[id] = new element(root);
});
}
}

View File

@ -19,6 +19,8 @@ export const MOD_SIGNALS = {
hudElementInitialized: /** @type {TypedSignal<[BaseHUDPart]>} */ (new Signal()),
hudElementFinalized: /** @type {TypedSignal<[BaseHUDPart]>} */ (new Signal()),
hudInitializer: /** @type {TypedSignal<[GameRoot]>} */ (new Signal()),
gameInitialized: /** @type {TypedSignal<[GameRoot]>} */ (new Signal()),
gameLoadingStageEntered: /** @type {TypedSignal<[InGameState, string]>} */ (new Signal()),