From dbb2f71fdc02044495bc0fdbf4f2ff344bf649d0 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Wed, 24 Apr 2024 23:15:54 -0700
Subject: [PATCH] feat:(slots UI): functional slots
---
ic10emu/src/device.rs | 39 +++++++-------
ic10emu/src/vm.rs | 20 +++++++
ic10emu_wasm/src/lib.rs | 5 ++
www/src/ts/utils.ts | 4 ++
www/src/ts/virtual_machine/device/slot.ts | 18 ++++++-
.../virtual_machine/device/slot_add_dialog.ts | 52 ++++++++++++-------
www/src/ts/virtual_machine/index.ts | 16 +++++-
7 files changed, 111 insertions(+), 43 deletions(-)
diff --git a/ic10emu/src/device.rs b/ic10emu/src/device.rs
index 199b0a7..e8f4f68 100644
--- a/ic10emu/src/device.rs
+++ b/ic10emu/src/device.rs
@@ -152,13 +152,17 @@ impl SlotOccupant {
copy
}
- pub fn set_field(
- &mut self,
- field: SlotLogicType,
- val: f64,
- force: bool,
- ) -> Result<(), ICError> {
- if let Some(logic) = self.fields.get_mut(&field) {
+ pub fn set_field(&mut self, typ: SlotLogicType, val: f64, force: bool) -> Result<(), ICError> {
+ if (typ == SlotLogicType::Quantity) && force {
+ self.quantity = val as u32;
+ Ok(())
+ } else if (typ == SlotLogicType::MaxQuantity) && force {
+ self.max_quantity = val as u32;
+ Ok(())
+ } else if (typ == SlotLogicType::Damage) && force {
+ self.damage = val;
+ Ok(())
+ } else if let Some(logic) = self.fields.get_mut(&typ) {
match logic.field_type {
FieldType::ReadWrite | FieldType::Write => {
logic.value = val;
@@ -169,13 +173,13 @@ impl SlotOccupant {
logic.value = val;
Ok(())
} else {
- Err(ICError::ReadOnlyField(field.to_string()))
+ Err(ICError::ReadOnlyField(typ.to_string()))
}
}
}
} else if force {
self.fields.insert(
- field,
+ typ,
LogicField {
field_type: FieldType::ReadWrite,
value: val,
@@ -183,7 +187,7 @@ impl SlotOccupant {
);
Ok(())
} else {
- Err(ICError::ReadOnlyField(field.to_string()))
+ Err(ICError::ReadOnlyField(typ.to_string()))
}
}
@@ -392,27 +396,20 @@ impl Slot {
}
}
- pub fn set_field(
- &mut self,
- field: SlotLogicType,
- val: f64,
- force: bool,
- ) -> Result<(), ICError> {
+ pub fn set_field(&mut self, typ: SlotLogicType, val: f64, force: bool) -> Result<(), ICError> {
if matches!(
- field,
+ typ,
SlotLogicType::Occupied
| SlotLogicType::OccupantHash
- | SlotLogicType::Quantity
- | SlotLogicType::MaxQuantity
| SlotLogicType::Class
| SlotLogicType::PrefabHash
| SlotLogicType::SortingClass
| SlotLogicType::ReferenceId
) {
- return Err(ICError::ReadOnlyField(field.to_string()));
+ return Err(ICError::ReadOnlyField(typ.to_string()));
}
if let Some(occupant) = self.occupant.as_mut() {
- occupant.set_field(field, val, force)
+ occupant.set_field(typ, val, force)
} else {
Err(ICError::SlotNotOccupied)
}
diff --git a/ic10emu/src/vm.rs b/ic10emu/src/vm.rs
index d07e3c8..72a391c 100644
--- a/ic10emu/src/vm.rs
+++ b/ic10emu/src/vm.rs
@@ -768,11 +768,31 @@ impl VM {
}
let occupant = SlotOccupant::from_template(template, || self.id_space.next());
+ if let Some(last) = slot.occupant.as_ref() {
+ self.id_space.free_id(last.id);
+ }
slot.occupant = Some(occupant);
Ok(())
}
+ pub fn remove_slot_occupant(&mut self, id: u32, index: usize) -> Result<(), VMError> {
+ let Some(device) = self.devices.get(&id) else {
+ return Err(VMError::UnknownId(id));
+ };
+
+ let mut device_ref = device.borrow_mut();
+ let slot = device_ref
+ .slots
+ .get_mut(index)
+ .ok_or(ICError::SlotIndexOutOfRange(index as f64))?;
+ if let Some(last) = slot.occupant.as_ref() {
+ self.id_space.free_id(last.id);
+ }
+ slot.occupant = None;
+ Ok(())
+ }
+
pub fn save_vm_state(&self) -> FrozenVM {
FrozenVM {
ics: self.ics.values().map(|ic| ic.borrow().into()).collect(),
diff --git a/ic10emu_wasm/src/lib.rs b/ic10emu_wasm/src/lib.rs
index 7b5c173..571b795 100644
--- a/ic10emu_wasm/src/lib.rs
+++ b/ic10emu_wasm/src/lib.rs
@@ -495,6 +495,11 @@ impl VMRef {
Ok(self.vm.borrow_mut().set_slot_occupant(id, index, template)?)
}
+ #[wasm_bindgen(js_name = "removeSlotOccupant")]
+ pub fn remove_slot_occupant(&self, id: u32, index: usize) -> Result<(), JsError> {
+ Ok(self.vm.borrow_mut().remove_slot_occupant(id, index)?)
+ }
+
#[wasm_bindgen(js_name = "saveVMState", skip_typescript)]
pub fn save_vm_state(&self) -> JsValue {
let state = self.vm.borrow().save_vm_state();
diff --git a/www/src/ts/utils.ts b/www/src/ts/utils.ts
index 62c94b6..9dcdbb7 100644
--- a/www/src/ts/utils.ts
+++ b/www/src/ts/utils.ts
@@ -242,3 +242,7 @@ export function parseIntWithHexOrBinary(s: string): number {
}
return parseInt(s);
}
+
+export function clamp (val: number, min: number, max: number) {
+ return Math.min(Math.max(val, min), max);
+}
diff --git a/www/src/ts/virtual_machine/device/slot.ts b/www/src/ts/virtual_machine/device/slot.ts
index e200652..76b6694 100644
--- a/www/src/ts/virtual_machine/device/slot.ts
+++ b/www/src/ts/virtual_machine/device/slot.ts
@@ -4,7 +4,7 @@ import { BaseElement, defaultCss } from "components";
import { VMDeviceDBMixin, VMDeviceMixin } from "virtual_machine/base_device";
import type { DeviceDB, DeviceDBEntry } from "virtual_machine/device_db";
import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js";
-import { displayNumber, parseIntWithHexOrBinary, parseNumber } from "utils";
+import { clamp, displayNumber, parseIntWithHexOrBinary, parseNumber } from "utils";
import {
LogicType,
Slot,
@@ -147,6 +147,7 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
.value=${slot.occupant.quantity.toString()}
.min=${1}
.max=${slot.occupant.max_quantity}
+ @sl-change=${this._handleSlotQuantityChange}
>
Max Quantity: ${slot.occupant.max_quantity}
@@ -170,6 +171,15 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
);
}
+ _handleSlotQuantityChange(e: Event) {
+ const input = e.currentTarget as SlInput;
+ const slot = this.slots[this.slotIndex];
+ const val = clamp(input.valueAsNumber, 1, slot.occupant.max_quantity);
+ if (!window.VM.vm.setDeviceSlotField(this.deviceID, this.slotIndex, "Quantity", val, true)) {
+ input.value = this.device.getSlotField(this.slotIndex, "Quantity").toString();
+ }
+ }
+
renderFields() {
const inputIdBase = `vmDeviceSlot${this.deviceID}Slot${this.slotIndex}Field`;
const _fields = this.device.getSlotFields(this.slotIndex);
@@ -202,7 +212,11 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
_handleChangeSlotField(e: CustomEvent) {
const input = e.target as SlInput;
const field = input.getAttribute("key")! as SlotLogicType;
- const val = parseNumber(input.value);
+ let val = parseNumber(input.value);
+ if (field === "Quantity") {
+ const slot = this.slots[this.slotIndex];
+ val = clamp(input.valueAsNumber, 1, slot.occupant.max_quantity);
+ }
window.VM.get().then((vm) => {
if (
!vm.setDeviceSlotField(this.deviceID, this.slotIndex, field, val, true)
diff --git a/www/src/ts/virtual_machine/device/slot_add_dialog.ts b/www/src/ts/virtual_machine/device/slot_add_dialog.ts
index 6ad43fa..4bc2079 100644
--- a/www/src/ts/virtual_machine/device/slot_add_dialog.ts
+++ b/www/src/ts/virtual_machine/device/slot_add_dialog.ts
@@ -31,6 +31,7 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
];
private _items: Map
= new Map();
+ private _filteredItems: DeviceDBEntry[];
private _datapoints: [string, string][] = [];
private _haystack: string[] = [];
@@ -57,33 +58,41 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
.filter((entry) => this.deviceDB.items.includes(entry.name), this)
.map((entry) => [entry.name, entry]),
);
+ this.setupSearch();
this.performSearch();
}
- performSearch() {
- if (this._filter) {
+
+ setupSearch() {
+ let filteredItemss = Array.from(this._items.values());
+ if( typeof this.deviceID !== "undefined" && typeof this.slotIndex !== "undefined") {
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);
+ if (typeof typ === "string" && typ !== "None") {
+ filteredItemss = 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],
- );
- }
+ }
+ this._filteredItems= filteredItemss;
+ const datapoints: [string, string][] = [];
+ for (const entry of this._filteredItems) {
+ 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 haystack: string[] = datapoints.map((data) => data[0]);
+ this._datapoints = datapoints;
+ this._haystack = haystack;
+ }
+
+ performSearch() {
+ if (this._filter) {
const uf = new uFuzzy({});
const [_idxs, info, order] = uf.search(
@@ -113,7 +122,7 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
}));
} else {
// return everything
- this._searchResults = [...this._items.values()].map((st) => ({
+ this._searchResults = [...this._filteredItems].map((st) => ({
entry: st,
haystackEntry: st.title,
ranges: [],
@@ -130,7 +139,7 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
${this._searchResults.map((result) => {
const imgSrc = `img/stationpedia/${result.entry.name}.png`;
const img = html`
-
+
`;
return html`
@@ -144,7 +153,8 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
}
_handleClickNone() {
- console.log("Clear Slot");
+ window.VM.vm.removeDeviceSlotOccupant(this.deviceID, this.slotIndex);
+ this.hide();
}
_handleClickItem(e: Event) {
@@ -170,11 +180,13 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
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 };
+ fields["Quantity"] = { field_type: "Read", value: 1 };
const template: SlotOccupantTemplate = {
fields
}
window.VM.vm.setDeviceSlotOccupant(this.deviceID, this.slotIndex, template);
+ this.hide();
}
@query("sl-dialog.slot-add-dialog") dialog: SlDialog;
@@ -233,6 +245,8 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
show(deviceID: number, slotIndex: number) {
this.deviceID = deviceID;
this.slotIndex = slotIndex;
+ this.setupSearch();
+ this.performSearch();
this.dialog.show();
this.searchInput.select();
}
diff --git a/www/src/ts/virtual_machine/index.ts b/www/src/ts/virtual_machine/index.ts
index 9a5dfc1..dff3210 100644
--- a/www/src/ts/virtual_machine/index.ts
+++ b/www/src/ts/virtual_machine/index.ts
@@ -316,7 +316,7 @@ class VirtualMachine extends EventTarget {
const device = this._devices.get(id);
if (device) {
try {
- device.setSlotField(slot, field, val, false);
+ device.setSlotField(slot, field, val, force);
this.updateDevice(device);
return true;
} catch (err) {
@@ -411,6 +411,20 @@ class VirtualMachine extends EventTarget {
return false;
}
+ removeDeviceSlotOccupant(id: number, index: number): boolean {
+ const device = this._devices.get(id);
+ if (typeof device !== "undefined") {
+ try {
+ this.ic10vm.removeSlotOccupant(id, index);
+ this.updateDevice(device);
+ return true;
+ } catch (err) {
+ this.handleVmError(err);
+ }
+ }
+ return false;
+ }
+
saveVMState(): FrozenVM {
return this.ic10vm.saveVMState();
}