Komplexe berechnungen am Frontend und backend

This commit is contained in:
2025-08-13 11:40:06 +02:00
parent 2e32363863
commit 93167f162c
7 changed files with 1302 additions and 89 deletions
@@ -18,17 +18,14 @@
required
/>
<button
v-if="value.key === 'pa' || value.key === 'wk' || value.key === 'lp_max'"
v-if="value.key === 'pa' || value.key === 'wk' || value.key === 'lp_max' || value.key === 'ap_max' || value.key === 'b_max'"
type="button"
class="dice-btn"
@click="value.key === 'pa' ? rollPA() :
value.key === 'wk' ? rollWK() :
value.key === 'lp_max' ? rollLP() : null"
:title="value.key === 'pa' ? 'Roll PA: 1d100 + 4×(In/10) - 20' :
value.key === 'wk' ? 'Roll WK: 1d100 + 2×(Ko/10 + In/10) - 20' :
value.key === 'lp_max' ? 'Roll LP: 1d3 + 7 + (Ko/10)' : ''"
@click="rollField(value.key)"
:title="getDiceTooltip(value.key)"
:disabled="isCalculating"
>
🎲
{{ isCalculating ? '⏳' : '🎲' }}
</button>
</div>
<div class="value-info">
@@ -61,8 +58,8 @@
<button type="button" @click="handlePrevious" class="prev-btn">
{{ $t('characters.derivedValues.previousAttributes') }}
</button>
<button type="button" @click="recalculate" class="calc-btn">
{{ $t('characters.derivedValues.recalculate') }}
<button type="button" @click="calculateAllStatic" class="calc-btn" :disabled="isCalculating">
{{ isCalculating ? $t('characters.derivedValues.calculating') : $t('characters.derivedValues.recalculate') }}
</button>
<button type="submit" class="next-btn" :disabled="!isValid">
{{ $t('characters.derivedValues.nextSkills') }}
@@ -73,6 +70,9 @@
</template>
<script>
import API from '../../utils/api'
import { rollDie, rollDice } from '../../utils/randomUtils'
export default {
name: 'CharacterDerivedValues',
props: {
@@ -85,27 +85,28 @@ export default {
data() {
return {
formData: {
pa: 50, // Persönliche Ausstrahlung
wk: 50, // Willenskraft
lp_max: 20,
ap_max: 20,
b_max: 50,
resistenz_koerper: 50, // Resistenz Körper
resistenz_geist: 50, // Resistenz Geist
pa: 0, // Persönliche Ausstrahlung
wk: 0, // Willenskraft
lp_max: 0,
ap_max: 0,
b_max: 0,
resistenz_koerper: 0, // Resistenz Körper
resistenz_geist: 0, // Resistenz Geist
resistenz_bonus_koerper: 0, // Resistenz Bonus Körper
resistenz_bonus_geist: 0, // Resistenz Bonus Geist
abwehr: 50, // Abwehr
abwehr: 0, // Abwehr
abwehr_bonus: 0, // Abwehr Bonus
ausdauer_bonus: 0, // Ausdauer Bonus
angriffs_bonus: 0, // Angriffs Bonus
zaubern: 50, // Zaubern
zaubern: 0, // Zaubern
zauber_bonus: 0, // Zauber Bonus
raufen: 50, // Raufen
raufen: 0, // Raufen
schadens_bonus: 0, // Schadens Bonus
sg: 1, // Schicksalsgunst
gg: 1, // Göttliche Gnade
gp: 1, // Glückspunkte
sg: 0, // Schicksalsgunst
gg: 0, // Göttliche Gnade
gp: 0, // Glückspunkte
},
isCalculating: false,
derivedValues: [
{
key: 'pa',
@@ -126,7 +127,7 @@ export default {
name: 'characters.derivedValues.lpMax',
description: 'characters.derivedValues.lpMaxDescription',
min: 1,
max: 200
max: 50
},
{
key: 'ap_max',
@@ -140,49 +141,49 @@ export default {
name: 'characters.derivedValues.bMax',
description: 'characters.derivedValues.bMaxDescription',
min: 1,
max: 500
max: 50
},
{
key: 'resistenz_koerper',
name: 'characters.derivedValues.resistenzKoerper',
description: 'characters.derivedValues.resistenzKoerperDescription',
min: 1,
max: 100
max: 20
},
{
key: 'resistenz_geist',
name: 'characters.derivedValues.resistenzGeist',
description: 'characters.derivedValues.resistenzGeistDescription',
min: 1,
max: 100
max: 20
},
{
key: 'resistenz_bonus_koerper',
name: 'characters.derivedValues.resistenzBonusKoerper',
description: 'characters.derivedValues.resistenzBonusKoerperDescription',
min: -50,
max: 50
min: -5,
max: 5
},
{
key: 'resistenz_bonus_geist',
name: 'characters.derivedValues.resistenzBonusGeist',
description: 'characters.derivedValues.resistenzBonusGeistDescription',
min: -50,
max: 50
min: -5,
max: 5
},
{
key: 'abwehr',
name: 'characters.derivedValues.abwehr',
description: 'characters.derivedValues.abwehrDescription',
min: 1,
max: 100
max: 20
},
{
key: 'abwehr_bonus',
name: 'characters.derivedValues.abwehrBonus',
description: 'characters.derivedValues.abwehrBonusDescription',
min: -50,
max: 50
min: -5,
max: 5
},
{
key: 'ausdauer_bonus',
@@ -195,57 +196,57 @@ export default {
key: 'angriffs_bonus',
name: 'characters.derivedValues.angriffsBonus',
description: 'characters.derivedValues.angriffsBonusDescription',
min: -50,
max: 50
min: -5,
max: 5
},
{
key: 'zaubern',
name: 'characters.derivedValues.zaubern',
description: 'characters.derivedValues.zaubernDescription',
min: 1,
max: 100
max: 20
},
{
key: 'zauber_bonus',
name: 'characters.derivedValues.zauberBonus',
description: 'characters.derivedValues.zauberBonusDescription',
min: -50,
max: 50
min: -5,
max: 5
},
{
key: 'raufen',
name: 'characters.derivedValues.raufen',
description: 'characters.derivedValues.raufenDescription',
min: 1,
max: 100
max: 20
},
{
key: 'schadens_bonus',
name: 'characters.derivedValues.schadensBonus',
description: 'characters.derivedValues.schadensBonusDescription',
min: -50,
max: 50
min: -10,
max: 10
},
{
key: 'sg',
name: 'characters.derivedValues.sg',
description: 'characters.derivedValues.sgDescription',
min: 0,
max: 10
max: 50
},
{
key: 'gg',
name: 'characters.derivedValues.gg',
description: 'characters.derivedValues.ggDescription',
min: 0,
max: 10
max: 50
},
{
key: 'gp',
name: 'characters.derivedValues.gp',
description: 'characters.derivedValues.gpDescription',
min: 0,
max: 10
max: 50
},
],
}
@@ -259,43 +260,161 @@ export default {
},
calculatedValues() {
const attrs = this.sessionData.attributes || {}
const wkValue = this.formData?.wk || this.calculateWK(attrs.ko || 50, attrs.in || 50)
return {
pa: this.calculatePA(attrs.in || 50),
wk: this.calculateWK(attrs.ko || 50, attrs.in || 50),
lp_max: this.calculateLP(attrs.ko || 50),
ap_max: Math.floor(((attrs.au || 50) + wkValue) / 2) + 5,
b_max: (attrs.st || 50) + 10,
resistenz_koerper: attrs.ko || 50, // Resistenz Körper = Konstitution
resistenz_geist: wkValue, // Resistenz Geist = Willenskraft
resistenz_bonus_koerper: Math.floor((attrs.ko || 50) / 20) - 2, // Bonus basierend auf Ko
resistenz_bonus_geist: Math.floor(wkValue / 20) - 2, // Bonus basierend auf WK
abwehr: Math.floor(((attrs.gw || 50) + (attrs.gs || 50)) / 2), // Abwehr = (Gewandtheit + Geschicklichkeit) / 2
abwehr_bonus: Math.floor(((attrs.gw || 50) + (attrs.gs || 50)) / 40) - 2, // Abwehr Bonus
ausdauer_bonus: Math.floor((attrs.ko || 50) / 20) - 2, // Ausdauer Bonus basierend auf Ko
angriffs_bonus: Math.floor((attrs.gs || 50) / 20) - 2, // Angriffs Bonus basierend auf Gs
zaubern: attrs.zt || 50, // Zaubern = Zaubertalent
zauber_bonus: Math.floor((attrs.zt || 50) / 20) - 2, // Zauber Bonus
raufen: Math.floor(((attrs.st || 50) + (attrs.gw || 50)) / 2), // Raufen = (Stärke + Gewandtheit) / 2
schadens_bonus: Math.floor((attrs.st || 50) / 20) - 2, // Schadens Bonus basierend auf St
sg: this.getClassBonnie('sg'),
gg: this.getClassBonnie('gg'),
gp: this.getClassBonnie('gp'),
}
// Return currently loaded values or defaults
// The actual calculation happens via API calls
return this.formData
}
},
watch: {
formData: {
handler(newValue) {
// Save changes automatically when form data changes
this.$emit('save', { derived_values: newValue })
},
deep: true
}
},
created() {
// Initialize with calculated values first
this.formData = { ...this.calculatedValues }
// Then override with session data if available
// Initialize with existing session data if available
if (this.sessionData.derived_values && Object.keys(this.sessionData.derived_values).length > 0) {
this.formData = { ...this.formData, ...this.sessionData.derived_values }
} else {
// Calculate initial values using new API
this.calculateAllStatic()
}
},
methods: {
async calculateAllStatic() {
if (this.isCalculating) return
this.isCalculating = true
try {
const attrs = this.sessionData.attributes || {}
const basic = this.sessionData.basic_info || {}
const token = localStorage.getItem('token')
const response = await API.post('/api/characters/calculate-static-fields', {
st: attrs.st || 0,
gs: attrs.gs || 0,
gw: attrs.gw || 0,
ko: attrs.ko || 0,
in: attrs.in || 0,
zt: attrs.zt || 0,
au: attrs.au || 0,
rasse: basic.rasse || 'Menschen',
typ: basic.typ || 'Barbar'
}, {
headers: { Authorization: `Bearer ${token}` }
})
const staticValues = response.data
// Update form data with calculated static values
this.formData = {
...this.formData,
ausdauer_bonus: staticValues.ausdauer_bonus,
schadens_bonus: staticValues.schadens_bonus,
angriffs_bonus: staticValues.angriffs_bonus,
abwehr_bonus: staticValues.abwehr_bonus,
zauber_bonus: staticValues.zauber_bonus,
resistenz_bonus_koerper: staticValues.resistenz_bonus_koerper,
resistenz_bonus_geist: staticValues.resistenz_bonus_geist,
resistenz_koerper: staticValues.resistenz_koerper,
resistenz_geist: staticValues.resistenz_geist,
abwehr: staticValues.abwehr,
zaubern: staticValues.zaubern,
raufen: staticValues.raufen
}
// Save the updated values to session
this.$emit('save', { derived_values: this.formData })
} catch (error) {
console.error('Error calculating static values:', error)
} finally {
this.isCalculating = false
}
},
async rollField(fieldName) {
if (this.isCalculating) return
this.isCalculating = true
try {
const attrs = this.sessionData.attributes || {}
const basic = this.sessionData.basic_info || {}
const token = localStorage.getItem('token')
// Generate dice roll based on field type
let roll
switch (fieldName) {
case 'pa':
case 'wk':
roll = rollDie(100) // 1d100
break
case 'lp_max':
roll = rollDie(3) // 1d3 - single number
break
case 'ap_max':
roll = rollDie(3) // 1d3 - array of 3 values
break
case 'b_max':
// B Max depends on race: Gnome/Halblinge=2d3, Zwerge=3d3, others=4d3
let diceCount = 4 // default for most races
if (basic.rasse === 'Gnome' || basic.rasse === 'Halblinge') {
diceCount = 2
} else if (basic.rasse === 'Zwerge') {
diceCount = 3
}
roll = rollDice(diceCount, 3) // XdY where X depends on race, Y=3
break
}
const response = await API.post('/api/characters/calculate-rolled-field', {
st: attrs.st || 0,
gs: attrs.gs || 0,
gw: attrs.gw || 0,
ko: attrs.ko || 0,
in: attrs.in || 0,
zt: attrs.zt || 0,
au: attrs.au || 0,
rasse: basic.rasse || 'Menschen',
typ: basic.typ || 'Barbar',
field: fieldName,
roll: roll
}, {
headers: { Authorization: `Bearer ${token}` }
})
const result = response.data
this.formData[fieldName] = result.value
// Save the updated values to session
this.$emit('save', { derived_values: this.formData })
} catch (error) {
console.error('Error calculating rolled field:', error)
} finally {
this.isCalculating = false
}
},
getDiceTooltip(fieldName) {
switch (fieldName) {
case 'pa':
return this.$t('characters.derivedValues.paRollTooltip')
case 'wk':
return this.$t('characters.derivedValues.wkRollTooltip')
case 'lp_max':
return this.$t('characters.derivedValues.lpRollTooltip')
case 'ap_max':
return this.$t('characters.derivedValues.apRollTooltip')
case 'b_max':
return this.$t('characters.derivedValues.bRollTooltip')
default:
return ''
}
},
// Legacy methods for backward compatibility
calculateLP(constitution) {
// LP = 1d3 + 7 + (Ko/10)
const diceRoll = Math.floor(Math.random() * 3) + 1 // 1d3
@@ -323,33 +442,30 @@ export default {
},
rollLP() {
const attrs = this.sessionData.attributes || {}
this.formData.lp_max = this.calculateLP(attrs.ko || 50)
this.rollField('lp_max')
},
rollPA() {
const attrs = this.sessionData.attributes || {}
this.formData.pa = this.calculatePA(attrs.in || 50)
this.rollField('pa')
},
rollWK() {
const attrs = this.sessionData.attributes || {}
this.formData.wk = this.calculateWK(attrs.ko || 50, attrs.in || 50)
this.rollField('wk')
},
getClassBonnie(type) {
// TODO: Implement class-specific bonnie calculations
// For now, return base values
const bonnieMap = {
'sg': 1,
'gg': 1,
'gp': 1,
'sg': 0,
'gg': 0,
'gp': 0,
}
return bonnieMap[type] || 1
},
recalculate() {
this.formData = { ...this.calculatedValues }
this.calculateAllStatic()
},
handlePrevious() {
+10 -4
View File
@@ -294,10 +294,10 @@ export default {
wkDescription: 'Mentale Widerstandsfähigkeit (Berechnung: 1d100 + 2×(Ko/10 + In/10) - 20)',
lpMax: 'Lebenspunkte (LP) Maximum',
lpMaxDescription: 'Maximale Lebens-/Gesundheitspunkte (Berechnung: 1d3 + 7 + Ko/10)',
apMax: 'Abenteuerpunkte (AP) Maximum',
apMax: 'Ausdauerpunkte (AP) Maximum',
apMaxDescription: 'Maximale Abenteuerpunkte für spezielle Aktionen',
bMax: 'Belastung (B) Maximum',
bMaxDescription: 'Maximale Tragekapazität',
bMax: 'Bewegungsweite (B)',
bMaxDescription: 'Maximale Bewegungsweite (Berechnung: 1d6 + Modifikator je nach Rasse)',
resistenzKoerper: 'Resistenz Körper',
resistenzKoerperDescription: 'Körperliche Widerstandsfähigkeit (= Konstitution)',
resistenzGeist: 'Resistenz Geist',
@@ -340,7 +340,13 @@ export default {
benniesDescription: 'Grundwerte basierend auf Charakterklasse',
previousAttributes: 'Zurück: Attribute',
recalculate: 'Aus Attributen neu berechnen',
nextSkills: 'Weiter: Fertigkeiten & Zauber'
calculating: 'Berechne...',
nextSkills: 'Weiter: Fertigkeiten & Zauber',
paRollTooltip: 'PA würfeln: 1d100 + 4×(In/10) - 20',
wkRollTooltip: 'WK würfeln: 1d100 + 2×(Ko/10 + In/10) - 20',
lpRollTooltip: 'LP würfeln: 1d3 + 7 + (Ko/10)',
apRollTooltip: 'AP würfeln: 3d6 + Modifikator je nach Klasse',
bRollTooltip: 'B würfeln: 1d6 + Modifikator je nach Rasse'
}
}
}
+7 -1
View File
@@ -246,7 +246,13 @@ export default {
benniesDescription: 'Base values based on character class',
previousAttributes: 'Previous: Attributes',
recalculate: 'Recalculate from Attributes',
nextSkills: 'Next: Skills & Spells'
calculating: 'Calculating...',
nextSkills: 'Next: Skills & Spells',
paRollTooltip: 'Roll PA: 1d100 + 4×(In/10) - 20',
wkRollTooltip: 'Roll WK: 1d100 + 2×(Ko/10 + In/10) - 20',
lpRollTooltip: 'Roll LP: 1d3 + 7 + (Ko/10)',
apRollTooltip: 'Roll AP: 3d6 + modifier based on class',
bRollTooltip: 'Roll B: 1d6 + modifier based on race'
}
},
common: {