Ga naar hoofdinhoud

Items Beheer

Admin interface voor item definities en container types.

Item Definities

Overzicht Pagina

┌─────────────────────────────────────────────────────────┐
│ Items Beheer [+ Nieuw Item]│
├─────────────────────────────────────────────────────────┤
│ Filter: [Alle categorieën ▼] [Zoeken... ] 🔍│
├─────────────────────────────────────────────────────────┤
│ ┌─────┐ honey_jar │ food │ 99 │ common │ ✏️│
│ │ 🍯 │ Honingpot │ │ │ │ │
│ └─────┘ │ │ │ │ │
├─────────────────────────────────────────────────────────┤
│ ┌─────┐ oak_seed │ seed │ 99 │ common │ ✏️│
│ │ 🌰 │ Eikenzaadje │ │ │ │ │
│ └─────┘ │ │ │ │ │
└─────────────────────────────────────────────────────────┘

Item Formulier

// components/ItemForm.tsx
interface ItemFormProps {
item?: ItemDefinition;
onSave: (item: ItemDefinition) => void;
}

export function ItemForm({ item, onSave }: ItemFormProps) {
const [formData, setFormData] = useState({
itemId: item?.itemId ?? '',
category: item?.category ?? 'item',
displayName: item?.displayName ?? '',
description: item?.description ?? '',
iconPath: item?.iconPath ?? '',
maxStack: item?.maxStack ?? 99,
isTradeable: item?.isTradeable ?? true,
rarity: item?.rarity ?? 0,
});

return (
<form onSubmit={handleSubmit}>
{/* Item ID (alleen bij nieuw) */}
{!item && (
<Input
label="Item ID"
value={formData.itemId}
onChange={e => setFormData({...formData, itemId: e.target.value})}
placeholder="honey_jar"
pattern="[a-z_]+"
/>
)}

{/* Categorie */}
<Select
label="Categorie"
value={formData.category}
onChange={e => setFormData({...formData, category: e.target.value})}
>
<option value="food">Voedsel</option>
<option value="seed">Zaden</option>
<option value="tool">Gereedschap</option>
<option value="material">Materiaal</option>
<option value="decoration">Decoratie</option>
</Select>

{/* Display Name */}
<Input
label="Weergavenaam"
value={formData.displayName}
onChange={e => setFormData({...formData, displayName: e.target.value})}
/>

{/* etc... */}
</form>
);
}

Categorieën

CategorieBeschrijvingVoorbeelden
foodEetbare itemsHoning, Appels, Brood
seedPlantbare zadenWortelzaad, Tomatenzaad
toolGereedschapSchep, Gieter, Bijl
materialGrondstoffenHout, Steen, IJzer
decorationDecoratiesBloemen, Beelden
containerRugzakkenStarter bag, Backpack
specialSpeciale itemsQuest items, Keys

Rarity Levels

LevelNaamKleurDrop Rate
0CommonWit70%
1UncommonGroen20%
2RareBlauw8%
3LegendaryGoud2%

Container Types

Beheer Interface

┌─────────────────────────────────────────────────────────┐
│ Container Types [+ Nieuw Type] │
├─────────────────────────────────────────────────────────┤
│ Type ID │ Naam │ Slots │ Acties │
├─────────────────────────────────────────────────────────┤
│ starter_bag │ Starter Rugzak │ 8 │ ✏️ 🗑️ │
│ leather_backpack │ Leren Rugzak │ 12 │ ✏️ 🗑️ │
│ adventure_pack │ Avonturen Rugzak │ 16 │ ✏️ 🗑️ │
│ pouch_small │ Kleine Buidel │ 4 │ ✏️ 🗑️ │
└─────────────────────────────────────────────────────────┘

Container Balancing

TierSlotsVerkrijgbaar via
18Start van spel
212Shop (500 eikels)
316Quest reward
424Club project
532Legendary drop

Bulk Import/Export

Export naar JSON

async function exportItems() {
const items = await spacetime.query('SELECT * FROM item_definition');
const json = JSON.stringify(items, null, 2);

// Download als bestand
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
downloadFile(url, 'items_export.json');
}

Import JSON

async function importItems(file: File) {
const json = await file.text();
const items = JSON.parse(json);

for (const item of items) {
await spacetime.call('create_item_definition', [
item.item_id,
item.category,
item.display_name,
item.description,
item.icon_path,
item.max_stack,
item.is_tradeable,
item.rarity
]);
}
}

JSON Format

[
{
"item_id": "honey_jar",
"category": "food",
"display_name": "Honingpot",
"description": "Zoete honing van de bijen",
"icon_path": "items/honey_jar.png",
"max_stack": 99,
"is_tradeable": true,
"rarity": 0
},
{
"item_id": "golden_shovel",
"category": "tool",
"display_name": "Gouden Schep",
"description": "Een glimmende schep die sneller graaft",
"icon_path": "items/golden_shovel.png",
"max_stack": 1,
"is_tradeable": false,
"rarity": 3
}
]

Icon Preview

// components/IconPreview.tsx
export function IconPreview({ iconPath }: { iconPath: string }) {
const [error, setError] = useState(false);

// Probeer van R2 storage te laden
const fullUrl = `${process.env.R2_PUBLIC_URL}/assets/${iconPath}`;

if (error) {
return <PlaceholderIcon />;
}

return (
<img
src={fullUrl}
alt="Item icon"
onError={() => setError(true)}
className="w-16 h-16 object-contain"
/>
);
}

Validatie

Item ID

  • Alleen lowercase letters en underscores
  • 3-50 karakters
  • Uniek

Icon Path

  • Moet eindigen op .png of .svg
  • Pad relatief aan assets folder
const validateIconPath = (path: string) => {
if (!path.match(/\.(png|svg)$/)) {
return 'Icon moet .png of .svg zijn';
}
if (path.startsWith('/') || path.includes('..')) {
return 'Ongeldig pad';
}
return null;
};

Volgende