Skip to content

Commit

Permalink
bugfixes, support filtering by abstract items
Browse files Browse the repository at this point in the history
  • Loading branch information
doubleaxe committed Mar 23, 2023
1 parent e3e2685 commit 73f1d9c
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 44 deletions.
3 changes: 3 additions & 0 deletions site/data/types/game-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Author: Alexey Usov ([email protected], https://github.com/doubleaxe)
Please don't remove this comment if you use unmodified file
*/
import type {GameItemType} from './contants';
import type {
GameItemSerialized,
GameRecipeSerialized,
Expand Down Expand Up @@ -49,6 +50,8 @@ export interface GameRecipeDictionaryRaw extends GameRecipeDictionarySerialized
//item name => recipe names
recipesByInputMap: ReadonlyMap<string, string[]>;
recipesByOutputMap: ReadonlyMap<string, string[]>;
hasInputTypes: ReadonlySet<GameItemType>;
hasOutputTypes: ReadonlySet<GameItemType>;
}
export type GameRecipeDictionary = Readonly<GameRecipeDictionaryRaw>;

Expand Down
20 changes: 16 additions & 4 deletions site/src/components/left-toolbox/icon-list-filter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {GameItem} from '#types/game-data';
import {injectGameData} from '@/scripts/data';
import {injectFilter} from '@/scripts/filter';
import {syncRef, useDebounceFn} from '@vueuse/core';
import {computed, ref, toRef, unref, watch} from 'vue';
import {computed, ref, unref, watch} from 'vue';
const gameData = injectGameData();
Expand Down Expand Up @@ -50,23 +50,35 @@ watch(search, (value, oldValue) => {
});
syncRef(
toRef(filter, 'key'),
//use this instead of toRef, because otherwise this assignment is hard to find
computed({
get: () => filter.key,
set: (value) => { filter.key = value; },
}),
computed({
get: () => unref(select)?.name,
set: (value?: string) => { select.value = value ? gameData.getGameItem(value) : undefined; },
}),
);
syncRef(
toRef(filter, 'direction'),
//use this instead of toRef, because otherwise this assignment is hard to find
computed({
get: () => filter.direction,
set: (value) => { filter.direction = value; },
}),
computed({
get: () => Number(unref(direction)),
set: (value: number) => { direction.value = String(value); },
}),
);
syncRef(
toRef(filter, 'tierEqual'),
//use this instead of toRef, because otherwise this assignment is hard to find
computed({
get: () => filter.tierEqual,
set: (value) => { filter.tierEqual = value; },
}),
computed({
get: () => Number(unref(tierEqual)),
set: (value: number) => { tierEqual.value = String(value); },
Expand Down
6 changes: 0 additions & 6 deletions site/src/scripts/data/game-data-holder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,9 @@ export function useGameDataHolder(gameImplementation: GameImplementation) {
const gameFactoriesArray = gameItemsArray.filter((item) => item.recipeDictionary);
Object.freeze(gameFactoriesArray);

const gameLogisticByNameMap = new Map<string, GameLogistic>();
const gameLogisticByItemMap = new Map<string, GameLogistic[]>();
const gameLogisticByTypeMap = new Map<GameItemType, GameLogistic[]>();
for(const logistic of parsedGameData.parsedLogistic) {
gameLogisticByNameMap.set(logistic.name, logistic);
for(const {name} of logistic.items) {
const item = gameItemsMap.get(name);
if(item) {
Expand Down Expand Up @@ -73,16 +71,12 @@ export function useGameDataHolder(gameImplementation: GameImplementation) {
const emptyRecipeDictionary = parsedGameData.emptyRecipeDictionary;

const gameData = {
gameItemsMap,
gameItemsArray,
gameAbstractItems: freezeMap(gameAbstractItems),
gameItemsByType: freezeMap(gameItemsByType),
getGameItem: (name: string) => gameItemsMap.get(name),
gameFactoriesArray,
getItemRecipeDictionary: (item?: GameItem) => (item?.recipeDictionary || emptyRecipeDictionary),
gameLogisticByNameMap: freezeMap(gameLogisticByNameMap),
gameLogisticByItemMap: freezeMap(gameLogisticByItemMap),
gameLogisticByTypeMap: freezeMap(gameLogisticByTypeMap),
getLogistic: (item: GameItem) => [
...(gameLogisticByItemMap.get(item.name) || []),
...((item.type && gameLogisticByTypeMap.get(item.type)) || []),
Expand Down
24 changes: 20 additions & 4 deletions site/src/scripts/data/game-data-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ import type {
} from '#types/game-data-serialized';
import {inflate} from 'pako';
import {toUint8Array} from 'js-base64';
import {freezeMap} from '../util';
import {freezeMap, freezeSet} from '../util';
import type {Calculator} from '#types/calculator';
import type {GameItemType} from '#types/contants';

export type ParsedItems = ReadonlyMap<string, GameItem>;
export type ParsedRecipes = ReadonlyMap<string, GameRecipeDictionary>;
Expand Down Expand Up @@ -179,6 +180,7 @@ type RecipeDictionaryImpl = {
};
function createRecipeDictionaryImpl(calculator: Calculator, _recipeDictionary: GameRecipeDictionarySerialized) {
const emptyMap = new Map();
const emptySet = new Set<GameItemType>();
let recipesImpl: RecipeImpl[] = [];
const recipeDictionary: GameRecipeDictionaryRaw = {
..._recipeDictionary,
Expand All @@ -189,6 +191,8 @@ function createRecipeDictionaryImpl(calculator: Calculator, _recipeDictionary: G
//item name => recipe names
recipesByInputMap: emptyMap,
recipesByOutputMap: emptyMap,
hasInputTypes: emptySet,
hasOutputTypes: emptySet,
};
//init exdata, keep it writable, it could be used as cache
if(!recipeDictionary.exdata)
Expand All @@ -214,6 +218,7 @@ function createRecipeDictionaryImpl(calculator: Calculator, _recipeDictionary: G

const recipesByProduct = (type: 'input' | 'output') => {
const recipesByProductMap = new Map<string, string[]>();
const hasIoTypes = new Set<GameItemType>();
for(const recipe of recipeDictionary.recipes) {
for(const io of recipe[type]) {
let byProduct = recipesByProductMap.get(io.name);
Expand All @@ -222,12 +227,23 @@ function createRecipeDictionaryImpl(calculator: Calculator, _recipeDictionary: G
recipesByProductMap.set(io.name, byProduct);
}
byProduct.push(recipe.name);
if(io.product.type) {
hasIoTypes.add(io.product.type);
}
}
}
return freezeMap(recipesByProductMap);
return {
recipesByProductMap: freezeMap(recipesByProductMap),
hasIoTypes: freezeSet(hasIoTypes),
};
};
recipeDictionary.recipesByInputMap = recipesByProduct('input');
recipeDictionary.recipesByOutputMap = recipesByProduct('output');
const {recipesByProductMap: recipesByInputMap, hasIoTypes: hasInputTypes} = recipesByProduct('input');
recipeDictionary.recipesByInputMap = recipesByInputMap;
recipeDictionary.hasInputTypes = hasInputTypes;

const {recipesByProductMap: recipesByOutputMap, hasIoTypes: hasOutputTypes} = recipesByProduct('output');
recipeDictionary.recipesByOutputMap = recipesByOutputMap;
recipeDictionary.hasOutputTypes = hasOutputTypes;
Object.freeze(recipeDictionary);
},
};
Expand Down
24 changes: 17 additions & 7 deletions site/src/scripts/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ class Filter {
this._gameData = _gameData;
}

//suddenly may be null
get tier() { return this._tier; }
set tier(tier: number | undefined) { this._tier = tier; this._filtered = undefined; }
set tier(tier: number | undefined) { this._tier = tier ?? undefined; this._filtered = undefined; }

get tierEqual() { return this._tierEqual; }
set tierEqual(tierEqual: number) { this._tierEqual = tierEqual; this._filtered = undefined; }
Expand All @@ -32,10 +33,7 @@ class Filter {
set groupTier(groupTier: boolean) { this._groupTier = groupTier; this._filtered = undefined; }

get key() { return this._key; }
set key(key: string | undefined) { this._key = key; this._filtered = undefined; }

get type() { return this._type; }
set type(type: GameItemType | undefined) { this._type = type; this._filtered = undefined; }
set key(key: string | undefined) { this._key = key ?? undefined; this._filtered = undefined; }

get direction() { return this._direction; }
set direction(direction: number) { this._direction = direction; this._filtered = undefined; }
Expand All @@ -46,16 +44,28 @@ class Filter {
let filteredItems = this._gameData.gameFactoriesArray;

//key takes precedence before label
if(this._key) {
const key = this._key;
//support filtering by abstract item class
const key = this._key;
const filterItem = key ? this._gameData.getGameItem(key) : undefined;
if(key && filterItem) {
const abstractFilterItemType = filterItem.isAbstractClassItem ? filterItem.type : undefined;
const relativeAbsractItem = filterItem.type ? this._gameData.gameAbstractItems.get(filterItem.type) : undefined;
filteredItems = filteredItems.filter((item) => {
if(item.name == key)
return true;
const recipeDictionary = item.recipeDictionary;
if((this._direction <= 0) && recipeDictionary?.recipesByInputMap?.has(key))
return true;
if(abstractFilterItemType && (this._direction <= 0) && recipeDictionary?.hasInputTypes?.has(abstractFilterItemType))
return true;
if(relativeAbsractItem && (this._direction <= 0) && recipeDictionary?.recipesByInputMap?.has(relativeAbsractItem.name))
return true;
if((this._direction >= 0) && recipeDictionary?.recipesByOutputMap?.has(key))
return true;
if(abstractFilterItemType && (this._direction >= 0) && recipeDictionary?.hasOutputTypes?.has(abstractFilterItemType))
return true;
if(relativeAbsractItem && (this._direction >= 0) && recipeDictionary?.recipesByOutputMap?.has(relativeAbsractItem.name))
return true;
return false;
});
}
Expand Down
14 changes: 7 additions & 7 deletions site/src/scripts/model/blueprint-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,20 +171,20 @@ export class BlueprintItemModelImpl extends ItemModelImpl {
this.isFlipped = i.f ? true : false;
this._isLocked = i.l ? true : false;
}
_$loadLink(sourceItem: BlueprintItemModel, errorCollector: ErrorCollector) {
const sourceIoArray = sourceItem.selectedRecipe?.items;
if(!sourceIoArray)
_$loadLink(outputItem: BlueprintItemModel, errorCollector: ErrorCollector) {
const outputIoArray = outputItem.selectedRecipe?.output;
if(!outputIoArray)
return undefined;
let link: LinkModel | undefined;
for(const sourceIo of sourceIoArray) {
const maybeTarget = this._selectedRecipe?.findSimilarIo(sourceIo, true);
for(const outputIo of outputIoArray) {
const maybeTarget = this._selectedRecipe?.findSimilarIo(outputIo, true);
if(maybeTarget) {
link = this.owner?._$addLink(sourceIo, maybeTarget);
link = this.owner?._$addLink(outputIo, maybeTarget);
break;
}
}
if(!link) {
errorCollector.collectError(`Cannot load link "${sourceItem.label}" => "${this.label}"`);
errorCollector.collectError(`Cannot load link "${outputItem.label}" => "${this.label}"`);
}
return link;
}
Expand Down
20 changes: 13 additions & 7 deletions site/src/scripts/model/blueprint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,19 +194,25 @@ export class BlueprintModelImpl {
itemIndexes.set(index, item.key);
});
savedBlueprint.l.forEach((link) => {
const itemKey1 = itemIndexes.get(link.l[0]);
const itemKey2 = itemIndexes.get(link.l[1]);
if(!itemKey1 || !itemKey2) {
//first array item is always input, second is always output
//the order is important, because we may connect for example generator to electric engine
//in this case we may connect by either end
//generator output to electric engine
//or electric engine output to generator
//which end is connected is determined by io order
const inputKey = itemIndexes.get(link.l[0]);
const outputKey = itemIndexes.get(link.l[1]);
if(!inputKey || !outputKey) {
//broken link
errorCollector.collectError(`Invalid link "${link.l[0]}" => "${link.l[1]}"`);
return;
}
const item1 = this.itemByKey(itemKey1);
const item2 = this.itemByKey(itemKey2);
if(!item1 || !item2) {
const input = this.itemByKey(inputKey);
const output = this.itemByKey(outputKey);
if(!input || !output) {
return;
}
const loadedLink = item1._$loadLink(item2, errorCollector);
const loadedLink = input._$loadLink(output, errorCollector);
if(loadedLink) {
loadedLink._$load(link, errorCollector);
}
Expand Down
2 changes: 1 addition & 1 deletion site/src/scripts/model/logistic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export class LogisticSetModelImpl {
}
const gameData = link.input?.owner?.gameData;
//should be the same input = output
const io = gameData?.gameItemsMap?.get(link.input?.name || link.output?.name || '');
const io = gameData?.getGameItem(link.input?.name || link.output?.name || '');
if(!gameData || !io) {
return this._emptyLogisticSet;
}
Expand Down
5 changes: 5 additions & 0 deletions site/src/scripts/model/normalize-item-positions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export function normalizeItemPositions(items: IterableIterator<BlueprintItemMode

//normalize minimum offset of item to 0 px
//new saves will always be normalized, old ones need to be normalized for nicer first display
if((minItemXY.x == 0) && (minItemXY.y == 0)) {
//already normalized
return;
}

const offsetBy = minItemXY.scalePoint(-1);
for(const item of _items) {
item.setRect(item.rect.offsetBy(offsetBy));
Expand Down
22 changes: 14 additions & 8 deletions site/src/scripts/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import {BlueprintItemState, DEFAULT_BLUEPRINT_SPLIT, type BlueprintItemStateValu

type BlueprintItemStateColorClass = Record<BlueprintItemStateValues, string>;

type PossibleKeys = Array<keyof Settings>;
const SavedKeys: PossibleKeys = [
const SavedKeys = [
'colorfulLinks',
'tier',
'tierEqual',
Expand All @@ -28,10 +27,10 @@ const SavedKeys: PossibleKeys = [
'blueprintCompress',
'blueprintEncode',
'blueprintSplit',
];
] as const;
type SavedKey = typeof SavedKeys[number];
type SavedObject = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[k: string]: any;
[k in SavedKey]: unknown;
};

function filterSavedKeys(object: SavedObject) {
Expand All @@ -51,7 +50,7 @@ function isTouchDevice() {
}
}

class Settings {
class Settings implements SavedObject {
private _filter: PublicFilter;
private _blueprintModel: BlueprintModel;
private _isTouchDevice;
Expand Down Expand Up @@ -90,7 +89,7 @@ class Settings {
blueprintSplit = DEFAULT_BLUEPRINT_SPLIT;

get tier() { return this._filter.tier; }
set tier(tier: number | null | undefined) { this._filter.tier = tier ?? undefined; }
set tier(tier: number | undefined) { this._filter.tier = tier; }
get tierEqual() { return this._filter.tierEqual; }
set tierEqual(tierEqual: number) { this._filter.tierEqual = tierEqual; }
get groupTier() { return this._filter.groupTier; }
Expand All @@ -104,7 +103,14 @@ class Settings {
return filterSavedKeys(this);
}
load(settings: SavedObject) {
Object.assign(this, filterSavedKeys(settings));
const self = this as SavedObject;
for(const [key, value] of Object.entries(settings)) {
const _key = key as SavedKey;
//prevent unnecessary change events, if corresponding option is not changed
if(self[_key] !== value) {
self[_key] = value;
}
}
}
}

Expand Down
18 changes: 18 additions & 0 deletions site/src/scripts/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ export function freezeMap<K, V>(map: Map<K, V>): ReadonlyMap<K, V> {
return Object.freeze(map);
}

export function freezeSet<T>(set: Set<T>): ReadonlySet<T> {
if(set instanceof Set) {
set.add = (value: T) => {
throw new Error(`Can't add value ${value}, set is frozen`);
};

set.delete = (value: T) => {
throw new Error(`Can't delete value ${value}, set is frozen`);
};

set.clear = () => {
throw new Error("Can't clear set, set is frozen");
};
}

return Object.freeze(set);
}

export function buildTransformStyle(transform: Record<string, string>): string {
return Object.entries(transform).map(([key, value]) => `${key}(${value})`).join(' ');
}
Expand Down

0 comments on commit 73f1d9c

Please sign in to comment.