This commit is contained in:
2025-11-23 21:10:05 +01:00
parent b3c4395fac
commit 76dff51c6f
367 changed files with 1488 additions and 112 deletions

View File

@@ -0,0 +1,269 @@
<script lang="ts">
import { onMount } from 'svelte';
import { API_BASE } from '$lib/config';
import { Defaults } from './defaults.svelte';
import type { Skill, Connection} from './types.svelte';
export let currentCharacter: any;
export let currentCharacterData: any;
let characterData = currentCharacterData ?? {};
// Setup the character with default values if data is not existant
characterData.Info ??= Defaults.Info
characterData.Attributes ??= Defaults.Attributes
characterData.Skills ??= [];
characterData.Connections ??= [];
characterData.RangedWeapons ??= [];
characterData.MeleeWeapons ??= [];
characterData.Cyberware ??= [];
characterData.Bioware ??= [];
const characterInfoTypes = {
Metatype: "text",
Age: "number",
Sex: "text",
Nuyen: "number",
Lifestyle: "text",
"Total Karma": "number",
"C. Karma": "number",
"Street Cred": "number",
Notoriety: "number",
Fame: "number"
};
function add(key : keyof typeof Defaults) {
const copy = { ...Defaults[key] }; // OBS not shallow copy
characterData[key] = [...characterData[key], copy];
}
function remove(key : keyof typeof characterData, index: number) {
characterData[key] = characterData[key].filter((_: any, i: number) => i !== index);
}
function viewPage(pageNumber: number) {
if (pageNumber > 0) {
// Open in new tab
window.open(`${API_BASE}/assets/shadowrun/Shadowrun-4E-Corebook-p${pageNumber}.pdf`, "_blank");
}
}
async function saveCharacterData() {
const res = await fetch(`${API_BASE}/api/shadowrun/characters_data/${currentCharacter.id}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(characterData)
});
}
// Inventory
let inventory = currentCharacter?.inventory || [];
</script>
<h1>Name: {currentCharacter.name}</h1>
<h2>Character Info</h2>
{#each Object.entries(characterData.Info) as [key, value], i}
<div class="input-row">
<label for={"field-" + i}>{key}</label>
<input
id={"field-" + i}
type={characterInfoTypes[key]}
bind:value={characterData.Info[key]}
min={characterInfoTypes[key] === "number" ? 0 : null} />
</div>
{/each}
<h2>Attributes</h2>
{#each Object.entries(characterData.Attributes) as [key, value], i}
<div class="input-row">
<label for={"field-" + i}>{key}</label>
<input
id={"field-" + i}
type="number"
bind:value={characterData["Attributes"][key]}
min=0 />
</div>
{/each}
<div class="skill-form-container">
<h2>Skills</h2>
<div class="skill-headers">
<span class="col-name">Name</span>
<span class="col-value">Rating</span>
<span class="col-value">Attribute</span>
<span class="col-value">Dice Pool</span>
<span class="col-remove"></span>
</div>
{#each characterData.Skills as skill, i}
<div class="skill-entry">
<input class="col-name" placeholder="Name" bind:value={characterData.Skills[i].Name} />
<input class="col-value" type="number" min="0" bind:value={characterData.Skills[i].Rating} />
<select class="col-value" bind:value={characterData.Skills[i].Attribute}>
{#each Object.keys(Defaults.Attributes) as attr}
<option value={attr}>{attr}</option>
{/each}
</select>
<span class="col-value">{skill.Rating + characterData.Attributes[skill.Attribute]}</span>
<input type="number" min="1" bind:value={characterData.Skills[i].Page} />
<button on:click={() => viewPage(characterData.Skills[i].Page)}>View</button>
<button class="col-remove" on:click={() => remove("Skills", i)}>X</button>
</div>
{/each}
<button on:click={() => add("Skills")}>Add Skill</button>
<h2>Connections</h2>
<div class="skill-headers">
<span class="col-name">Name</span>
<span class="col-value">Loyalty</span>
<span class="col-value">Connection</span>
</div>
{#each characterData.Connections as connection, i}
<div class="skill-entry">
<input class="col-name" placeholder="Name" bind:value={characterData.Connections[i].Name} />
<input class="col-value" type="number" min="0" bind:value={characterData.Connections[i].Loyalty} />
<input class="col-value" type="number" min="0" bind:value={characterData.Connections[i].Connection} />
<button on:click={() => remove("Connections", i)}>X</button>
</div>
{/each}
<button on:click={() => add("Connections")}>Add Connection</button>
<h2>Ranged Weapons</h2>
<div class="skill-headers">
<span class="col-name">Weapon</span>
<span class="col-value">Damage</span>
<span class="col-value">Type</span>
<span class="col-value">AP</span>
<span class="col-value">Mode</span>
<span class="col-value">RC</span>
<span class="col-value">Ammo</span>
<span class="col-value">Availabiliy</span>
</div>
{#each characterData.RangedWeapons as weapon, i}
<div class="skill-entry">
<input class="col-name" placeholder="Name" bind:value={characterData.RangedWeapons[i].Weapon} />
<input class="col-value" type="number" min="0" bind:value={characterData.RangedWeapons[i].Damage} />
<input class="col-value" bind:value={characterData.RangedWeapons[i].Type} />
<input class="col-value" type="number" min="0" bind:value={characterData.RangedWeapons[i].AP} />
<input class="col-value" bind:value={characterData.RangedWeapons[i].Mode} />
<input class="col-value" type="number" min="0" bind:value={characterData.RangedWeapons[i].RC} />
<input class="col-value" type="number" min="0" bind:value={characterData.RangedWeapons[i].Ammo} />
<input class="col-value" bind:value={characterData.RangedWeapons[i].Availabiliy} />
<button on:click={() => remove("RangedWeapons", i)}>X</button>
</div>
{/each}
<button on:click={() => add("RangedWeapons")}>Add Ranged Weapon</button>
<h2>Melee Weapons</h2>
<div class="skill-headers">
<span class="col-name">Weapon</span>
<span class="col-value">Reach</span>
<span class="col-value">Damage</span>
<span class="col-value">Type</span>
<span class="col-value">Strength Multiplier</span>
<span class="col-value">Calculated Damage</span>
<span class="col-value">AP</span>
<span></span> <!-- for the remove button -->
</div>
{#each characterData.MeleeWeapons as weapon, i}
<div class="skill-entry">
<input class="col-name" placeholder="Weapon" bind:value={characterData.MeleeWeapons[i].Weapon} />
<input class="col-value" type="number" min="0" bind:value={characterData.MeleeWeapons[i].Reach} />
<input class="col-value" type="number" min="0" bind:value={characterData.MeleeWeapons[i].Damage} />
<input class="col-value" placeholder="Type" bind:value={characterData.MeleeWeapons[i].Type} />
<input class="col-value" type="number" min="0" bind:value={characterData.MeleeWeapons[i]["Strength Multiplier"]} />
<span class="col-value">{(weapon["Strength Multiplier"] * characterData.Attributes.Strength) + weapon.Damage }</span>
<input class="col-value" type="number" min="0" bind:value={characterData.MeleeWeapons[i].AP} />
<button on:click={() => remove("MeleeWeapons", i)}>X</button>
</div>
{/each}
<button on:click={() => add("MeleeWeapons")}>Add Melee Weapon</button>
<h2>Cyberware</h2>
<div class="skill-headers">
<span class="col-name">Cyberware</span>
<span class="col-value">Rating</span>
<span class="col-value">Essence</span>
<span class="col-value">Notes</span>
<span></span> <!-- for remove button -->
</div>
{#each characterData.Cyberware as Cyberware, i}
<div class="skill-entry">
<input class="col-name" placeholder="Cyberware" bind:value={characterData.Cyberware[i].Implant} />
<input class="col-value" type="number" min="0" bind:value={characterData.Cyberware[i].Rating} />
<input class="col-value" type="number" min="0" bind:value={characterData.Cyberware[i].Essence} />
<input class="col-value" placeholder="Notes" bind:value={characterData.Cyberware[i].Notes} />
<button on:click={() => remove("Cyberware", i)}>X</button>
</div>
{/each}
<button on:click={() => add("Cyberware")}>Add Cyberware</button>
<h2>Bioware</h2>
<div class="skill-headers">
<span class="col-name">Bioware</span>
<span class="col-value">Rating</span>
<span class="col-value">Essence</span>
<span class="col-name">Notes</span>
<span></span> <!-- for remove button -->
</div>
{#each characterData.Bioware as Bioware, i}
<div class="skill-entry">
<input class="col-name" placeholder="Bioware" bind:value={characterData.Bioware[i].Implant} />
<input class="col-value" type="number" min="0" bind:value={characterData.Bioware[i].Rating} />
<input class="col-value" type="number" min="0" bind:value={characterData.Bioware[i].Essence} />
<input class="col-name" placeholder="Notes" bind:value={characterData.Bioware[i].Notes} />
<button on:click={() => remove("Bioware", i)}>X</button>
</div>
{/each}
<button on:click={() => add("Bioware")}>Add Bioware</button>
</div>
<button on:click={saveCharacterData}>Save</button>
<style>
.skill-form-container {
width: 100%; /* 100% of the screen */
max-width: 1200px; /* optional, limits width on large screens */
}
.skill-headers, .skill-entry {
display: flex;
gap: 0.3rem;
align-items: center;
flex-wrap: wrap; /* allow wrapping on small screens */
}
.skill-headers {
font-weight: bold;
margin-bottom: 0.3rem;
}
.skill-headers span {
padding: 0;
margin: 0;
}
.skill-headers span,
.skill-entry input,
.skill-entry select,
.skill-entry span {
box-sizing: border-box;
}
.col-name {
flex: 2;
min-width: 80px;
}
.col-value {
flex: 1;
min-width: 40px;
}
.col-remove {
flex: 0;
}
</style>