Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,10 @@ <h3 class="group-title">Machines amount</h3>
</div>
</div>

<div class="row">
<div class="row" id="overclock-label">
<h3 class="group-title">Overclock (every machine)</h3>
</div>
<div class="row">
<div class="row" id="overclock-config">
<div class="table overclock">
<div class="column multipliers">
<div class="title">Multiplier</div>
Expand Down
14 changes: 14 additions & 0 deletions dist/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -1638,6 +1638,7 @@ div#resources-summary div.content {
#saves-loader div.loaded-plan:hover,
#saves-loader .plan-selector div.plan-name:hover,
#saves-loader .plan-selector div.delete-button:hover,
#saves-loader .plan-selector div.import-button:hover,
#saves-loader .plan-selector.selected div.plan-name {
background-color: var(--background-lv2);
}
Expand Down Expand Up @@ -1665,6 +1666,15 @@ div#resources-summary div.content {
min-height: 48px;
}

#saves-loader .plan-selector div.import-button {
display: flex;
align-items: center;
justify-content: center;

min-width: 48px;
min-height: 48px;
}

#saves-loader div.collapsible-container {
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -1709,6 +1719,10 @@ div#resources-summary div.content {
cursor: pointer;
}

#saves-loader .plan-selector div.import-button {
cursor: pointer;
}

#saves-loader div.create-new {
display: flex;
align-items: stretch;
Expand Down
87 changes: 76 additions & 11 deletions src/AppData.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { GameRecipe } from "./GameData/GameRecipe";
import { SankeyNode } from "./Sankey/SankeyNode";

export class AppData extends EventTarget
Expand Down Expand Up @@ -90,23 +91,32 @@ export class AppData extends EventTarget
public loadDatabasePlan(planName: string): void
{
this.currentPlanName = planName;
let dataEncoded = this.getEncodedPlanFromDatabase(planName);
if (dataEncoded !== "")
{
this.loadFromEncoded(dataEncoded);
return;
}

if (planName !== "")
{
// If suitable plan wasn't found, load the "None" one.
this.loadDatabasePlan("");
}
}

public getEncodedPlanFromDatabase(planName: string): string
{
for (const dbPlanName in this._database.plans)
{
let dataEncoded = this._database.plans[planName];

if (dbPlanName === planName)
{
this.loadFromEncoded(dataEncoded);
return;
return dataEncoded;
}
}

if (planName !== "")
{
// If suitable plan wasn't found, load the "None" one.
this.loadDatabasePlan("");
}
return "";
}

public deleteDatabasePlan(planName: string)
Expand Down Expand Up @@ -157,16 +167,22 @@ export class AppData extends EventTarget
return JSON.stringify(AppData.objToArray(data));
}


public static getSerializableData(json: string): AppData.SerializableData
{
let parsedJson: any[] = JSON.parse(json);

return this.dataFromArray(parsedJson);
}

private saveToUrl(dataEncoded: string): void
{
location.hash = dataEncoded;
}

private loadFromJson(json: string)
{
let parsedJson: any[] = JSON.parse(json);

let data = AppData.dataFromArray(parsedJson);
let data = AppData.getSerializableData(json)

let nodeIds = new Map<number, SankeyNode>();

Expand Down Expand Up @@ -277,6 +293,16 @@ export class AppData extends EventTarget
nodes: [],
};

let defaultRecipe: AppData.SerializableCustomRecipe = {
ingredients: [],
products: []
};

let defaultResources: AppData.SerializableRecipeResource = {
id: "",
amount: 0
};

let defaultNode: AppData.SerializableNode = {
id: 0,
recipeId: "",
Expand All @@ -285,6 +311,9 @@ export class AppData extends EventTarget
positionX: 0,
positionY: 0,
outputsGroups: [],
recipeType: "",
customRecipe: defaultRecipe,
customPower: 0
};

let defaultGroup: AppData.SerializableSlotsGroup = {
Expand Down Expand Up @@ -315,6 +344,24 @@ export class AppData extends EventTarget
= this.objFromArray(data.nodes[nodeIndex].outputsGroups[groupIndex].connectedOutputs[slotIndex] as unknown as any[], defaultSlot);
}
}

// Custom recipes
if (data.nodes[nodeIndex].recipeType === "LinkedFactory")
{
data.nodes[nodeIndex].customRecipe
= this.objFromArray(data.nodes[nodeIndex].customRecipe as unknown as any[], defaultRecipe);

for (let ingredIndex = 0; ingredIndex < data.nodes[nodeIndex].customRecipe.ingredients.length; ++ingredIndex)
{
data.nodes[nodeIndex].customRecipe.ingredients[ingredIndex]
= this.objFromArray(data.nodes[nodeIndex].customRecipe.ingredients[ingredIndex] as unknown as any[], defaultResources);
}
for (let productIdx = 0; productIdx < data.nodes[nodeIndex].customRecipe.products.length; ++productIdx)
{
data.nodes[nodeIndex].customRecipe.products[productIdx]
= this.objFromArray(data.nodes[nodeIndex].customRecipe.products[productIdx] as unknown as any[], defaultResources);
}
}
}

return data;
Expand Down Expand Up @@ -380,6 +427,16 @@ export namespace AppData
connectedOutputs: SerializableConnectedSlot[],
};

export type SerializableRecipeResource = {
id: string,
amount: number,
}

export type SerializableCustomRecipe = {
ingredients: SerializableRecipeResource[],
products: SerializableRecipeResource[],
}

export type SerializableNode = {
id: number,

Expand All @@ -392,6 +449,14 @@ export namespace AppData
positionY: number,

outputsGroups: SerializableSlotsGroup[],

// The type of recipe that is being saved (GameRecipe, LinkedFactory)
recipeType: string,

// These fields are populated for arbitrary nodes that aren't represented by any "real" recipe or factory.
// These are also populated in addition to the "linkedFactory" to allow URL sharing even when the canvas includes a linkedFactory
customRecipe: SerializableCustomRecipe,
customPower: number,
};
};

Expand Down
16 changes: 16 additions & 0 deletions src/CustomData/CustomMachine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Machine } from "../Machine";

export class CustomMachine implements Machine
{
public constructor(
public iconPath: string,
public displayName: string,
public powerConsumption: number,
public powerConsumptionExponent: number = 1,
) { }

public static getPlaceholderMachine(displayName: string, customPower: number): CustomMachine {
// Temp placeholder.
return new CustomMachine("Buildable/Factory/SmelterMk1/SmelterMk1.png", displayName, customPower);
}
}
47 changes: 47 additions & 0 deletions src/CustomData/LinkedFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { AppData } from "../AppData";
import { Machine } from "../Machine";
import { Recipe } from "../Recipe";
import { CustomMachine } from "./CustomMachine";

// Container for data that represents a saved factory that has been loaded into a separate factory.
export class LinkedFactory implements Recipe
{
private _machine: Machine;
// LinkedFactories are always reported in items per minute
public manufacturingDuration: number = 60;

public constructor(
public id: string,
public displayName: string,
public ingredients: RecipeResource[],
public products: RecipeResource[],
public customPower: number,
) {

let machine = CustomMachine.getPlaceholderMachine("Temp",customPower);
this._machine = machine;
}

public getRecipeType(): string {
return "LinkedFactory";
}

public getMachine(): Machine
{
return this._machine;
}

public toSerializable(): AppData.SerializableCustomRecipe
{
return {
"ingredients": this.ingredients,
"products": this.products
}
}

public static fromSerializable(id: string, serialized: AppData.SerializableCustomRecipe, customPower: number): LinkedFactory
{
let factory = new LinkedFactory(id, id, serialized.ingredients, serialized.products, customPower);
return factory;
}
}
94 changes: 94 additions & 0 deletions src/FactoryImporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { AppData } from "./AppData";
import { LinkedFactory } from "./CustomData/LinkedFactory";
import { GameRecipe } from "./GameData/GameRecipe";
import { Recipe } from "./Recipe";

export class FactoryImporter extends EventTarget
{
public static readonly factoryImportedEvent = "factory-imported";

private static _instance = new FactoryImporter();

public static get instance(): FactoryImporter
{
return FactoryImporter._instance;
}

public static importSavedFactory(factoryToImport: string): void
{
let pageCenter = {
x: document.documentElement.clientWidth / 2,
y: document.documentElement.clientHeight / 2
};


let encodedData = AppData.instance.getEncodedPlanFromDatabase(factoryToImport);
let jsonData = atob(decodeURI(encodedData));

// Process the factory string to get the inputs/outputs
let data = AppData.getSerializableData(jsonData);
let loadedFactoryNodes = data.nodes

let resources = {
"input": new Map<string, number>(),
"output": new Map<string, number>(),
}

let powerConsumption = 0;
loadedFactoryNodes.forEach((node) =>
{
let recipe: Recipe;
switch (node.recipeType)
{
case "":
case undefined:
case "GameRecipe":
recipe = GameRecipe.fromSerializable(node.recipeId);
break;
case "LinkedFactory":
recipe = LinkedFactory.fromSerializable(node.recipeId, node.customRecipe, node.customPower);
break;
default:
throw Error("Unknown RecipeType [" + node.recipeType + "]");

}
let machine = recipe.getMachine();

let opsPerMinute = (60.0 / recipe.manufacturingDuration)
powerConsumption += machine.powerConsumption * node.machinesAmount;
recipe.ingredients.forEach(ingredient => {
let currentAmount = resources.input.get(ingredient.id) || 0
let addedAmount = ingredient.amount * node.machinesAmount * opsPerMinute;
resources.input.set(ingredient.id, currentAmount + addedAmount);
});
recipe.products.forEach(product => {
let currentAmount = resources.output.get(product.id) || 0
let addedAmount = product.amount * node.machinesAmount * opsPerMinute;
resources.output.set(product.id, currentAmount + addedAmount);
// Remove resources from overall Inputs/Outputs if this output is supplying an input.
node.outputsGroups.forEach(outputGroup =>
{
let resourceId = outputGroup.resourceId;
outputGroup.connectedOutputs.forEach(connectedOutput =>
{
let currentInputAmount = resources.input.get(resourceId) || 0
resources.input.set(resourceId, currentInputAmount - connectedOutput.resourcesAmount);
let currentOutputAmount = resources.output.get(resourceId) || 0
resources.output.set(resourceId, currentOutputAmount - connectedOutput.resourcesAmount);
})
})
});
})

let factoryRecipe = new LinkedFactory(
factoryToImport,
factoryToImport,
Array.from(resources.input).filter(([key, value]) => value > 0).map(([key, value]) => ({ "id": key, "amount": value })),
Array.from(resources.output).filter(([key, value]) => value > 0).map(([key, value]) => ({ "id": key, "amount": value })),
powerConsumption
)
let detail = { recipe: factoryRecipe, machine: factoryRecipe.getMachine() };
const importFactoryEvent = new CustomEvent(FactoryImporter.factoryImportedEvent, { detail: detail});
FactoryImporter._instance.dispatchEvent(importFactoryEvent);
}
}
10 changes: 5 additions & 5 deletions src/GameData/GameData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,18 @@ export function loadSatisfactoryRecipe(recipeId: string): { recipe: GameRecipe,
{
for (const machine of satisfactoryData.machines)
{
let result = machine.recipes.find((recipe: GameRecipe) => recipe.id === recipeId);
let result = machine.recipes.find((recipe) => recipe.id === recipeId);

if (result != undefined)
{
return { recipe: result, machine: machine };
return { recipe: GameRecipe.fromRawData(result, machine), machine: machine };
}

let alternate = machine.alternateRecipes.find((recipe: GameRecipe) => recipe.id === recipeId);
let alternate = machine.alternateRecipes.find((recipe) => recipe.id === recipeId);

if (alternate != undefined)
{
return { recipe: alternate, machine: machine };
return { recipe: GameRecipe.fromRawData(alternate, machine), machine: machine };
}
}

Expand Down Expand Up @@ -92,7 +92,7 @@ export function loadSingleSatisfactoryRecipe(requiredItem: { id: string; type: "
return false;
}

suitableRecipe = recipe;
suitableRecipe = GameRecipe.fromRawData(recipe, machine);
suitableMachine = machine;
resourceAmount = foundResource.amount;
}
Expand Down
Loading