Skip to content

Commit 1a085f5

Browse files
feat: dev
1 parent e364b0f commit 1a085f5

File tree

4 files changed

+207
-16
lines changed

4 files changed

+207
-16
lines changed

scripts/Justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ set shell := ["bash", "-eu", "-o", "pipefail", "-c"]
33
MBIN_COMPILER_VERSION := "v6.13.0-pre1"
44
HGPAK_TOOL_VERSION := "1.0.0"
55

6-
default: clean setup extract-mappings extract-rewards
6+
default: clean setup extract-mappings extract-rewards extract-items
77

88
# ---------------------------------------------------------------------------
99
# 🧹 CLEANUP

scripts/src/extract_items.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@
2020

2121
items = {}
2222

23+
def remove_text_tags(text):
24+
if not text:
25+
return text
26+
# Loop through and remove everything in <> and the <> themselves
27+
while True:
28+
start = text.find("<")
29+
end = text.find(">")
30+
if start == -1 or end == -1:
31+
break
32+
text = text[:start] + text[end+1:]
33+
return text.strip()
34+
2335
def process_xml(xml_path):
2436
tree = ET.parse(xml_path)
2537
root = tree.getroot()
@@ -37,6 +49,7 @@ def process_xml(xml_path):
3749
for product in products:
3850
prod_id = None
3951
name_lower_key = None
52+
description_key = None
4053
texture_path = None
4154

4255
for prop in product.findall("Property"):
@@ -46,6 +59,8 @@ def process_xml(xml_path):
4659
name_lower_key = prop.get("value")
4760
if prop.get("name") == "NameLower":
4861
name_lower_key = prop.get("value")
62+
if prop.get("name") == "Description":
63+
description_key = prop.get("value")
4964
if prop.get("name") == "Template":
5065
template_value = prop.get("value")
5166
if template_value in items:
@@ -73,9 +88,11 @@ def process_xml(xml_path):
7388

7489
if prod_id and name_lower_key:
7590
translated_value = translations.get(name_lower_key, name_lower_key)
91+
description_value = translations.get(description_key, description_key) if description_key else None
7692

7793
items[prod_id] = {
7894
"name": translated_value,
95+
"description": remove_text_tags(description_value),
7996
"texture": texture_path
8097
}
8198

src/components/editors/InputInventory.svelte

Lines changed: 187 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,202 @@
22
import type { JSONSaveData } from '$lib/json';
33
import clsx from 'clsx';
44
import itemData from '../../data/items.json';
5+
import { ContextMenu, DropdownMenu } from 'bits-ui';
6+
import ChevronDownIcon from '@iconify-svelte/heroicons/chevron-down-20-solid';
57
68
type Props = {
79
value: JSONSaveData;
10+
type: 'cargo' | 'tech';
811
};
912
10-
let { value = $bindable() }: Props = $props();
13+
let { value = $bindable(), type = 'cargo' }: Props = $props();
1114
1215
let size = $derived(value.Width * value.Height);
16+
17+
let inventoryID = crypto.randomUUID();
18+
19+
function dragstartHandler(
20+
event: DragEvent & {
21+
currentTarget: EventTarget & HTMLDivElement;
22+
}
23+
) {
24+
if (event.dataTransfer == null) return;
25+
event.dataTransfer.setData('data', event.currentTarget.getAttribute('data-data') ?? '');
26+
event.dataTransfer.setData('inventory', inventoryID);
27+
}
28+
29+
function dragoverHandler(event: DragEvent) {
30+
event.preventDefault();
31+
}
32+
33+
function dropHandler(
34+
event: DragEvent & {
35+
currentTarget: EventTarget & HTMLDivElement;
36+
}
37+
) {
38+
event.preventDefault();
39+
if (event.dataTransfer == null) return;
40+
const inventory = event.dataTransfer.getData('inventory');
41+
if (inventory !== inventoryID) return;
42+
const data = JSON.parse(event.dataTransfer.getData('data'));
43+
44+
// Remove item from its previous slot
45+
value.Slots = value.Slots.filter((slot: JSONSaveData) => !(slot.Index.X === data.Index.X && slot.Index.Y === data.Index.Y));
46+
value.Slots.push({
47+
...data,
48+
Index: {
49+
X: event.currentTarget.dataset.x ? parseInt(event.currentTarget.dataset.x) : 0,
50+
Y: event.currentTarget.dataset.y ? parseInt(event.currentTarget.dataset.y) : 0
51+
}
52+
});
53+
}
54+
55+
function refillAllSlots() {
56+
for (const slot of value.Slots) {
57+
if (slot.Amount >= 0) slot.Amount = slot.MaxAmount;
58+
}
59+
}
60+
61+
function clearSlots() {
62+
value.Slots = [];
63+
}
64+
65+
function enableAllSlots() {
66+
for (let y = 0; y < value.Height; y++) {
67+
for (let x = 0; x < value.Width; x++) {
68+
if (!value.ValidSlotIndices.some((slot: JSONSaveData) => slot.X === x && slot.Y === y)) {
69+
value.ValidSlotIndices.push({ X: x, Y: y });
70+
}
71+
}
72+
}
73+
}
74+
75+
function superchargeAllSlots() {
76+
for (let y = 0; y < value.Height; y++) {
77+
for (let x = 0; x < value.Width; x++) {
78+
if (!value.SpecialSlots.some((slot: JSONSaveData) => slot.Index.X === x && slot.Index.Y === y && slot.Type.InventorySpecialSlotType === 'TechBonus')) {
79+
value.SpecialSlots.push({ Index: { X: x, Y: y }, Type: { InventorySpecialSlotType: 'TechBonus' } });
80+
}
81+
}
82+
}
83+
}
1384
</script>
1485

15-
<div class="grid w-full grid-cols-3 grid-rows-2 gap-px" style={`grid-template-columns: repeat(${value.Width}, minmax(0, 1fr)); grid-template-rows: repeat(${value.Height}, minmax(0, 1fr));`}>
86+
<div class="mt-10">
87+
<button type="button" class="cursor-pointer rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-xs inset-ring inset-ring-gray-300 hover:bg-gray-50 dark:bg-white/10 dark:text-white dark:inset-ring-white/5 dark:hover:bg-white/20"
88+
>Resize</button
89+
>
90+
<DropdownMenu.Root>
91+
<DropdownMenu.Trigger
92+
class="inline-flex cursor-pointer justify-center gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-xs inset-ring-1 inset-ring-gray-300 hover:bg-gray-50 dark:bg-white/10 dark:text-white dark:inset-ring-white/5 dark:hover:bg-white/20"
93+
>
94+
Quick Actions
95+
<ChevronDownIcon aria-hidden="true" class="-mr-1 size-5 text-gray-400" width="20" height="20" />
96+
</DropdownMenu.Trigger>
97+
<DropdownMenu.Portal>
98+
<DropdownMenu.Content class="mt-2 w-56 rounded-md bg-white shadow-lg outline-1 outline-black/5 dark:bg-gray-800 dark:outline-white/10">
99+
<div class="py-1">
100+
<DropdownMenu.Item
101+
onclick={refillAllSlots}
102+
class="block cursor-pointer px-4 py-2 text-sm text-gray-700 data-highlighted:bg-gray-100 data-highlighted:text-gray-900 data-highlighted:outline-hidden dark:text-gray-300 dark:data-highlighted:bg-white/5 dark:data-highlighted:text-white"
103+
>Refill All Slots</DropdownMenu.Item
104+
>
105+
106+
<DropdownMenu.Item
107+
onclick={enableAllSlots}
108+
class="block cursor-pointer px-4 py-2 text-sm text-gray-700 data-highlighted:bg-gray-100 data-highlighted:text-gray-900 data-highlighted:outline-hidden dark:text-gray-300 dark:data-highlighted:bg-white/5 dark:data-highlighted:text-white"
109+
>Enable All Slots</DropdownMenu.Item
110+
>
111+
{#if type === 'tech'}
112+
<DropdownMenu.Item
113+
onclick={superchargeAllSlots}
114+
class="block cursor-pointer px-4 py-2 text-sm text-gray-700 data-highlighted:bg-gray-100 data-highlighted:text-gray-900 data-highlighted:outline-hidden dark:text-gray-300 dark:data-highlighted:bg-white/5 dark:data-highlighted:text-white"
115+
>Turbocharge All Slots</DropdownMenu.Item
116+
>
117+
{/if}
118+
<DropdownMenu.Item
119+
onclick={clearSlots}
120+
class="block cursor-pointer px-4 py-2 text-sm text-gray-700 data-highlighted:bg-gray-100 data-highlighted:text-gray-900 data-highlighted:outline-hidden dark:text-gray-300 dark:data-highlighted:bg-white/5 dark:data-highlighted:text-white"
121+
>Clear</DropdownMenu.Item
122+
>
123+
</div>
124+
</DropdownMenu.Content>
125+
</DropdownMenu.Portal>
126+
</DropdownMenu.Root>
127+
</div>
128+
129+
<div id={inventoryID} class="relative mt-4 grid w-full gap-px bg-gray-200 p-px dark:bg-white/10" style={`grid-template-columns: repeat(${value.Width}, minmax(0, 1fr)); grid-template-rows: repeat(${value.Height}, minmax(0, 1fr));`}>
16130
{#each Array.from({ length: size }, (_, i) => i) as index}
17-
{@const item = (value.Slots as { Index: { X: number; Y: number }; Id: string }[]).find((slot) => slot.Index.X + slot.Index.Y * value.Width === index)}
18-
{@const isSlotEnabled = (value.ValidSlotIndices as { X: number; Y: number }[]).some((slot) => slot.X + slot.Y * value.Width === index)}
19-
{@const isSlotSpecial = (value.SpecialSlots as { Index: { X: number; Y: number } }[]).some((slot) => slot.Index.X + slot.Index.Y * value.Width === index)}
20-
<div class={clsx('group relative flex aspect-square flex-col items-center justify-center gap-2 overflow-hidden px-3 py-2 text-gray-500', isSlotEnabled ? 'bg-white' : 'bg-gray-300', isSlotSpecial ? 'inset-ring-4 inset-ring-yellow-100' : '')}>
21-
{#if item}
22-
{@const itemMeta = itemData[item.Id.replaceAll('^', '').split('#')[0]] ?? { texture: '', name: item.Id }}
23-
<img class="aspect-square w-1/2" src={itemMeta.texture} alt="" />
24-
<p class="text-xs">{itemMeta.name}</p>
25-
<p class="text-xs">{item.Amount}/{item.MaxAmount}</p>
26-
{/if}
27-
</div>
131+
{@const item = value.Slots.find((slot: JSONSaveData) => slot.Index.X + slot.Index.Y * value.Width === index)}
132+
{@const isSlotEnabled = value.ValidSlotIndices.some((slot: JSONSaveData) => slot.X + slot.Y * value.Width === index)}
133+
{@const isSlotSpecial = value.SpecialSlots.some((slot: JSONSaveData) => slot.Index.X + slot.Index.Y * value.Width === index && slot.Type.InventorySpecialSlotType === 'TechBonus')}
134+
<ContextMenu.Root>
135+
<ContextMenu.Trigger
136+
data-x={index % value.Width}
137+
data-y={Math.floor(index / value.Width)}
138+
ondragover={dragoverHandler}
139+
ondrop={item ? null : dropHandler}
140+
class={clsx('group relative aspect-video w-full overflow-hidden', isSlotEnabled ? 'bg-white dark:bg-gray-900' : 'bg-gray-100 dark:bg-gray-900/50', isSlotSpecial ? 'inset-ring-4 inset-ring-yellow-100' : '')}
141+
>
142+
{#if item}
143+
{@const itemMeta = itemData[item.Id.replaceAll('^', '').split('#')[0]] ?? { texture: '', name: item.Id }}
144+
<div class="isolate flex size-full cursor-grab flex-col items-center justify-end px-1 pb-0.5" draggable="true" data-data={JSON.stringify(item)} ondragstart={dragstartHandler} role="cell" tabindex="-1">
145+
<img class="pointer-events-none absolute inset-0 -z-10 h-2/3 w-full object-contain p-2 select-none" src={itemMeta.texture} alt="" />
146+
<p class="pointer-events-none truncate text-xs font-medium text-gray-900 select-none group-hover:text-primary-600 dark:text-white dark:group-hover:text-primary-400">{itemMeta.name}</p>
147+
{#if item.Amount >= 0}
148+
<p class="pointer-events-none text-xs text-gray-500 select-none group-hover:text-primary-600 dark:text-gray-400 dark:group-hover:text-primary-400">{item.Amount}/{item.MaxAmount}</p>
149+
{/if}
150+
</div>
151+
{/if}
152+
</ContextMenu.Trigger>
153+
<ContextMenu.Portal>
154+
<ContextMenu.Content class="mt-2 w-56 rounded-md bg-white shadow-lg outline-1 outline-black/5 dark:bg-gray-800 dark:outline-white/10">
155+
<div class="py-1">
156+
{#if item}
157+
<ContextMenu.Item
158+
onclick={() => (value.Slots = value.Slots.filter((slot: JSONSaveData) => !(slot.Index.X === item.Index.X && slot.Index.Y === item.Index.Y)))}
159+
class="block cursor-pointer px-4 py-2 text-sm text-gray-700 data-highlighted:bg-gray-100 data-highlighted:text-gray-900 data-highlighted:outline-hidden dark:text-gray-300 dark:data-highlighted:bg-white/5 dark:data-highlighted:text-white"
160+
>Remove Item</ContextMenu.Item
161+
>
162+
{#if item.Amount >= 0 && item.Amount < item.MaxAmount}
163+
<ContextMenu.Item
164+
onclick={() => (item.Amount >= 0 ? (item.Amount = item.MaxAmount) : null)}
165+
class="block cursor-pointer px-4 py-2 text-sm text-gray-700 data-highlighted:bg-gray-100 data-highlighted:text-gray-900 data-highlighted:outline-hidden dark:text-gray-300 dark:data-highlighted:bg-white/5 dark:data-highlighted:text-white"
166+
>Refill Slot</ContextMenu.Item
167+
>
168+
{/if}
169+
{/if}
170+
{#if isSlotEnabled}
171+
<ContextMenu.Item
172+
onclick={() => (value.ValidSlotIndices = value.ValidSlotIndices.filter((slot: JSONSaveData) => !(slot.X === index % value.Width && slot.Y === Math.floor(index / value.Width))))}
173+
class="block cursor-pointer px-4 py-2 text-sm text-gray-700 data-highlighted:bg-gray-100 data-highlighted:text-gray-900 data-highlighted:outline-hidden dark:text-gray-300 dark:data-highlighted:bg-white/5 dark:data-highlighted:text-white"
174+
>Disable Slot</ContextMenu.Item
175+
>
176+
{:else}
177+
<ContextMenu.Item
178+
onclick={() => value.ValidSlotIndices.push({ X: index % value.Width, Y: Math.floor(index / value.Width) })}
179+
class="block cursor-pointer px-4 py-2 text-sm text-gray-700 data-highlighted:bg-gray-100 data-highlighted:text-gray-900 data-highlighted:outline-hidden dark:text-gray-300 dark:data-highlighted:bg-white/5 dark:data-highlighted:text-white"
180+
>Enable Slot</ContextMenu.Item
181+
>
182+
{/if}
183+
{#if type === 'tech'}
184+
{#if isSlotSpecial}
185+
<ContextMenu.Item
186+
onclick={() => (value.SpecialSlots = value.SpecialSlots.filter((slot: JSONSaveData) => !(slot.Index.X === index % value.Width && slot.Index.Y === Math.floor(index / value.Width) && slot.Type.InventorySpecialSlotType === 'TechBonus')))}
187+
class="block cursor-pointer px-4 py-2 text-sm text-gray-700 data-highlighted:bg-gray-100 data-highlighted:text-gray-900 data-highlighted:outline-hidden dark:text-gray-300 dark:data-highlighted:bg-white/5 dark:data-highlighted:text-white"
188+
>Unturbocharge Slot</ContextMenu.Item
189+
>
190+
{:else}
191+
<ContextMenu.Item
192+
onclick={() => value.SpecialSlots.push({ Index: { X: index % value.Width, Y: Math.floor(index / value.Width) }, Type: { InventorySpecialSlotType: 'TechBonus' } })}
193+
class="block cursor-pointer px-4 py-2 text-sm text-gray-700 data-highlighted:bg-gray-100 data-highlighted:text-gray-900 data-highlighted:outline-hidden dark:text-gray-300 dark:data-highlighted:bg-white/5 dark:data-highlighted:text-white"
194+
>Turbocharge Slot</ContextMenu.Item
195+
>
196+
{/if}
197+
{/if}
198+
</div>
199+
</ContextMenu.Content>
200+
</ContextMenu.Portal>
201+
</ContextMenu.Root>
28202
{/each}
29203
</div>

src/routes/save/exosuit/+page.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@
3030
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">Inventory</h2>
3131
<p class="mt-1 text-sm/6 text-gray-600 dark:text-gray-400">Modify the exosuit's inventory items.</p>
3232
<div class="mt-10">
33-
<InputInventory bind:value={editorData.data.BaseContext.PlayerStateData.Inventory} />
33+
<InputInventory bind:value={editorData.data.BaseContext.PlayerStateData.Inventory} type="cargo" />
3434
</div>
3535
</div>
3636
<div class="border-b border-gray-900/10 pb-12 dark:border-white/10">
3737
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">Inventory_TechOnly</h2>
3838
<p class="mt-1 text-sm/6 text-gray-600 dark:text-gray-400">Modify the exosuit's inventory items.</p>
3939
<div class="mt-10">
40-
<InputInventory bind:value={editorData.data.BaseContext.PlayerStateData.Inventory_TechOnly} />
40+
<InputInventory bind:value={editorData.data.BaseContext.PlayerStateData.Inventory_TechOnly} type="tech" />
4141
</div>
4242
</div>
4343
</div>

0 commit comments

Comments
 (0)