push
This commit is contained in:
269
frontend/src/lib/shadorwun/character.svelte
Normal file
269
frontend/src/lib/shadorwun/character.svelte
Normal 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>
|
||||
Reference in New Issue
Block a user