properly set occupant hash, max quantity, sorting class
This commit is contained in:
1
www/data/Enums.json
Normal file
1
www/data/Enums.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
22607
www/data/database.json
22607
www/data/database.json
File diff suppressed because it is too large
Load Diff
305
www/src/ts/virtual_machine/device/add_device.ts
Normal file
305
www/src/ts/virtual_machine/device/add_device.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
|
||||
import { html, css } from "lit";
|
||||
import { customElement, query, state } from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "components";
|
||||
|
||||
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js";
|
||||
|
||||
import SlDrawer from "@shoelace-style/shoelace/dist/components/drawer/drawer.js";
|
||||
import type { DeviceDBEntry } from "virtual_machine/device_db";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
import { cache } from "lit/directives/cache.js";
|
||||
import { default as uFuzzy } from "@leeoniya/ufuzzy";
|
||||
import { when } from "lit/directives/when.js";
|
||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
||||
import { VMDeviceDBMixin } from "virtual_machine/base_device";
|
||||
|
||||
|
||||
@customElement("vm-add-device-button")
|
||||
export class VMAddDeviceButton extends VMDeviceDBMixin(BaseElement) {
|
||||
static styles = [
|
||||
...defaultCss,
|
||||
css`
|
||||
.add-device-drawer {
|
||||
--size: 36rem;
|
||||
--footer-spacing: var(--sl-spacing-small);
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-top: var(--sl-spacing-small);
|
||||
margin-right: var(--sl-spacing-small);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@query("sl-drawer") drawer: SlDrawer;
|
||||
@query(".device-search-input") searchInput: SlInput;
|
||||
|
||||
private _structures: Map<string, DeviceDBEntry> = new Map();
|
||||
private _datapoints: [string, string][] = [];
|
||||
private _haystack: string[] = [];
|
||||
|
||||
postDBSetUpdate(): void {
|
||||
this._structures = new Map(
|
||||
Object.values(this.deviceDB.db)
|
||||
.filter((entry) => this.deviceDB.structures.includes(entry.name), this)
|
||||
.filter(
|
||||
(entry) => this.deviceDB.logic_enabled.includes(entry.name),
|
||||
this,
|
||||
)
|
||||
.map((entry) => [entry.name, entry]),
|
||||
);
|
||||
|
||||
const datapoints: [string, string][] = [];
|
||||
for (const entry of this._structures.values()) {
|
||||
datapoints.push(
|
||||
[entry.title, entry.name],
|
||||
[entry.name, entry.name],
|
||||
[entry.desc, entry.name],
|
||||
);
|
||||
}
|
||||
const haystack: string[] = datapoints.map((data) => data[0]);
|
||||
this._datapoints = datapoints;
|
||||
this._haystack = haystack;
|
||||
this.performSearch();
|
||||
}
|
||||
|
||||
private _filter: string = "";
|
||||
|
||||
get filter() {
|
||||
return this._filter;
|
||||
}
|
||||
|
||||
@state()
|
||||
set filter(val: string) {
|
||||
this._filter = val;
|
||||
this.page = 0;
|
||||
this.performSearch();
|
||||
}
|
||||
|
||||
private _searchResults: {
|
||||
entry: DeviceDBEntry;
|
||||
haystackEntry: string;
|
||||
ranges: number[];
|
||||
}[] = [];
|
||||
|
||||
private filterTimeout: number | undefined;
|
||||
|
||||
performSearch() {
|
||||
if (this._filter) {
|
||||
const uf = new uFuzzy({});
|
||||
const [_idxs, info, order] = uf.search(
|
||||
this._haystack,
|
||||
this._filter,
|
||||
0,
|
||||
1e3,
|
||||
);
|
||||
|
||||
const filtered = order?.map((infoIdx) => ({
|
||||
name: this._datapoints[info.idx[infoIdx]][1],
|
||||
haystackEntry: this._haystack[info.idx[infoIdx]],
|
||||
ranges: info.ranges[infoIdx],
|
||||
}));
|
||||
|
||||
const unique = [...new Set(filtered.map((obj) => obj.name))].map(
|
||||
(result) => {
|
||||
return filtered.find((obj) => obj.name === result);
|
||||
},
|
||||
);
|
||||
|
||||
this._searchResults = unique.map(({ name, haystackEntry, ranges }) => ({
|
||||
entry: this._structures.get(name)!,
|
||||
haystackEntry,
|
||||
ranges,
|
||||
}));
|
||||
} else {
|
||||
// return everything
|
||||
this._searchResults = [...this._structures.values()].map((st) => ({
|
||||
entry: st,
|
||||
haystackEntry: st.title,
|
||||
ranges: [],
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
window.VM.get().then((vm) =>
|
||||
vm.addEventListener(
|
||||
"vm-device-db-loaded",
|
||||
this._handleDeviceDBLoad.bind(this),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_handleDeviceDBLoad(e: CustomEvent) {
|
||||
this.deviceDB = e.detail;
|
||||
}
|
||||
|
||||
@state() private page = 0;
|
||||
|
||||
renderSearchResults() {
|
||||
const perPage = 40;
|
||||
const totalPages = Math.ceil((this._searchResults?.length ?? 0) / perPage);
|
||||
let pageKeys = Array.from({ length: totalPages }, (_, index) => index);
|
||||
const extra: {
|
||||
entry: { title: string; name: string };
|
||||
haystackEntry: string;
|
||||
ranges: number[];
|
||||
}[] = [];
|
||||
if (this.page < totalPages - 1) {
|
||||
extra.push({
|
||||
entry: { title: "", name: this.filter },
|
||||
haystackEntry: "...",
|
||||
ranges: [],
|
||||
});
|
||||
}
|
||||
return when(
|
||||
typeof this._searchResults !== "undefined" &&
|
||||
this._searchResults.length < 20,
|
||||
() =>
|
||||
repeat(
|
||||
this._searchResults ?? [],
|
||||
(result) => result.entry.name,
|
||||
(result) =>
|
||||
cache(html`
|
||||
<vm-device-template
|
||||
prefab_name=${result.entry.name}
|
||||
class="card"
|
||||
@add-device-template=${this._handleDeviceAdd}
|
||||
>
|
||||
</vm-device-template>
|
||||
`),
|
||||
),
|
||||
() => html`
|
||||
<div class="p-2">
|
||||
<div class="flex flex-row">
|
||||
<p class="p-2">
|
||||
<sl-format-number
|
||||
.value=${this._searchResults?.length}
|
||||
></sl-format-number>
|
||||
results, filter more to get cards
|
||||
</p>
|
||||
<div class="p-2 ml-2">
|
||||
Page:
|
||||
${pageKeys.map(
|
||||
(key, index) => html`
|
||||
<span
|
||||
class="p-2 cursor-pointer hover:text-purple-400 ${index ===
|
||||
this.page
|
||||
? " text-purple-500"
|
||||
: ""}"
|
||||
key=${key}
|
||||
@click=${this._handlePageChange}
|
||||
>${key + 1}${index < totalPages - 1 ? "," : ""}</span
|
||||
>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row flex-wrap">
|
||||
${[
|
||||
...this._searchResults.slice(
|
||||
perPage * this.page,
|
||||
perPage * this.page + perPage,
|
||||
),
|
||||
...extra,
|
||||
].map((result) => {
|
||||
let hay = result.haystackEntry.slice(0, 15);
|
||||
if (result.haystackEntry.length > 15) hay += "...";
|
||||
const ranges = result.ranges.filter((pos) => pos < 20);
|
||||
const key = result.entry.name;
|
||||
return html`
|
||||
<div
|
||||
class="m-2 text-neutral-200/90 italic cursor-pointer rounded bg-neutral-700 hover:bg-purple-500 px-1"
|
||||
key=${key}
|
||||
@click=${this._handleHaystackClick}
|
||||
>
|
||||
${result.entry.title} (<small class="text-sm">
|
||||
${ranges.length
|
||||
? unsafeHTML(uFuzzy.highlight(hay, ranges))
|
||||
: hay} </small
|
||||
>)
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
_handlePageChange(e: Event) {
|
||||
const span = e.currentTarget as HTMLSpanElement;
|
||||
const key = parseInt(span.getAttribute("key"));
|
||||
this.page = key;
|
||||
}
|
||||
|
||||
_handleHaystackClick(e: Event) {
|
||||
const div = e.currentTarget as HTMLDivElement;
|
||||
const key = div.getAttribute("key");
|
||||
if (key === this.filter) {
|
||||
this.page += 1;
|
||||
} else {
|
||||
this.filter = key;
|
||||
this.searchInput.value = key;
|
||||
}
|
||||
}
|
||||
|
||||
_handleDeviceAdd() {
|
||||
this.drawer.hide();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<sl-button
|
||||
variant="neutral"
|
||||
outline
|
||||
pill
|
||||
@click=${this._handleAddButtonClick}
|
||||
>
|
||||
Add Device
|
||||
</sl-button>
|
||||
<sl-drawer class="add-device-drawer" placement="bottom" no-header>
|
||||
<sl-input
|
||||
class="device-search-input"
|
||||
autofocus
|
||||
placeholder="filter"
|
||||
clearable
|
||||
@sl-input=${this._handleSearchInput}
|
||||
>
|
||||
<span slot="prefix">Search Structures</span>
|
||||
<sl-icon slot="suffix" name="search"></sl-icon>
|
||||
</sl-input>
|
||||
<div class="flex flex-row overflow-x-auto">
|
||||
${this.renderSearchResults()}
|
||||
</div>
|
||||
<sl-button
|
||||
slot="footer"
|
||||
variant="primary"
|
||||
@click=${() => {
|
||||
this.drawer.hide();
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</sl-button>
|
||||
</sl-drawer>
|
||||
`;
|
||||
}
|
||||
|
||||
_handleSearchInput(e: CustomEvent) {
|
||||
if (this.filterTimeout) {
|
||||
clearTimeout(this.filterTimeout);
|
||||
}
|
||||
const that = this;
|
||||
this.filterTimeout = setTimeout(() => {
|
||||
that.filter = that.searchInput.value;
|
||||
that.filterTimeout = undefined;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
_handleAddButtonClick() {
|
||||
this.drawer.show();
|
||||
this.searchInput.select();
|
||||
}
|
||||
}
|
||||
243
www/src/ts/virtual_machine/device/slot_add_dialog.ts
Normal file
243
www/src/ts/virtual_machine/device/slot_add_dialog.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
import { html, css } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "components";
|
||||
import { VMDeviceDBMixin } from "virtual_machine/base_device";
|
||||
import type { DeviceDB, DeviceDBEntry } from "virtual_machine/device_db";
|
||||
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
|
||||
import SlDialog from "@shoelace-style/shoelace/dist/components/dialog/dialog.component.js";
|
||||
import { VMDeviceCard } from "./card";
|
||||
import { when } from "lit/directives/when.js";
|
||||
import uFuzzy from "@leeoniya/ufuzzy";
|
||||
import { LogicField, SlotLogicType, SlotOccupantTemplate } from "ic10emu_wasm";
|
||||
|
||||
@customElement("vm-slot-add-dialog")
|
||||
export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
|
||||
static styles = [
|
||||
...defaultCss,
|
||||
css`
|
||||
.slot-card {
|
||||
--padding: var(--sl-spacing-x-small);
|
||||
}
|
||||
.slot-card::part(header) {
|
||||
padding: var(--sl-spacing-x-small);
|
||||
}
|
||||
.slot-card::part(base) {
|
||||
background-color: var(--sl-color-neutral-50);
|
||||
}
|
||||
.quantity-input sl-input::part(input) {
|
||||
width: 3rem;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
private _items: Map<string, DeviceDBEntry> = new Map();
|
||||
private _datapoints: [string, string][] = [];
|
||||
private _haystack: string[] = [];
|
||||
|
||||
private _filter: string = "";
|
||||
get filter() {
|
||||
return this._filter;
|
||||
}
|
||||
|
||||
@state()
|
||||
set filter(val: string) {
|
||||
this._filter = val;
|
||||
this.performSearch();
|
||||
}
|
||||
|
||||
private _searchResults: {
|
||||
entry: DeviceDBEntry;
|
||||
haystackEntry: string;
|
||||
ranges: number[];
|
||||
}[] = [];
|
||||
|
||||
postDBSetUpdate(): void {
|
||||
this._items = new Map(
|
||||
Object.values(this.deviceDB.db)
|
||||
.filter((entry) => this.deviceDB.items.includes(entry.name), this)
|
||||
.map((entry) => [entry.name, entry]),
|
||||
);
|
||||
this.performSearch();
|
||||
}
|
||||
|
||||
performSearch() {
|
||||
if (this._filter) {
|
||||
const device = window.VM.vm.devices.get(this.deviceID);
|
||||
const dbDevice = this.deviceDB.db[device.prefabName]
|
||||
const slot = dbDevice.slots[this.slotIndex]
|
||||
const typ = slot.typ;
|
||||
|
||||
let filterdItems = Array.from(this._items.values());
|
||||
if (typ !== "None") {
|
||||
filterdItems = Array.from(this._items.values()).filter(item => item.item.slotclass === typ);
|
||||
}
|
||||
|
||||
const datapoints: [string, string][] = [];
|
||||
for (const entry of filterdItems) {
|
||||
datapoints.push(
|
||||
[entry.title, entry.name],
|
||||
[entry.name, entry.name],
|
||||
[entry.desc, entry.name],
|
||||
);
|
||||
}
|
||||
|
||||
const haystack: string[] = datapoints.map((data) => data[0]);
|
||||
this._datapoints = datapoints;
|
||||
this._haystack = haystack;
|
||||
|
||||
const uf = new uFuzzy({});
|
||||
const [_idxs, info, order] = uf.search(
|
||||
this._haystack,
|
||||
this._filter,
|
||||
0,
|
||||
1e3,
|
||||
);
|
||||
|
||||
const filtered = order?.map((infoIdx) => ({
|
||||
name: this._datapoints[info.idx[infoIdx]][1],
|
||||
haystackEntry: this._haystack[info.idx[infoIdx]],
|
||||
ranges: info.ranges[infoIdx],
|
||||
})) ?? [];
|
||||
|
||||
const uniqueNames = new Set(filtered.map((obj) => obj.name));
|
||||
const unique = [...uniqueNames].map(
|
||||
(result) => {
|
||||
return filtered.find((obj) => obj.name === result);
|
||||
},
|
||||
);
|
||||
|
||||
this._searchResults = unique.map(({ name, haystackEntry, ranges }) => ({
|
||||
entry: this._items.get(name)!,
|
||||
haystackEntry,
|
||||
ranges,
|
||||
}));
|
||||
} else {
|
||||
// return everything
|
||||
this._searchResults = [...this._items.values()].map((st) => ({
|
||||
entry: st,
|
||||
haystackEntry: st.title,
|
||||
ranges: [],
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
renderSearchResults() {
|
||||
return html`
|
||||
<div class="mt-2 max-h-48 overflow-y-auto w-full">
|
||||
<div class="cursor-pointer hover:bg-neutral-600 rounded px-2 me-1" @click=${this._handleClickNone}>
|
||||
None
|
||||
</div>
|
||||
${this._searchResults.map((result) => {
|
||||
const imgSrc = `img/stationpedia/${result.entry.name}.png`;
|
||||
const img = html`
|
||||
<img class="w-8 h-8" src=${imgSrc} onerror="this.src = '${VMDeviceCard.transparentImg}'" />
|
||||
`;
|
||||
return html`
|
||||
<div class="cursor-pointer hover:bg-neutral-600 rounded px-2 me-1 flex flex-row" key=${result.entry.name} @click=${this._handleClickItem}>
|
||||
${img}
|
||||
<div>${result.entry.title}</div>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
_handleClickNone() {
|
||||
console.log("Clear Slot");
|
||||
}
|
||||
|
||||
_handleClickItem(e: Event) {
|
||||
const div = e.currentTarget as HTMLDivElement;
|
||||
const key = div.getAttribute("key");
|
||||
const entry = this.deviceDB.db[key];
|
||||
const device = window.VM.vm.devices.get(this.deviceID);
|
||||
const dbDevice = this.deviceDB.db[device.prefabName]
|
||||
const sorting = this.deviceDB.enums["SortingClass"][entry.item.sorting ?? "Default"] ?? 0;
|
||||
console.log("using entry", dbDevice);
|
||||
const fields: { [key in SlotLogicType]?: LogicField } = Object.fromEntries(
|
||||
Object.entries(dbDevice.slotlogic[this.slotIndex] ?? {})
|
||||
.map(([slt_s, field_type]) => {
|
||||
let slt = slt_s as SlotLogicType;
|
||||
let value = 0.0
|
||||
if (slt === "FilterType") {
|
||||
value = this.deviceDB.enums["GasType"][entry.item.filtertype]
|
||||
}
|
||||
const field: LogicField = { field_type, value};
|
||||
return [slt, field];
|
||||
})
|
||||
);
|
||||
fields["PrefabHash"] = { field_type: "Read", value: entry.hash };
|
||||
fields["MaxQuantity"] = { field_type: "Read", value: entry.item.maxquantity ?? 1.0 };
|
||||
fields["SortingClass"] = { field_type: "Read", value: sorting };
|
||||
|
||||
const template: SlotOccupantTemplate = {
|
||||
fields
|
||||
}
|
||||
window.VM.vm.setDeviceSlotOccupant(this.deviceID, this.slotIndex, template);
|
||||
}
|
||||
|
||||
@query("sl-dialog.slot-add-dialog") dialog: SlDialog;
|
||||
@query(".device-search-input") searchInput: SlInput;
|
||||
|
||||
render() {
|
||||
const device = window.VM.vm.devices.get(this.deviceID);
|
||||
const name = device?.name ?? device?.prefabName ?? "";
|
||||
const id = this.deviceID ?? 0;
|
||||
return html`
|
||||
<sl-dialog
|
||||
label="Edit device ${id} : ${name} Slot ${this.slotIndex}"
|
||||
class="slot-add-dialog"
|
||||
@sl-hide=${this._handleDialogHide}
|
||||
>
|
||||
<sl-input class="device-search-input" autofocus placeholder="filter" clearable
|
||||
@sl-input=${this._handleSearchInput}>
|
||||
<span slot="prefix">Search Items</span>
|
||||
<sl-icon slot="suffix" name="search"></sl-icon>
|
||||
</sl-input>
|
||||
${when(
|
||||
typeof this.deviceID !== "undefined" &&
|
||||
typeof this.slotIndex !== "undefined",
|
||||
() => html`
|
||||
<div class="flex flex-row overflow-x-auto">
|
||||
${this.renderSearchResults()}
|
||||
</div>
|
||||
`,
|
||||
() => html``,
|
||||
)}
|
||||
</sl-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private filterTimeout: number | undefined;
|
||||
|
||||
_handleSearchInput(_e: CustomEvent) {
|
||||
if (this.filterTimeout) {
|
||||
clearTimeout(this.filterTimeout);
|
||||
}
|
||||
const that = this;
|
||||
this.filterTimeout = setTimeout(() => {
|
||||
that.filter = that.searchInput.value;
|
||||
that.filterTimeout = undefined;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
_handleDialogHide() {
|
||||
this.deviceID = undefined;
|
||||
this.slotIndex = undefined;
|
||||
}
|
||||
|
||||
@state() private deviceID: number;
|
||||
@state() private slotIndex: number;
|
||||
|
||||
show(deviceID: number, slotIndex: number) {
|
||||
this.deviceID = deviceID;
|
||||
this.slotIndex = slotIndex;
|
||||
this.dialog.show();
|
||||
this.searchInput.select();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.dialog.hide();
|
||||
}
|
||||
}
|
||||
@@ -90,16 +90,6 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) {
|
||||
}
|
||||
|
||||
setupState() {
|
||||
const slotlogicmap: { [key: number]: SlotLogicType[] } = {};
|
||||
for (const [slt, slotIndexes] of Object.entries(
|
||||
this.dbDevice?.slotlogic ?? {},
|
||||
)) {
|
||||
for (const slotIndex of slotIndexes) {
|
||||
const list = slotlogicmap[slotIndex] ?? [];
|
||||
list.push(slt as SlotLogicType);
|
||||
slotlogicmap[slotIndex] = list;
|
||||
}
|
||||
}
|
||||
|
||||
this.fields = Object.fromEntries(
|
||||
Object.entries(this.dbDevice?.logic ?? {}).map(([lt, ft]) => {
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { LogicType, SlotLogicType, SortingClass, SlotType, FieldType, ReagentMode, BatchMode, ConnectionType, ConnectionRole } from "ic10emu_wasm";
|
||||
import {
|
||||
LogicType,
|
||||
SlotLogicType,
|
||||
SortingClass,
|
||||
SlotType,
|
||||
FieldType,
|
||||
ReagentMode,
|
||||
BatchMode,
|
||||
ConnectionType,
|
||||
ConnectionRole,
|
||||
} from "ic10emu_wasm";
|
||||
export interface DeviceDBItem {
|
||||
slotclass: SlotType;
|
||||
sorting: SortingClass;
|
||||
@@ -6,7 +16,7 @@ export interface DeviceDBItem {
|
||||
filtertype?: string;
|
||||
consumable?: boolean;
|
||||
ingredient?: boolean;
|
||||
reagents?: { [key: string]: number};
|
||||
reagents?: { [key: string]: number };
|
||||
}
|
||||
|
||||
export interface DeviceDBDevice {
|
||||
@@ -22,6 +32,22 @@ export interface DeviceDBConnection {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface DeviceDBInstruction {
|
||||
typ: string;
|
||||
value: number;
|
||||
desc: string;
|
||||
}
|
||||
|
||||
export interface DeviceDBMemory {
|
||||
size: number;
|
||||
sizeDisplay: string;
|
||||
access: MemoryAccess
|
||||
instructions?: { [key: string]: DeviceDBInstruction };
|
||||
}
|
||||
|
||||
export type MemoryAccess = "Read" | "Write" | "ReadWrite" | "None";
|
||||
|
||||
|
||||
export interface DeviceDBEntry {
|
||||
name: string;
|
||||
hash: number;
|
||||
@@ -29,12 +55,15 @@ export interface DeviceDBEntry {
|
||||
desc: string;
|
||||
slots?: { name: string; typ: SlotType }[];
|
||||
logic?: { [key in LogicType]?: FieldType };
|
||||
slotlogic?: { [key in SlotLogicType]?: number[] };
|
||||
slotlogic?: { [key: number]: {[key in SlotLogicType]?: FieldType } };
|
||||
modes?: { [key: number]: string };
|
||||
conn?: { [key: number]: DeviceDBConnection };
|
||||
conn?: { [key: number]: DeviceDBConnection }
|
||||
item?: DeviceDBItem;
|
||||
device?: DeviceDBDevice;
|
||||
};
|
||||
transmitter: boolean;
|
||||
receiver: boolean;
|
||||
memory?: DeviceDBMemory;
|
||||
}
|
||||
|
||||
export interface DBStates {
|
||||
activate: boolean;
|
||||
@@ -45,6 +74,12 @@ export interface DBStates {
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
export interface DeviceDBReagent {
|
||||
Hash: number;
|
||||
Unit: string;
|
||||
Sources?: { [key: string]: number };
|
||||
}
|
||||
|
||||
export interface DeviceDB {
|
||||
logic_enabled: string[];
|
||||
slot_logic_enabled: string[];
|
||||
@@ -55,6 +90,6 @@ export interface DeviceDB {
|
||||
[key: string]: DeviceDBEntry;
|
||||
};
|
||||
names_by_hash: { [key: number]: string };
|
||||
reagent_hashes: { [key: string]: number}
|
||||
};
|
||||
|
||||
reagents: { [key: string]: DeviceDBReagent };
|
||||
enums: { [key: string]: { [key: string]: number } };
|
||||
}
|
||||
|
||||
@@ -5,6 +5,11 @@ from pathlib import Path
|
||||
from pprint import pprint
|
||||
from typing import Any, NotRequired, TypedDict # type: ignore[Any]
|
||||
|
||||
try:
|
||||
import markdown
|
||||
except ImportError:
|
||||
markdown = None
|
||||
|
||||
|
||||
class SlotInsert(TypedDict):
|
||||
SlotIndex: str
|
||||
@@ -40,6 +45,24 @@ class PediaPageDevice(TypedDict):
|
||||
DevicesLength: NotRequired[int]
|
||||
|
||||
|
||||
class MemoryInstruction(TypedDict):
|
||||
Type: str
|
||||
Value: int
|
||||
Description: str
|
||||
|
||||
|
||||
class PediaPageMemory(TypedDict):
|
||||
MemorySize: int
|
||||
MemorySizeReadable: str
|
||||
MemoryAccess: str
|
||||
Instructions: dict[str, MemoryInstruction] | None
|
||||
|
||||
|
||||
class PediaPageLogicInfo(TypedDict):
|
||||
LogicSlotTypes: dict[str, dict[str, str]]
|
||||
LogicTypes: dict[str, str]
|
||||
|
||||
|
||||
class PediaPage(TypedDict):
|
||||
Key: str
|
||||
Title: str
|
||||
@@ -51,13 +74,30 @@ class PediaPage(TypedDict):
|
||||
LogicSlotInsert: list[LInsert]
|
||||
ModeInsert: list[LInsert]
|
||||
ConnectionInsert: list[LInsert]
|
||||
Device: NotRequired[PediaPageDevice]
|
||||
LogicInfo: PediaPageLogicInfo | None
|
||||
Item: NotRequired[PediaPageItem]
|
||||
Device: NotRequired[PediaPageDevice]
|
||||
WirelessLogic: bool | None
|
||||
Memory: PediaPageMemory | None
|
||||
TransmissionReceiver: bool | None
|
||||
|
||||
|
||||
class ScriptCommand(TypedDict):
|
||||
desc: str
|
||||
example: str
|
||||
|
||||
|
||||
class PediaReagent(TypedDict):
|
||||
Hash: int
|
||||
Unit: str
|
||||
Sources: dict[str, float] | None
|
||||
|
||||
|
||||
class Pedia(TypedDict):
|
||||
pages: list[PediaPage]
|
||||
reagents: dict[str, int]
|
||||
scriptCommands: dict[str, ScriptCommand]
|
||||
|
||||
|
||||
class DBSlot(TypedDict):
|
||||
name: str
|
||||
@@ -96,6 +136,19 @@ class DBPageItem(TypedDict):
|
||||
reagents: NotRequired[dict[str, float]]
|
||||
|
||||
|
||||
class DBPageMemoryInstruction(TypedDict):
|
||||
typ: str
|
||||
value: int
|
||||
desc: str
|
||||
|
||||
|
||||
class DBPageMemory(TypedDict):
|
||||
size: int
|
||||
sizeDisplay: str
|
||||
access: str
|
||||
instructions: dict[str, DBPageMemoryInstruction] | None
|
||||
|
||||
|
||||
class DBPage(TypedDict):
|
||||
name: str
|
||||
hash: int
|
||||
@@ -103,16 +156,21 @@ class DBPage(TypedDict):
|
||||
desc: str
|
||||
slots: list[DBSlot] | None
|
||||
logic: dict[str, str] | None
|
||||
slotlogic: dict[str, list[int]] | None
|
||||
slotlogic: dict[str, dict[str, str]] | None
|
||||
modes: dict[int, str] | None
|
||||
conn: dict[int, DBPageConnection] | None
|
||||
item: NotRequired[DBPageItem]
|
||||
device: NotRequired[DBPageDevice]
|
||||
transmitter: bool
|
||||
receiver: bool
|
||||
memory: DBPageMemory | None
|
||||
|
||||
|
||||
translation_regex = re.compile(r"<N:([A-Z]{2}):(\w+)>")
|
||||
translation_keys: set[str] = set()
|
||||
translation_codes: set[str] = set()
|
||||
|
||||
|
||||
def replace_translation(m: re.Match[str]) -> str:
|
||||
match m.groups():
|
||||
case (code, key):
|
||||
@@ -123,19 +181,67 @@ def replace_translation(m: re.Match[str]) -> str:
|
||||
print("bad translation match?", g, m.string)
|
||||
return m.string
|
||||
|
||||
|
||||
def trans(s: str) -> str:
|
||||
return re.sub(translation_regex, replace_translation, s)
|
||||
|
||||
|
||||
color_regex = re.compile(
|
||||
r"<color=(#?\w+)>((:?(?!<color=(?:#?\w+)>).)+?)</color>", re.DOTALL
|
||||
)
|
||||
link_regex = re.compile(r"<link=(\w+)>(.+?)</link>")
|
||||
|
||||
|
||||
def strip_color(s: str) -> str:
|
||||
replacemnt = r"\2"
|
||||
last = s
|
||||
new = color_regex.sub(replacemnt, last)
|
||||
while new != last:
|
||||
last = new
|
||||
new = color_regex.sub(replacemnt, last)
|
||||
return new
|
||||
|
||||
|
||||
def color_to_html(s: str) -> str:
|
||||
replacemnt = r"""<div style="color: \1;">\2</div>"""
|
||||
last = s
|
||||
new = color_regex.sub(replacemnt, last)
|
||||
while new != last:
|
||||
last = new
|
||||
new = color_regex.sub(replacemnt, last)
|
||||
return new
|
||||
|
||||
|
||||
def strip_link(s: str) -> str:
|
||||
replacemnt = r"\2"
|
||||
last = s
|
||||
new = link_regex.sub(replacemnt, last)
|
||||
while new != last:
|
||||
last = new
|
||||
new = link_regex.sub(replacemnt, last)
|
||||
return new
|
||||
|
||||
|
||||
def extract_all() -> None:
|
||||
db: dict[str, DBPage] = {}
|
||||
pedia: Pedia = {"pages": [], "reagents": {}}
|
||||
linkPat = re.compile(r"<link=\w+><color=[\w#]+>(.+?)</color></link>")
|
||||
pedia: Pedia = {"pages": [], "reagents": {}, "scriptCommands": {}}
|
||||
with (Path("data") / "Stationpedia.json").open("r") as f:
|
||||
pedia = json.load(f)
|
||||
for page in pedia["pages"]:
|
||||
item: DBPage = defaultdict(list) # type: ignore[reportAssignmentType]
|
||||
|
||||
item: DBPage = {
|
||||
"name": "",
|
||||
"hash": 0,
|
||||
"title": "",
|
||||
"desc": "",
|
||||
"slots": None,
|
||||
"logic": None,
|
||||
"slotlogic": None,
|
||||
"modes": None,
|
||||
"conn": None,
|
||||
"transmitter": False,
|
||||
"receiver": False,
|
||||
"memory": None,
|
||||
}
|
||||
match page:
|
||||
case {
|
||||
"Key": _,
|
||||
@@ -149,7 +255,6 @@ def extract_all() -> None:
|
||||
"ModeInsert": modes,
|
||||
"ConnectionInsert": conninsert,
|
||||
}:
|
||||
|
||||
connNames = {
|
||||
int(insert["LogicAccessTypes"]): insert["LogicName"]
|
||||
for insert in conninsert
|
||||
@@ -157,10 +262,14 @@ def extract_all() -> None:
|
||||
|
||||
device = page.get("Device", None)
|
||||
item_props = page.get("Item", None)
|
||||
logicinfo = page.get("LogicInfo", None)
|
||||
wireless = page.get("WirelessLogic", False)
|
||||
receiver = page.get("TransmissionReceiver", False)
|
||||
memory = page.get("Memory", None)
|
||||
item["name"] = name
|
||||
item["hash"] = name_hash
|
||||
item["title"] = trans(title)
|
||||
item["desc"] = trans(re.sub(linkPat, r"\1", desc))
|
||||
item["desc"] = trans(strip_link(strip_color(desc)))
|
||||
match slots:
|
||||
case []:
|
||||
item["slots"] = None
|
||||
@@ -178,7 +287,7 @@ def extract_all() -> None:
|
||||
case _:
|
||||
item["logic"] = {}
|
||||
for lat in logic:
|
||||
item["logic"][re.sub(linkPat, r"\1", lat["LogicName"])] = (
|
||||
item["logic"][strip_link(strip_color(lat["LogicName"]))] = (
|
||||
lat["LogicAccessTypes"].replace(" ", "")
|
||||
)
|
||||
|
||||
@@ -189,8 +298,8 @@ def extract_all() -> None:
|
||||
item["slotlogic"] = {}
|
||||
for slt in slotlogic:
|
||||
item["slotlogic"][
|
||||
re.sub(linkPat, r"\1", slt["LogicName"])
|
||||
] = [int(s) for s in slt["LogicAccessTypes"].split(", ")]
|
||||
strip_link(strip_color(slt["LogicName"]))
|
||||
] = {s: "Read" for s in slt["LogicAccessTypes"].split(", ")}
|
||||
|
||||
match modes:
|
||||
case []:
|
||||
@@ -216,7 +325,6 @@ def extract_all() -> None:
|
||||
"HasActivateState": hasActivateState,
|
||||
"HasColorState": hasColorState,
|
||||
}:
|
||||
|
||||
match connections:
|
||||
case []:
|
||||
item["conn"] = None
|
||||
@@ -256,7 +364,7 @@ def extract_all() -> None:
|
||||
item["device"] = dbdevice
|
||||
|
||||
case _:
|
||||
print(f"NON-CONFORMING: ")
|
||||
print("NON-CONFORMING: ")
|
||||
pprint(device)
|
||||
return
|
||||
|
||||
@@ -305,18 +413,70 @@ def extract_all() -> None:
|
||||
|
||||
item["item"] = dbitem
|
||||
case _:
|
||||
print(f"NON-CONFORMING: ")
|
||||
print("NON-CONFORMING: ")
|
||||
pprint(item_props)
|
||||
return
|
||||
|
||||
match logicinfo:
|
||||
case None:
|
||||
pass
|
||||
case _:
|
||||
for lt, access in logicinfo["LogicTypes"].items():
|
||||
if item["logic"] is None:
|
||||
item["logic"] = {}
|
||||
item["logic"][lt] = access
|
||||
for slot, slotlogicinfo in logicinfo["LogicSlotTypes"].items():
|
||||
if item["slotlogic"] is None:
|
||||
item["slotlogic"] = {}
|
||||
if slot not in item["slotlogic"]:
|
||||
item["slotlogic"][slot] = {}
|
||||
for slt, access in slotlogicinfo.items():
|
||||
item["slotlogic"][slot][slt] = access
|
||||
|
||||
if wireless:
|
||||
item["transmitter"] = True
|
||||
if receiver:
|
||||
item["receiver"] = True
|
||||
|
||||
match memory:
|
||||
case None:
|
||||
pass
|
||||
case _:
|
||||
item["memory"] = {
|
||||
"size": memory["MemorySize"],
|
||||
"sizeDisplay": memory["MemorySizeReadable"],
|
||||
"access": memory["MemoryAccess"],
|
||||
"instructions": None,
|
||||
}
|
||||
instructions = memory.get("Instructions", None)
|
||||
match instructions:
|
||||
case None:
|
||||
pass
|
||||
case _:
|
||||
|
||||
def condense_lines(s: str) -> str:
|
||||
return "\r\n".join(
|
||||
[" ".join(line.split()) for line in s.splitlines()]
|
||||
)
|
||||
|
||||
item["memory"]["instructions"] = {
|
||||
inst: {
|
||||
"typ": info["Type"],
|
||||
"value": info["Value"],
|
||||
"desc": condense_lines(
|
||||
strip_color(strip_link(info["Description"]))
|
||||
),
|
||||
}
|
||||
for inst, info in instructions.items()
|
||||
}
|
||||
|
||||
case _:
|
||||
print(f"NON-CONFORMING: ")
|
||||
print("NON-CONFORMING: ")
|
||||
pprint(page)
|
||||
return
|
||||
|
||||
db[name] = item
|
||||
|
||||
|
||||
print("Translation codes:")
|
||||
pprint(translation_codes)
|
||||
print("Translations keys:")
|
||||
@@ -340,11 +500,28 @@ def extract_all() -> None:
|
||||
return [clean_nones(x) for x in value if x is not None] # type: ignore[unknown]
|
||||
elif isinstance(value, dict):
|
||||
return {
|
||||
key: clean_nones(val) for key, val in value.items() if val is not None # type: ignore[unknown]
|
||||
key: clean_nones(val)
|
||||
for key, val in value.items() # type:ignore[reportUnknownVariable]
|
||||
if val is not None
|
||||
}
|
||||
else:
|
||||
return value # type: ignore[Any]
|
||||
|
||||
enums: dict[str, dict[str, int]] = {}
|
||||
with open("data/Enums.json", "r") as f:
|
||||
exported_enums: dict[str, dict[str, int]] = json.load(f)
|
||||
for cat, cat_enums in exported_enums.items():
|
||||
for enum, val in cat_enums.items():
|
||||
key = cat
|
||||
if cat == "Enums":
|
||||
if "." in enum:
|
||||
key, enum = enum.split(".")
|
||||
else :
|
||||
key = "Condition"
|
||||
if key not in enums:
|
||||
enums[key] = {}
|
||||
enums[key][enum] = val
|
||||
|
||||
with open("data/database.json", "w") as f:
|
||||
json.dump(
|
||||
clean_nones(
|
||||
@@ -358,7 +535,8 @@ def extract_all() -> None:
|
||||
"names_by_hash": {
|
||||
page["hash"]: page["name"] for page in db.values()
|
||||
},
|
||||
"reagent_hashes": pedia["reagents"]
|
||||
"reagents": pedia["reagents"],
|
||||
"enums": enums,
|
||||
}
|
||||
),
|
||||
f,
|
||||
|
||||
Reference in New Issue
Block a user