feat: Complete character system, animated login, WebSocket sync
Character System: - Inventory system with 5,482 equipment items - Feats tab with categories and details - Actions tab with 99 PF2e actions - Item detail modal with equipment info - Feat detail modal with descriptions - Edit character modal with image cropping Auth & UI: - Animated login screen with splash → form transition - Letter-by-letter "DIMENSION 47" animation - Starfield background with floating orbs - Logo tap glow effect - "Remember me" functionality (localStorage/sessionStorage) Real-time Sync: - WebSocket gateway for character updates - Live sync for HP, conditions, inventory, equipment status, money, level Database: - Added credits field to characters - Added custom fields for items - Added feat fields and relations - Included full database backup Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,19 @@ interface ArmorJson {
|
||||
dex_cap?: string;
|
||||
}
|
||||
|
||||
interface ShieldJson {
|
||||
name: string;
|
||||
trait: string;
|
||||
item_category: string;
|
||||
item_subcategory: string;
|
||||
bulk: string;
|
||||
url: string;
|
||||
summary: string;
|
||||
ac?: string;
|
||||
hp?: string; // Format: "6 (3)" - HP und Broken Threshold
|
||||
hardness?: string;
|
||||
}
|
||||
|
||||
interface EquipmentJson {
|
||||
name: string;
|
||||
trait: string;
|
||||
@@ -66,6 +79,18 @@ function parseNumber(str: string | undefined): number | null {
|
||||
return isNaN(num) ? null : num;
|
||||
}
|
||||
|
||||
function parseShieldHp(hpStr: string | undefined): { hp: number | null; bt: number | null } {
|
||||
if (!hpStr || hpStr.trim() === '') return { hp: null, bt: null };
|
||||
// Format: "6 (3)" oder "12 (6)"
|
||||
const match = hpStr.match(/^(\d+)\s*\((\d+)\)$/);
|
||||
if (match) {
|
||||
return { hp: parseInt(match[1], 10), bt: parseInt(match[2], 10) };
|
||||
}
|
||||
// Fallback: nur HP
|
||||
const num = parseInt(hpStr, 10);
|
||||
return { hp: isNaN(num) ? null : num, bt: null };
|
||||
}
|
||||
|
||||
async function seedWeapons() {
|
||||
const dataPath = path.join(__dirname, 'data', 'weapons.json');
|
||||
const data: WeaponJson[] = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
|
||||
@@ -184,6 +209,63 @@ async function seedArmor() {
|
||||
console.log(` ✅ Created: ${created}, Updated: ${updated}, Errors: ${errors}`);
|
||||
}
|
||||
|
||||
async function seedShields() {
|
||||
const dataPath = path.join(__dirname, 'data', 'shields.json');
|
||||
const data: ShieldJson[] = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
|
||||
|
||||
console.log(`🛡️ Importing ${data.length} shields...`);
|
||||
|
||||
let created = 0;
|
||||
let updated = 0;
|
||||
let errors = 0;
|
||||
|
||||
for (const item of data) {
|
||||
try {
|
||||
const { hp, bt } = parseShieldHp(item.hp);
|
||||
const existing = await prisma.equipment.findUnique({ where: { name: item.name } });
|
||||
|
||||
if (existing) {
|
||||
await prisma.equipment.update({
|
||||
where: { name: item.name },
|
||||
data: {
|
||||
ac: parseNumber(item.ac) ?? existing.ac,
|
||||
shieldHp: hp ?? existing.shieldHp,
|
||||
shieldBt: bt ?? existing.shieldBt,
|
||||
shieldHardness: parseNumber(item.hardness) ?? existing.shieldHardness,
|
||||
traits: existing.traits.length > 0 ? existing.traits : parseTraits(item.trait),
|
||||
summary: existing.summary || item.summary || null,
|
||||
},
|
||||
});
|
||||
updated++;
|
||||
} else {
|
||||
await prisma.equipment.create({
|
||||
data: {
|
||||
name: item.name,
|
||||
traits: parseTraits(item.trait),
|
||||
itemCategory: item.item_category || 'Shields',
|
||||
itemSubcategory: item.item_subcategory || null,
|
||||
bulk: item.bulk || null,
|
||||
url: item.url || null,
|
||||
summary: item.summary || null,
|
||||
ac: parseNumber(item.ac),
|
||||
shieldHp: hp,
|
||||
shieldBt: bt,
|
||||
shieldHardness: parseNumber(item.hardness),
|
||||
},
|
||||
});
|
||||
created++;
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (errors < 3) {
|
||||
console.log(` ⚠️ Error for "${item.name}": ${error.message?.slice(0, 100)}`);
|
||||
}
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ Created: ${created}, Updated: ${updated}, Errors: ${errors}`);
|
||||
}
|
||||
|
||||
async function seedEquipment() {
|
||||
const dataPath = path.join(__dirname, 'data', 'equipment.json');
|
||||
const data: EquipmentJson[] = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
|
||||
@@ -233,10 +315,11 @@ async function main() {
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
// WICHTIG: Equipment zuerst, dann Waffen/Rüstung um spezifische Felder zu ergänzen
|
||||
// WICHTIG: Equipment zuerst, dann Waffen/Rüstung/Schilde um spezifische Felder zu ergänzen
|
||||
await seedEquipment();
|
||||
await seedWeapons(); // Ergänzt damage, hands, weapon_category etc.
|
||||
await seedArmor(); // Ergänzt ac, dex_cap etc.
|
||||
await seedShields(); // Ergänzt shieldHp, shieldHardness, shieldBt etc.
|
||||
|
||||
const totalCount = await prisma.equipment.count();
|
||||
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
|
||||
Reference in New Issue
Block a user