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

@@ -1,109 +0,0 @@
<script lang="ts">
import { onMount } from 'svelte';
import { API_BASE } from '$lib/config';
export let currentCharacter: any;
export let currentCharacterData: any;
let characterData = currentCharacterData ?? {};
characterData.Info ??= {
Metatype : "",
Age : 30,
Sex : "Man",
Nuyen: 0,
Lifestyle: "",
"Total Karma": 0,
"C. Karma": 0,
"Street Cred": 0,
Notoriety : 0,
Fame : 0
};
characterData.Attributes ??= {
Agility: 1,
Body: 1,
Charisma: 1,
Edge: 1,
Essence: 1,
Initiative: 1,
Intuition: 1,
Logic: 1,
Reaction: 1,
Strength: 1,
Willpower: 1,
};
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"
};
let skills = currentCharacter?.skills || [
{ name: 'Pistols', rating: 0, linked: 'Agility' },
{ name: 'Perception', rating: 0, linked: 'Intuition' },
{ name: 'Unarmed Combat', rating: 0, linked: 'Agility' }
];
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}
<!--
<h2>Skills</h2>
{#each skills as skill, i}
<div>
<input placeholder="Skill Name" bind:value={skill.name} />
<input type="number" min="0" bind:value={skill.rating} />
<select bind:value={skill.linked}>
{#each Object.keys(attributes) as attr}
<option value={attr}>{attr}</option>
{/each}
</select>
<span>Dice Pool: {skill.rating + attributes[skill.linked]}</span>
</div>
{/each}
-->
<button on:click={saveCharacterData}>Save</button>

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>

View File

@@ -0,0 +1,81 @@
<script lang="ts" context="module">
const Info = {
Metatype : "",
Age : 30,
Sex : "Man",
Nuyen: 0,
Lifestyle: "",
"Total Karma": 0,
"C. Karma": 0,
"Street Cred": 0,
Notoriety : 0,
Fame : 0
};
const Attributes = {
Agility: 1,
Body: 1,
Charisma: 1,
Edge: 1,
Essence: 1,
Initiative: 1,
Intuition: 1,
Logic: 1,
Reaction: 1,
Strength: 1,
Willpower: 1,
};
const Skill = {
Name: "",
Rating: 0,
Attribute: Object.keys(Attributes)[0]
}
const Connection = {
Name: "",
Loyalty: 0,
Connection: 0
}
const RangedWeapon = {
Weapon: "",
Damage: 0,
Type: "Piercing",
AP: 0,
Mode: "SA",
RC: 0,
Ammo: 0,
Availabiliy: "",
}
const MeleeWeapon = {
Weapon: "",
Reach: 0,
Damage: 1,
"Strength Multiplier" : 0.5,
AP: 0,
};
const Implant = {
Implant: "",
Rating: 0,
Essence: 0,
Notes: "",
}
export const Defaults = {
Info: Info,
Attributes: Attributes,
Skills: Skill,
Connections: Connection,
RangedWeapons : RangedWeapon,
MeleeWeapons : MeleeWeapon,
Cyberware : Implant,
Bioware : Implant,
}
</script>

View File

@@ -0,0 +1,42 @@
<script lang="ts" context="module">
export type Skill = {
Name: string;
Rating: number;
Attribute: string;
}
export type Connection = {
Name: string,
Loyalty: number,
Connection: number
}
export type RangeWeapon = {
Weapon: string,
Damage: number,
Type: string,
AP: number,
Mode: string,
RC: number,
Ammo: number,
Availabiliy: string,
}
export type MeleeWeapon = {
Weapon: string,
Reach: number,
Damage: number,
Type: string,
"Strength Multiplier" : number,
AP: number,
};
export type Implant = {
Implant: string,
Rating: number,
Essence: number,
Notes: string,
};
</script>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { API_BASE } from '$lib/config';
import CharacterSheet from '$lib/ShadowrunCharacter.svelte';
import CharacterSheet from '$lib/shadorwun/character.svelte';
type Character = {
id: number;