mirror of
https://github.com/tobspr/shapez.io.git
synced 2026-03-02 03:39:21 +00:00
Initial take on wires
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
$buildings: belt, cutter, miner, mixer, painter, rotater, splitter, stacker, trash, underground_belt;
|
||||
$buildings: belt, cutter, miner, mixer, painter, rotater, splitter, stacker, trash, underground_belt,
|
||||
energy_generator;
|
||||
|
||||
@each $building in $buildings {
|
||||
[data-icon="building_icons/#{$building}.png"] {
|
||||
@@ -27,8 +28,8 @@ $icons: notification_saved, notification_success, notification_upgrade;
|
||||
}
|
||||
}
|
||||
|
||||
$languages: en, de, cs, da, et, es-419, fr, it, pt-BR, sv, tr, el, ru, uk, zh-TW, zh-CN, nb, mt-MT, ar, nl, vi, th,
|
||||
hu, pl, ja, kor, no, pt-PT;
|
||||
$languages: en, de, cs, da, et, es-419, fr, it, pt-BR, sv, tr, el, ru, uk, zh-TW, zh-CN, nb, mt-MT, ar, nl, vi,
|
||||
th, hu, pl, ja, kor, no, pt-PT;
|
||||
|
||||
@each $language in $languages {
|
||||
[data-languageicon="#{$language}"] {
|
||||
|
||||
107
src/js/game/buildings/energy_generator.js
Normal file
107
src/js/game/buildings/energy_generator.js
Normal file
@@ -0,0 +1,107 @@
|
||||
import { enumDirection, Vector } from "../../core/vector";
|
||||
import { ItemAcceptorComponent, enumItemAcceptorItemFilter } from "../components/item_acceptor";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
import { EnergyGeneratorComponent } from "../components/energy_generator";
|
||||
import { WiredPinsComponent, enumPinSlotType } from "../components/wired_pins";
|
||||
|
||||
export class MetaEnergyGenerator extends MetaBuilding {
|
||||
constructor() {
|
||||
super("energy_generator");
|
||||
}
|
||||
|
||||
isRotateable(variant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#c425d7";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GameRoot} root
|
||||
* @param {string} variant
|
||||
* @returns {Array<[string, string]>}
|
||||
*/
|
||||
getAdditionalStatistics(root, variant) {
|
||||
// TODO
|
||||
return [];
|
||||
}
|
||||
|
||||
getDimensions(variant) {
|
||||
return new Vector(2, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
getIsUnlocked(root) {
|
||||
return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_cutter_and_trash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity at the given location
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
setupEntityComponents(entity) {
|
||||
entity.addComponent(
|
||||
new ItemAcceptorComponent({
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.top],
|
||||
filter: enumItemAcceptorItemFilter.shape,
|
||||
},
|
||||
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [enumDirection.top],
|
||||
filter: enumItemAcceptorItemFilter.shape,
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 1),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: enumItemAcceptorItemFilter.shape,
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 1),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: enumItemAcceptorItemFilter.shape,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
entity.addComponent(
|
||||
new EnergyGeneratorComponent({
|
||||
// Set by the energy generator system later
|
||||
requiredKey: null,
|
||||
})
|
||||
);
|
||||
|
||||
entity.addComponent(
|
||||
new WiredPinsComponent({
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
type: enumPinSlotType.energyEjector,
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
type: enumPinSlotType.energyEjector,
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 1),
|
||||
type: enumPinSlotType.energyEjector,
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 1),
|
||||
type: enumPinSlotType.energyEjector,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import { UndergroundBeltComponent } from "./components/underground_belt";
|
||||
import { UnremovableComponent } from "./components/unremovable";
|
||||
import { HubComponent } from "./components/hub";
|
||||
import { StorageComponent } from "./components/storage";
|
||||
import { EnergyGeneratorComponent } from "./components/energy_generator";
|
||||
import { WiredPinsComponent } from "./components/wired_pins";
|
||||
|
||||
export function initComponentRegistry() {
|
||||
gComponentRegistry.register(StaticMapEntityComponent);
|
||||
@@ -23,6 +25,8 @@ export function initComponentRegistry() {
|
||||
gComponentRegistry.register(UnremovableComponent);
|
||||
gComponentRegistry.register(HubComponent);
|
||||
gComponentRegistry.register(StorageComponent);
|
||||
gComponentRegistry.register(EnergyGeneratorComponent);
|
||||
gComponentRegistry.register(WiredPinsComponent);
|
||||
|
||||
// IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS
|
||||
|
||||
|
||||
59
src/js/game/components/energy_generator.js
Normal file
59
src/js/game/components/energy_generator.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import { types } from "../../savegame/serialization";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { Component } from "../component";
|
||||
import { ShapeItem } from "../items/shape_item";
|
||||
|
||||
const maxQueueSize = 10;
|
||||
|
||||
export class EnergyGeneratorComponent extends Component {
|
||||
static getId() {
|
||||
return "EnergyGenerator";
|
||||
}
|
||||
|
||||
static getSchema() {
|
||||
return {
|
||||
requiredKey: types.string,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} param0
|
||||
* @param {string} param0.requiredKey Which shape this generator needs, can be null if not computed yet
|
||||
*/
|
||||
constructor({ requiredKey }) {
|
||||
super();
|
||||
this.requiredKey = requiredKey;
|
||||
|
||||
/**
|
||||
* Stores how many items are ready to be converted to energy
|
||||
* @type {number}
|
||||
*/
|
||||
this.itemsInQueue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {BaseItem} item
|
||||
*/
|
||||
tryTakeItem(item) {
|
||||
if (!(item instanceof ShapeItem)) {
|
||||
// Not a shape
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.definition.getHash() !== this.requiredKey) {
|
||||
// Not our shape
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.itemsInQueue >= maxQueueSize) {
|
||||
// Queue is full
|
||||
return false;
|
||||
}
|
||||
|
||||
// Take item and put it into the queue
|
||||
++this.itemsInQueue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
65
src/js/game/components/wired_pins.js
Normal file
65
src/js/game/components/wired_pins.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Component } from "../component";
|
||||
import { Vector } from "../../core/vector";
|
||||
import { types } from "../../savegame/serialization";
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumPinSlotType = {
|
||||
energyEjector: "energyEjector",
|
||||
};
|
||||
|
||||
/** @typedef {{
|
||||
* pos: Vector,
|
||||
* type: enumPinSlotType
|
||||
* }} WirePinSlotDefinition */
|
||||
|
||||
/** @typedef {{
|
||||
* pos: Vector,
|
||||
* type: enumPinSlotType,
|
||||
* value: number
|
||||
* }} WirePinSlot */
|
||||
|
||||
export class WiredPinsComponent extends Component {
|
||||
static getId() {
|
||||
return "WiredPins";
|
||||
}
|
||||
|
||||
static getSchema() {
|
||||
return {
|
||||
slots: types.array(
|
||||
types.structured({
|
||||
pos: types.vector,
|
||||
type: types.enum(enumPinSlotType),
|
||||
value: types.float,
|
||||
})
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} param0
|
||||
* @param {Array<WirePinSlotDefinition>} param0.slots
|
||||
*/
|
||||
constructor({ slots }) {
|
||||
super();
|
||||
this.setSlots(slots);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the slots of this building
|
||||
* @param {Array<WirePinSlotDefinition>} slots
|
||||
*/
|
||||
setSlots(slots) {
|
||||
/** @type {Array<WirePinSlot>} */
|
||||
this.slots = [];
|
||||
|
||||
for (let i = 0; i < slots.length; ++i) {
|
||||
const slotData = slots[i];
|
||||
this.slots.push({
|
||||
pos: slotData.pos,
|
||||
type: slotData.type,
|
||||
value: 0.0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import { GameHUD } from "./hud/hud";
|
||||
import { KeyActionMapper } from "./key_action_mapper";
|
||||
import { GameLogic } from "./logic";
|
||||
import { MapView } from "./map_view";
|
||||
import { GameRoot } from "./root";
|
||||
import { GameRoot, enumEditMode } from "./root";
|
||||
import { ShapeDefinitionManager } from "./shape_definition_manager";
|
||||
import { SoundProxy } from "./sound_proxy";
|
||||
import { GameTime } from "./time/game_time";
|
||||
@@ -403,9 +403,17 @@ export class GameCore {
|
||||
root.map.drawForeground(params);
|
||||
if (!this.root.camera.getIsMapOverlayActive()) {
|
||||
systems.hub.draw(params);
|
||||
systems.energyGenerator.draw(params);
|
||||
systems.storage.draw(params);
|
||||
}
|
||||
|
||||
// WIRES LAYER
|
||||
root.hud.parts.wiresOverlay.draw(params);
|
||||
|
||||
if (this.root.editMode === enumEditMode.wires) {
|
||||
systems.wiredPins.drawWiresLayer(params);
|
||||
}
|
||||
|
||||
if (G_IS_DEV) {
|
||||
root.map.drawStaticEntityDebugOverlays(params);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import { UndergroundBeltComponent } from "./components/underground_belt";
|
||||
import { UnremovableComponent } from "./components/unremovable";
|
||||
import { HubComponent } from "./components/hub";
|
||||
import { StorageComponent } from "./components/storage";
|
||||
import { EnergyGeneratorComponent } from "./components/energy_generator";
|
||||
import { WiredPinsComponent } from "./components/wired_pins";
|
||||
/* typehints:end */
|
||||
|
||||
/**
|
||||
@@ -56,6 +58,12 @@ export class EntityComponentStorage {
|
||||
/** @type {StorageComponent} */
|
||||
this.Storage;
|
||||
|
||||
/** @type {EnergyGeneratorComponent} */
|
||||
this.EnergyGenerator;
|
||||
|
||||
/** @type {WiredPinsComponent} */
|
||||
this.WiredPins;
|
||||
|
||||
/* typehints:end */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import { HubSystem } from "./systems/hub";
|
||||
import { StaticMapEntitySystem } from "./systems/static_map_entity";
|
||||
import { ItemAcceptorSystem } from "./systems/item_acceptor";
|
||||
import { StorageSystem } from "./systems/storage";
|
||||
import { EnergyGeneratorSystem } from "./systems/energy_generator";
|
||||
import { WiredPinsSystem } from "./systems/wired_pins";
|
||||
|
||||
const logger = createLogger("game_system_manager");
|
||||
|
||||
@@ -56,6 +58,12 @@ export class GameSystemManager {
|
||||
/** @type {StorageSystem} */
|
||||
storage: null,
|
||||
|
||||
/** @type {EnergyGeneratorSystem} */
|
||||
energyGenerator: null,
|
||||
|
||||
/** @type {WiredPinsSystem} */
|
||||
wiredPins: null,
|
||||
|
||||
/* typehints:end */
|
||||
};
|
||||
this.systemUpdateOrder = [];
|
||||
@@ -90,8 +98,12 @@ export class GameSystemManager {
|
||||
|
||||
add("hub", HubSystem);
|
||||
|
||||
add("energyGenerator", EnergyGeneratorSystem);
|
||||
|
||||
add("staticMapEntities", StaticMapEntitySystem);
|
||||
|
||||
add("wiredPins", WiredPinsSystem);
|
||||
|
||||
// IMPORTANT: Must be after belt system since belt system can change the
|
||||
// orientation of an entity after it is placed -> the item acceptor cache
|
||||
// then would be invalid
|
||||
|
||||
@@ -36,6 +36,7 @@ import { HUDInteractiveTutorial } from "./parts/interactive_tutorial";
|
||||
import { HUDScreenshotExporter } from "./parts/screenshot_exporter";
|
||||
import { HUDColorBlindHelper } from "./parts/color_blind_helper";
|
||||
import { HUDShapeViewer } from "./parts/shape_viewer";
|
||||
import { HUDWiresOverlay } from "./parts/wires_overlay";
|
||||
|
||||
export class GameHUD {
|
||||
/**
|
||||
@@ -70,6 +71,7 @@ export class GameHUD {
|
||||
dialogs: new HUDModalDialogs(this.root),
|
||||
screenshotExporter: new HUDScreenshotExporter(this.root),
|
||||
shapeViewer: new HUDShapeViewer(this.root),
|
||||
wiresOverlay: new HUDWiresOverlay(this.root),
|
||||
};
|
||||
|
||||
this.signals = {
|
||||
|
||||
172
src/js/game/hud/parts/base_toolbar.js
Normal file
172
src/js/game/hud/parts/base_toolbar.js
Normal file
@@ -0,0 +1,172 @@
|
||||
import { gMetaBuildingRegistry } from "../../../core/global_registries";
|
||||
import { Signal } from "../../../core/signal";
|
||||
import { TrackedState } from "../../../core/tracked_state";
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||
import { MetaBuilding } from "../../meta_building";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { GameRoot } from "../../root";
|
||||
|
||||
export class HUDBaseToolbar extends BaseHUDPart {
|
||||
/**
|
||||
* @param {GameRoot} root
|
||||
* @param {Array<typeof MetaBuilding>} supportedBuildings
|
||||
* @param {function} visibilityCondition
|
||||
*/
|
||||
constructor(root, supportedBuildings, visibilityCondition) {
|
||||
super(root);
|
||||
|
||||
this.supportedBuildings = supportedBuildings;
|
||||
this.visibilityCondition = visibilityCondition;
|
||||
|
||||
/** @type {Object.<string, {
|
||||
* metaBuilding: MetaBuilding,
|
||||
* unlocked: boolean,
|
||||
* selected: boolean,
|
||||
* element: HTMLElement,
|
||||
* index: number
|
||||
* }>} */
|
||||
this.buildingHandles = {};
|
||||
|
||||
this.sigBuildingSelected = new Signal();
|
||||
this.trackedIsVisisible = new TrackedState(this.onVisibilityChanged, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the visibility of the toolbar changed
|
||||
* @param {boolean} visible
|
||||
*/
|
||||
onVisibilityChanged(visible) {
|
||||
this.element.classList.toggle("visible", visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should create all require elements
|
||||
* @param {HTMLElement} parent
|
||||
*/
|
||||
createElements(parent) {
|
||||
this.element = makeDiv(parent, "ingame_HUD_buildings_toolbar", ["ingame_buildingsToolbar"], "");
|
||||
}
|
||||
|
||||
initialize() {
|
||||
const actionMapper = this.root.keyMapper;
|
||||
|
||||
const items = makeDiv(this.element, null, ["buildings"]);
|
||||
|
||||
for (let i = 0; i < this.supportedBuildings.length; ++i) {
|
||||
const metaBuilding = gMetaBuildingRegistry.findByClass(this.supportedBuildings[i]);
|
||||
const binding = actionMapper.getBinding(KEYMAPPINGS.buildings[metaBuilding.getId()]);
|
||||
|
||||
const itemContainer = makeDiv(items, null, ["building"]);
|
||||
itemContainer.setAttribute("data-icon", "building_icons/" + metaBuilding.getId() + ".png");
|
||||
|
||||
binding.add(() => this.selectBuildingForPlacement(metaBuilding));
|
||||
|
||||
this.trackClicks(itemContainer, () => this.selectBuildingForPlacement(metaBuilding), {
|
||||
clickSound: null,
|
||||
});
|
||||
|
||||
this.buildingHandles[metaBuilding.id] = {
|
||||
metaBuilding,
|
||||
element: itemContainer,
|
||||
unlocked: false,
|
||||
selected: false,
|
||||
index: i,
|
||||
};
|
||||
}
|
||||
|
||||
this.root.hud.signals.selectedPlacementBuildingChanged.add(
|
||||
this.onSelectedPlacementBuildingChanged,
|
||||
this
|
||||
);
|
||||
|
||||
this.lastSelectedIndex = 0;
|
||||
actionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildings).add(this.cycleBuildings, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the toolbar
|
||||
*/
|
||||
update() {
|
||||
this.trackedIsVisisible.set(this.visibilityCondition());
|
||||
|
||||
if (!this.trackedIsVisisible.get()) {
|
||||
// Currently not active
|
||||
} else {
|
||||
for (const buildingId in this.buildingHandles) {
|
||||
const handle = this.buildingHandles[buildingId];
|
||||
const newStatus = handle.metaBuilding.getIsUnlocked(this.root);
|
||||
if (handle.unlocked !== newStatus) {
|
||||
handle.unlocked = newStatus;
|
||||
handle.element.classList.toggle("unlocked", newStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycles through all buildings
|
||||
*/
|
||||
cycleBuildings() {
|
||||
let newIndex = this.lastSelectedIndex;
|
||||
for (let i = 0; i < this.supportedBuildings.length; ++i, ++newIndex) {
|
||||
newIndex %= this.supportedBuildings.length;
|
||||
const metaBuilding = gMetaBuildingRegistry.findByClass(this.supportedBuildings[newIndex]);
|
||||
const handle = this.buildingHandles[metaBuilding.id];
|
||||
if (!handle.selected && handle.unlocked) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const metaBuildingClass = this.supportedBuildings[newIndex];
|
||||
const metaBuilding = gMetaBuildingRegistry.findByClass(metaBuildingClass);
|
||||
this.selectBuildingForPlacement(metaBuilding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the selected building got changed
|
||||
* @param {MetaBuilding} metaBuilding
|
||||
*/
|
||||
onSelectedPlacementBuildingChanged(metaBuilding) {
|
||||
for (const buildingId in this.buildingHandles) {
|
||||
const handle = this.buildingHandles[buildingId];
|
||||
const newStatus = handle.metaBuilding === metaBuilding;
|
||||
if (handle.selected !== newStatus) {
|
||||
handle.selected = newStatus;
|
||||
handle.element.classList.toggle("selected", newStatus);
|
||||
}
|
||||
if (handle.selected) {
|
||||
this.lastSelectedIndex = handle.index;
|
||||
}
|
||||
}
|
||||
|
||||
this.element.classList.toggle("buildingSelected", !!metaBuilding);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MetaBuilding} metaBuilding
|
||||
*/
|
||||
selectBuildingForPlacement(metaBuilding) {
|
||||
if (!this.visibilityCondition()) {
|
||||
// Not active
|
||||
return;
|
||||
}
|
||||
|
||||
if (!metaBuilding.getIsUnlocked(this.root)) {
|
||||
this.root.soundProxy.playUiError();
|
||||
return STOP_PROPAGATION;
|
||||
}
|
||||
|
||||
// Allow clicking an item again to deselect it
|
||||
for (const buildingId in this.buildingHandles) {
|
||||
const handle = this.buildingHandles[buildingId];
|
||||
if (handle.selected && handle.metaBuilding === metaBuilding) {
|
||||
metaBuilding = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.root.soundProxy.playUiClick();
|
||||
this.sigBuildingSelected.dispatch(metaBuilding);
|
||||
this.onSelectedPlacementBuildingChanged(metaBuilding);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { BaseHUDPart } from "../base_hud_part";
|
||||
import { SOUNDS } from "../../../platform/sound";
|
||||
import { MetaMinerBuilding, enumMinerVariants } from "../../buildings/miner";
|
||||
import { enumHubGoalRewards } from "../../tutorial_goals";
|
||||
import { enumEditMode } from "../../root";
|
||||
|
||||
/**
|
||||
* Contains all logic for the building placer - this doesn't include the rendering
|
||||
@@ -115,6 +116,7 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
|
||||
this.root.hud.signals.pasteBlueprintRequested.add(this.abortPlacement, this);
|
||||
this.root.signals.storyGoalCompleted.add(() => this.signals.variantChanged.dispatch());
|
||||
this.root.signals.upgradePurchased.add(() => this.signals.variantChanged.dispatch());
|
||||
this.root.signals.editModeChanged.add(this.onEditModeChanged, this);
|
||||
|
||||
// MOUSE BINDINGS
|
||||
this.root.camera.downPreHandler.add(this.onMouseDown, this);
|
||||
@@ -122,6 +124,20 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
|
||||
this.root.camera.upPostHandler.add(this.onMouseUp, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the edit mode got changed
|
||||
* @param {enumEditMode} editMode
|
||||
*/
|
||||
onEditModeChanged(editMode) {
|
||||
const metaBuilding = this.currentMetaBuilding.get();
|
||||
if (metaBuilding) {
|
||||
if (metaBuilding.getEditLayer() !== editMode) {
|
||||
// This layer doesn't fit the edit mode anymore
|
||||
this.currentMetaBuilding.set(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current base rotation for the current meta-building.
|
||||
* @returns {number}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { gMetaBuildingRegistry } from "../../../core/global_registries";
|
||||
import { Signal } from "../../../core/signal";
|
||||
import { TrackedState } from "../../../core/tracked_state";
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
import { MetaBeltBaseBuilding } from "../../buildings/belt_base";
|
||||
import { MetaCutterBuilding } from "../../buildings/cutter";
|
||||
import { MetaEnergyGenerator } from "../../buildings/energy_generator";
|
||||
import { MetaMinerBuilding } from "../../buildings/miner";
|
||||
import { MetaMixerBuilding } from "../../buildings/mixer";
|
||||
import { MetaPainterBuilding } from "../../buildings/painter";
|
||||
@@ -12,9 +9,8 @@ import { MetaSplitterBuilding } from "../../buildings/splitter";
|
||||
import { MetaStackerBuilding } from "../../buildings/stacker";
|
||||
import { MetaTrashBuilding } from "../../buildings/trash";
|
||||
import { MetaUndergroundBeltBuilding } from "../../buildings/underground_belt";
|
||||
import { MetaBuilding } from "../../meta_building";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||
import { enumEditMode } from "../../root";
|
||||
import { HUDBaseToolbar } from "./base_toolbar";
|
||||
|
||||
const toolbarBuildings = [
|
||||
MetaBeltBaseBuilding,
|
||||
@@ -27,146 +23,15 @@ const toolbarBuildings = [
|
||||
MetaMixerBuilding,
|
||||
MetaPainterBuilding,
|
||||
MetaTrashBuilding,
|
||||
MetaEnergyGenerator,
|
||||
];
|
||||
|
||||
export class HUDBuildingsToolbar extends BaseHUDPart {
|
||||
export class HUDBuildingsToolbar extends HUDBaseToolbar {
|
||||
constructor(root) {
|
||||
super(root);
|
||||
|
||||
/** @type {Object.<string, {
|
||||
* metaBuilding: MetaBuilding,
|
||||
* unlocked: boolean,
|
||||
* selected: boolean,
|
||||
* element: HTMLElement,
|
||||
* index: number
|
||||
* }>} */
|
||||
this.buildingHandles = {};
|
||||
|
||||
this.sigBuildingSelected = new Signal();
|
||||
|
||||
this.trackedIsVisisible = new TrackedState(this.onVisibilityChanged, this);
|
||||
}
|
||||
|
||||
onVisibilityChanged(visible) {
|
||||
this.element.classList.toggle("visible", visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should create all require elements
|
||||
* @param {HTMLElement} parent
|
||||
*/
|
||||
createElements(parent) {
|
||||
this.element = makeDiv(parent, "ingame_HUD_buildings_toolbar", [], "");
|
||||
}
|
||||
|
||||
initialize() {
|
||||
const actionMapper = this.root.keyMapper;
|
||||
|
||||
const items = makeDiv(this.element, null, ["buildings"]);
|
||||
|
||||
for (let i = 0; i < toolbarBuildings.length; ++i) {
|
||||
const metaBuilding = gMetaBuildingRegistry.findByClass(toolbarBuildings[i]);
|
||||
const binding = actionMapper.getBinding(KEYMAPPINGS.buildings[metaBuilding.getId()]);
|
||||
|
||||
const itemContainer = makeDiv(items, null, ["building"]);
|
||||
itemContainer.setAttribute("data-icon", "building_icons/" + metaBuilding.getId() + ".png");
|
||||
|
||||
binding.add(() => this.selectBuildingForPlacement(metaBuilding));
|
||||
|
||||
this.trackClicks(itemContainer, () => this.selectBuildingForPlacement(metaBuilding), {
|
||||
clickSound: null,
|
||||
});
|
||||
|
||||
this.buildingHandles[metaBuilding.id] = {
|
||||
metaBuilding,
|
||||
element: itemContainer,
|
||||
unlocked: false,
|
||||
selected: false,
|
||||
index: i,
|
||||
};
|
||||
}
|
||||
|
||||
this.root.hud.signals.selectedPlacementBuildingChanged.add(
|
||||
this.onSelectedPlacementBuildingChanged,
|
||||
this
|
||||
super(
|
||||
root,
|
||||
toolbarBuildings,
|
||||
() => !this.root.camera.getIsMapOverlayActive() && this.root.editMode === enumEditMode.regular
|
||||
);
|
||||
|
||||
this.lastSelectedIndex = 0;
|
||||
actionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildings).add(this.cycleBuildings, this);
|
||||
}
|
||||
|
||||
update() {
|
||||
this.trackedIsVisisible.set(!this.root.camera.getIsMapOverlayActive());
|
||||
|
||||
for (const buildingId in this.buildingHandles) {
|
||||
const handle = this.buildingHandles[buildingId];
|
||||
const newStatus = handle.metaBuilding.getIsUnlocked(this.root);
|
||||
if (handle.unlocked !== newStatus) {
|
||||
handle.unlocked = newStatus;
|
||||
handle.element.classList.toggle("unlocked", newStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cycleBuildings() {
|
||||
let newIndex = this.lastSelectedIndex;
|
||||
for (let i = 0; i < toolbarBuildings.length; ++i, ++newIndex) {
|
||||
newIndex %= toolbarBuildings.length;
|
||||
const metaBuilding = gMetaBuildingRegistry.findByClass(toolbarBuildings[newIndex]);
|
||||
const handle = this.buildingHandles[metaBuilding.id];
|
||||
if (!handle.selected && handle.unlocked) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const metaBuildingClass = toolbarBuildings[newIndex];
|
||||
const metaBuilding = gMetaBuildingRegistry.findByClass(metaBuildingClass);
|
||||
this.selectBuildingForPlacement(metaBuilding);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MetaBuilding} metaBuilding
|
||||
*/
|
||||
onSelectedPlacementBuildingChanged(metaBuilding) {
|
||||
for (const buildingId in this.buildingHandles) {
|
||||
const handle = this.buildingHandles[buildingId];
|
||||
const newStatus = handle.metaBuilding === metaBuilding;
|
||||
if (handle.selected !== newStatus) {
|
||||
handle.selected = newStatus;
|
||||
handle.element.classList.toggle("selected", newStatus);
|
||||
}
|
||||
if (handle.selected) {
|
||||
this.lastSelectedIndex = handle.index;
|
||||
}
|
||||
}
|
||||
|
||||
this.element.classList.toggle("buildingSelected", !!metaBuilding);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MetaBuilding} metaBuilding
|
||||
*/
|
||||
selectBuildingForPlacement(metaBuilding) {
|
||||
if (!metaBuilding.getIsUnlocked(this.root)) {
|
||||
this.root.soundProxy.playUiError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.root.camera.getIsMapOverlayActive()) {
|
||||
this.root.soundProxy.playUiError();
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow clicking an item again to deselect it
|
||||
for (const buildingId in this.buildingHandles) {
|
||||
const handle = this.buildingHandles[buildingId];
|
||||
if (handle.selected && handle.metaBuilding === metaBuilding) {
|
||||
metaBuilding = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.root.soundProxy.playUiClick();
|
||||
this.sigBuildingSelected.dispatch(metaBuilding);
|
||||
this.onSelectedPlacementBuildingChanged(metaBuilding);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +254,13 @@ export class HUDKeybindingOverlay extends BaseHUDPart {
|
||||
keys: [k.massSelect.massSelectCopy],
|
||||
condition: () => this.anythingSelectedOnMap,
|
||||
},
|
||||
|
||||
{
|
||||
// Switch layers
|
||||
label: T.ingame.keybindingsOverlay.switchLayers,
|
||||
keys: [k.ingame.switchLayers],
|
||||
condition: () => true,
|
||||
},
|
||||
];
|
||||
|
||||
if (!this.root.app.settings.getAllSettings().alwaysMultiplace) {
|
||||
|
||||
83
src/js/game/hud/parts/wires_overlay.js
Normal file
83
src/js/game/hud/parts/wires_overlay.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import { makeOffscreenBuffer } from "../../../core/buffer_utils";
|
||||
import { globalConfig } from "../../../core/config";
|
||||
import { DrawParameters } from "../../../core/draw_parameters";
|
||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||
import { enumEditMode } from "../../root";
|
||||
import { THEME } from "../../theme";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
|
||||
const wiresBackgroundDpi = 3;
|
||||
|
||||
export class HUDWiresOverlay extends BaseHUDPart {
|
||||
createElements(parent) {}
|
||||
|
||||
initialize() {
|
||||
// Probably not the best location, but the one which makes most sense
|
||||
this.root.keyMapper.getBinding(KEYMAPPINGS.ingame.switchLayers).add(this.switchLayers, this);
|
||||
|
||||
this.generateTilePattern();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches between layers
|
||||
*/
|
||||
switchLayers() {
|
||||
if (this.root.editMode === enumEditMode.regular) {
|
||||
this.root.editMode = enumEditMode.wires;
|
||||
} else {
|
||||
this.root.editMode = enumEditMode.regular;
|
||||
}
|
||||
this.root.signals.editModeChanged.dispatch(this.root.editMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the background pattern for the wires overlay
|
||||
*/
|
||||
generateTilePattern() {
|
||||
const dims = globalConfig.tileSize * wiresBackgroundDpi;
|
||||
const [canvas, context] = makeOffscreenBuffer(dims, dims, {
|
||||
smooth: false,
|
||||
reusable: false,
|
||||
label: "wires-tile-pattern",
|
||||
});
|
||||
|
||||
context.scale(wiresBackgroundDpi, wiresBackgroundDpi);
|
||||
context.fillStyle = THEME.map.wires.overlay;
|
||||
context.fillRect(0, 0, globalConfig.tileSize, globalConfig.tileSize);
|
||||
|
||||
const lineWidth = 1;
|
||||
|
||||
context.fillRect(0, 0, globalConfig.tileSize, lineWidth);
|
||||
context.fillRect(0, lineWidth, lineWidth, globalConfig.tileSize);
|
||||
|
||||
this.tilePatternCanvas = canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
draw(parameters) {
|
||||
if (this.root.editMode !== enumEditMode.wires) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.cachedPatternBackground) {
|
||||
this.cachedPatternBackground = parameters.context.createPattern(this.tilePatternCanvas, "repeat");
|
||||
}
|
||||
|
||||
const bounds = parameters.visibleRect;
|
||||
|
||||
const scaleFactor = 1 / wiresBackgroundDpi;
|
||||
|
||||
parameters.context.scale(scaleFactor, scaleFactor);
|
||||
parameters.context.fillStyle = this.cachedPatternBackground;
|
||||
parameters.context.fillRect(
|
||||
bounds.x / scaleFactor,
|
||||
bounds.y / scaleFactor,
|
||||
bounds.w / scaleFactor,
|
||||
bounds.h / scaleFactor
|
||||
);
|
||||
parameters.context.scale(1 / scaleFactor, 1 / scaleFactor);
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ export const KEYMAPPINGS = {
|
||||
toggleHud: { keyCode: 113 }, // F2
|
||||
exportScreenshot: { keyCode: 114 }, // F3PS
|
||||
toggleFPSInfo: { keyCode: 115 }, // F4
|
||||
|
||||
switchLayers: { keyCode: key("Y") },
|
||||
},
|
||||
|
||||
navigation: {
|
||||
@@ -53,6 +55,7 @@ export const KEYMAPPINGS = {
|
||||
mixer: { keyCode: key("8") },
|
||||
painter: { keyCode: key("9") },
|
||||
trash: { keyCode: key("0") },
|
||||
energy_generator: { keyCode: key("O") },
|
||||
},
|
||||
|
||||
placement: {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Vector, enumDirection, enumAngleToDirection } from "../core/vector";
|
||||
import { Loader } from "../core/loader";
|
||||
import { GameRoot } from "./root";
|
||||
import { AtlasSprite } from "../core/sprites";
|
||||
import { Entity } from "./entity";
|
||||
import { StaticMapEntityComponent } from "./components/static_map_entity";
|
||||
import { Vector } from "../core/vector";
|
||||
import { SOUNDS } from "../platform/sound";
|
||||
import { StaticMapEntityComponent } from "./components/static_map_entity";
|
||||
import { Entity } from "./entity";
|
||||
import { enumEditMode, GameRoot } from "./root";
|
||||
|
||||
export const defaultBuildingVariant = "default";
|
||||
|
||||
@@ -24,6 +24,14 @@ export class MetaBuilding {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the edit layer of the building
|
||||
* @returns {enumEditMode}
|
||||
*/
|
||||
getEditLayer() {
|
||||
return enumEditMode.regular;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should return the dimensions of the building
|
||||
*/
|
||||
|
||||
@@ -10,6 +10,7 @@ import { MetaStackerBuilding } from "./buildings/stacker";
|
||||
import { MetaTrashBuilding } from "./buildings/trash";
|
||||
import { MetaUndergroundBeltBuilding } from "./buildings/underground_belt";
|
||||
import { MetaHubBuilding } from "./buildings/hub";
|
||||
import { MetaEnergyGenerator } from "./buildings/energy_generator";
|
||||
|
||||
export function initMetaBuildingRegistry() {
|
||||
gMetaBuildingRegistry.register(MetaSplitterBuilding);
|
||||
@@ -23,4 +24,5 @@ export function initMetaBuildingRegistry() {
|
||||
gMetaBuildingRegistry.register(MetaBeltBaseBuilding);
|
||||
gMetaBuildingRegistry.register(MetaUndergroundBeltBuilding);
|
||||
gMetaBuildingRegistry.register(MetaHubBuilding);
|
||||
gMetaBuildingRegistry.register(MetaEnergyGenerator);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,12 @@ import { KeyActionMapper } from "./key_action_mapper";
|
||||
|
||||
const logger = createLogger("game/root");
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumEditMode = {
|
||||
regular: "regular",
|
||||
wires: "wires",
|
||||
};
|
||||
|
||||
/**
|
||||
* The game root is basically the whole game state at a given point,
|
||||
* combining all important classes. We don't have globals, but this
|
||||
@@ -124,6 +130,9 @@ export class GameRoot {
|
||||
/** @type {DynamicTickrate} */
|
||||
this.dynamicTickrate = null;
|
||||
|
||||
/** @type {enumEditMode} */
|
||||
this.editMode = enumEditMode.regular;
|
||||
|
||||
this.signals = {
|
||||
// Entities
|
||||
entityManuallyPlaced: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
@@ -155,6 +164,8 @@ export class GameRoot {
|
||||
itemProduced: /** @type {TypedSignal<[BaseItem]>} */ (new Signal()),
|
||||
|
||||
bulkOperationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||
|
||||
editModeChanged: /** @type {TypedSignal<[enumEditMode]>} */ (new Signal()),
|
||||
};
|
||||
|
||||
// RNG's
|
||||
|
||||
76
src/js/game/systems/energy_generator.js
Normal file
76
src/js/game/systems/energy_generator.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { T } from "../../translations";
|
||||
import { EnergyGeneratorComponent } from "../components/energy_generator";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { ShapeDefinition } from "../shape_definition";
|
||||
|
||||
export class EnergyGeneratorSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [EnergyGeneratorComponent]);
|
||||
}
|
||||
|
||||
draw(parameters) {
|
||||
this.forEachMatchingEntityOnScreen(parameters, this.drawEntity.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns which shape is required for a given generator
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
getShapeRequiredForGenerator(entity) {
|
||||
return "CuCuCuCu";
|
||||
}
|
||||
|
||||
update() {
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
const energyGenComp = entity.components.EnergyGenerator;
|
||||
|
||||
if (!energyGenComp.requiredKey) {
|
||||
// Compute required key for this generator
|
||||
energyGenComp.requiredKey = this.getShapeRequiredForGenerator(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
drawEntity(parameters, entity) {
|
||||
const context = parameters.context;
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
|
||||
if (!staticComp.shouldBeDrawn(parameters)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const energyGenComp = entity.components.EnergyGenerator;
|
||||
if (!energyGenComp.requiredKey) {
|
||||
// Not initialized yet
|
||||
return;
|
||||
}
|
||||
|
||||
const pos = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
|
||||
|
||||
// TESTING
|
||||
const definition = ShapeDefinition.fromShortKey(energyGenComp.requiredKey);
|
||||
definition.draw(pos.x, pos.y, parameters, 30);
|
||||
|
||||
const energyGenerated = 5;
|
||||
|
||||
// deliver: Deliver
|
||||
// toGenerateEnergy: For <x> energy
|
||||
context.font = "bold 7px GameFont";
|
||||
context.fillStyle = "#64666e";
|
||||
context.textAlign = "left";
|
||||
context.fillText(T.buildings.energy_generator.deliver.toUpperCase(), pos.x - 25, pos.y - 18);
|
||||
|
||||
context.fillText(
|
||||
T.buildings.energy_generator.toGenerateEnergy.replace("<x>", "" + energyGenerated).toUpperCase(),
|
||||
pos.x - 25,
|
||||
pos.y + 28
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -261,6 +261,14 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
|
||||
}
|
||||
}
|
||||
|
||||
const energyGeneratorComp = receiver.components.EnergyGenerator;
|
||||
if (energyGeneratorComp) {
|
||||
if (energyGeneratorComp.tryTakeItem(item)) {
|
||||
// Passed it over
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
51
src/js/game/systems/wired_pins.js
Normal file
51
src/js/game/systems/wired_pins.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { WiredPinsComponent } from "../components/wired_pins";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { Entity } from "../entity";
|
||||
import { THEME } from "../theme";
|
||||
|
||||
export class WiredPinsSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [WiredPinsComponent]);
|
||||
}
|
||||
|
||||
update() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
drawWiresLayer(parameters) {
|
||||
this.forEachMatchingEntityOnScreen(parameters, this.drawEntityPins.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
drawEntityPins(parameters, entity) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
|
||||
if (!staticComp.shouldBeDrawn(parameters)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pinsComp = entity.components.WiredPins;
|
||||
|
||||
const slots = pinsComp.slots;
|
||||
|
||||
for (let i = 0; i < slots.length; ++i) {
|
||||
const slot = slots[i];
|
||||
const tile = staticComp.localTileToWorld(slot.pos);
|
||||
|
||||
const worldPos = tile.toWorldSpaceCenterOfTile();
|
||||
|
||||
parameters.context.fillStyle = THEME.map.wires.pins[slot.type];
|
||||
parameters.context.beginCircle(worldPos.x, worldPos.y, 5);
|
||||
parameters.context.fill();
|
||||
|
||||
parameters.context.lineWidth = 2;
|
||||
parameters.context.fillStyle = "rgba(0, 0, 0, 0.1)";
|
||||
parameters.context.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,13 @@
|
||||
"chunkOverview": {
|
||||
"empty": "#444856",
|
||||
"filled": "#646b7d"
|
||||
},
|
||||
|
||||
"wires": {
|
||||
"overlay": "rgba(52, 150, 128, 0.5)",
|
||||
"pins": {
|
||||
"energyEjector": "#c425d7"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -24,6 +24,13 @@
|
||||
"chunkOverview": {
|
||||
"empty": "#a6afbb",
|
||||
"filled": "#c5ccd6"
|
||||
},
|
||||
|
||||
"wires": {
|
||||
"overlay": "rgba(52, 150, 128, 0.8)",
|
||||
"pins": {
|
||||
"energyEjector": "#c425d7"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ export const enumHubGoalRewards = {
|
||||
reward_painter_quad: "reward_painter_quad",
|
||||
reward_storage: "reward_storage",
|
||||
|
||||
reward_wires: "reward_wires",
|
||||
|
||||
reward_blueprints: "reward_blueprints",
|
||||
reward_freeplay: "reward_freeplay",
|
||||
|
||||
@@ -163,6 +165,13 @@ export const tutorialGoals = [
|
||||
{
|
||||
shape: finalGameShape,
|
||||
required: 250000,
|
||||
reward: enumHubGoalRewards.reward_wires,
|
||||
},
|
||||
|
||||
// 19
|
||||
{
|
||||
shape: finalGameShape,
|
||||
required: 1,
|
||||
reward: enumHubGoalRewards.reward_freeplay,
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user