added lern and skill improvement cost editing
This commit is contained in:
@@ -39,6 +39,7 @@ import GameSystemView from "./maintenance/GameSystemView.vue";
|
||||
import LitSourceView from "./maintenance/LitSourceView.vue";
|
||||
import MiscLookupView from "./maintenance/MiscLookupView.vue";
|
||||
import SkillImprovementCostView from "./maintenance/SkillImprovementCostView.vue";
|
||||
import LearningCostView from "./maintenance/LearningCostView.vue";
|
||||
|
||||
|
||||
export default {
|
||||
@@ -55,6 +56,7 @@ export default {
|
||||
LitSourceView,
|
||||
MiscLookupView,
|
||||
SkillImprovementCostView,
|
||||
LearningCostView,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -82,7 +84,7 @@ export default {
|
||||
{ id: 7, name: "litsource", component: "LitSourceView" },
|
||||
{ id: 8, name: "misc", component: "MiscLookupView" },
|
||||
{ id: 9, name: "skillimprovement", component: "SkillImprovementCostView" },
|
||||
|
||||
{ id: 10, name: "learningcost", component: "LearningCostView" },
|
||||
],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -0,0 +1,440 @@
|
||||
<template>
|
||||
<div class="header-section">
|
||||
<h2>{{ $t('maintenance') }} - {{ $t('learningcost.title') }}</h2>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error-box">{{ error }}</div>
|
||||
|
||||
<div v-if="isLoading" class="cd-view">
|
||||
<p>{{ $t('common.loading') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Section 1: EP per TE — Class × Skill Category matrix -->
|
||||
<div v-if="!isLoading" class="cd-view">
|
||||
<h3>{{ $t('learningcost.headerEPPerTE') }}</h3>
|
||||
<p class="lc-description">{{ $t('learningcost.epPerTEDesc') }}</p>
|
||||
<div class="cd-list">
|
||||
<table class="cd-table lc-matrix">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cd-table-header lc-sticky-col">{{ $t('learningcost.class') }}</th>
|
||||
<th v-for="cat in skillCategories" :key="cat.id" class="cd-table-header">
|
||||
{{ cat.name }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="cc in characterClasses" :key="cc.code">
|
||||
<td class="lc-sticky-col lc-row-header">{{ cc.code }}</td>
|
||||
<td
|
||||
v-for="cat in skillCategories"
|
||||
:key="cat.id"
|
||||
class="lc-cell"
|
||||
@click="startEditEPPerTE(cc.code, cat.name)"
|
||||
>
|
||||
<template v-if="editingCell === cellKey('te', cc.code, cat.name)">
|
||||
<input
|
||||
v-model.number="editValue"
|
||||
type="number"
|
||||
min="0"
|
||||
class="lc-input"
|
||||
@keyup.enter="saveEditEPPerTE(cc.code, cat.name)"
|
||||
@keyup.escape="cancelEdit"
|
||||
@blur="saveEditEPPerTE(cc.code, cat.name)"
|
||||
ref="cellInput"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ getEPPerTE(cc.code, cat.name) ?? '-' }}
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="lc-note">{{ $t('learningcost.goldCostTE') }}</p>
|
||||
<p class="lc-hint">{{ $t('learningcost.clickToEdit') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Section 2: EP per LE — Class × Spell School matrix -->
|
||||
<div v-if="!isLoading" class="cd-view">
|
||||
<h3>{{ $t('learningcost.headerEPPerLE') }}</h3>
|
||||
<p class="lc-description">{{ $t('learningcost.epPerLEDesc') }}</p>
|
||||
<div class="cd-list">
|
||||
<table class="cd-table lc-matrix">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cd-table-header lc-sticky-col">{{ $t('learningcost.class') }}</th>
|
||||
<th v-for="school in spellSchools" :key="school.id" class="cd-table-header">
|
||||
{{ school.name }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="cc in spellCasterClasses" :key="cc.code">
|
||||
<td class="lc-sticky-col lc-row-header">{{ cc.code }}</td>
|
||||
<td
|
||||
v-for="school in spellSchools"
|
||||
:key="school.id"
|
||||
class="lc-cell"
|
||||
@click="startEditEPPerLE(cc.code, school.name)"
|
||||
>
|
||||
<template v-if="editingCell === cellKey('le', cc.code, school.name)">
|
||||
<input
|
||||
v-model.number="editValue"
|
||||
type="number"
|
||||
min="0"
|
||||
class="lc-input"
|
||||
@keyup.enter="saveEditEPPerLE(cc.code, school.name)"
|
||||
@keyup.escape="cancelEdit"
|
||||
@blur="saveEditEPPerLE(cc.code, school.name)"
|
||||
ref="cellInput"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ getEPPerLE(cc.code, school.name) ?? '-' }}
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="lc-note">{{ $t('learningcost.goldCostLE') }}</p>
|
||||
<p class="lc-note">{{ $t('learningcost.maSpecialNote') }}</p>
|
||||
<p class="lc-note">{{ $t('learningcost.scrollLearnNote') }}</p>
|
||||
<p class="lc-hint">{{ $t('learningcost.clickToEdit') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Section 3: LE per Spell Level -->
|
||||
<div v-if="!isLoading" class="cd-view">
|
||||
<h3>{{ $t('learningcost.headerSpellLevelLE') }}</h3>
|
||||
<p class="lc-description">{{ $t('learningcost.spellLevelLEDesc') }}</p>
|
||||
<div class="cd-list">
|
||||
<table class="cd-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cd-table-header">{{ $t('learningcost.spellLevel') }}</th>
|
||||
<th class="cd-table-header">{{ $t('learningcost.leRequired') }}</th>
|
||||
<th class="cd-table-header"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="cost in spellLevelCosts" :key="cost.id">
|
||||
<tr v-if="editingSpellLevelId !== cost.id">
|
||||
<td>{{ cost.level }}</td>
|
||||
<td>{{ cost.le_required }}</td>
|
||||
<td><button @click="startEditSpellLevel(cost)">{{ $t('common.edit') }}</button></td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>{{ cost.level }}</td>
|
||||
<td>
|
||||
<input
|
||||
v-model.number="editSpellLevelValue"
|
||||
type="number"
|
||||
min="0"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div class="edit-actions">
|
||||
<button class="btn-primary btn-save" :disabled="isSaving" @click="saveEditSpellLevel">
|
||||
<span v-if="!isSaving">{{ $t('common.save') }}</span>
|
||||
<span v-else>{{ $t('common.saving') }}</span>
|
||||
</button>
|
||||
<button class="btn-cancel" :disabled="isSaving" @click="cancelEditSpellLevel">
|
||||
{{ $t('common.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.lc-description {
|
||||
margin: 8px 0;
|
||||
font-style: italic;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.lc-note {
|
||||
margin: 4px 0;
|
||||
font-size: 0.9em;
|
||||
font-style: italic;
|
||||
opacity: 0.75;
|
||||
}
|
||||
.lc-hint {
|
||||
margin-top: 6px;
|
||||
font-size: 0.85em;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.lc-matrix {
|
||||
min-width: max-content;
|
||||
}
|
||||
.lc-matrix th,
|
||||
.lc-matrix td {
|
||||
text-align: center;
|
||||
min-width: 60px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.lc-sticky-col {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
background: var(--color-background);
|
||||
z-index: 1;
|
||||
}
|
||||
.lc-row-header {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
padding-right: 12px;
|
||||
}
|
||||
.lc-cell {
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
.lc-cell:hover {
|
||||
background: var(--color-background-mute);
|
||||
}
|
||||
.lc-input {
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
padding: 2px 4px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.error-box {
|
||||
margin: 10px 0;
|
||||
padding: 10px 12px;
|
||||
background: #ffe3e3;
|
||||
color: #8a1c1c;
|
||||
border: 1px solid #f5c2c2;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.edit-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import API from '../../utils/api'
|
||||
|
||||
export default {
|
||||
name: 'LearningCostView',
|
||||
data() {
|
||||
return {
|
||||
characterClasses: [],
|
||||
skillCategories: [],
|
||||
spellSchools: [],
|
||||
epPerTECosts: [],
|
||||
epPerLECosts: [],
|
||||
spellLevelCosts: [],
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
error: '',
|
||||
// Matrix cell editing
|
||||
editingCell: null,
|
||||
editValue: 0,
|
||||
// Spell level editing
|
||||
editingSpellLevelId: null,
|
||||
editSpellLevelValue: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
spellCasterClasses() {
|
||||
const codesWithSpells = new Set(this.epPerLECosts.map(c => c.characterClass || c.character_class?.code))
|
||||
return this.characterClasses.filter(cc => codesWithSpells.has(cc.code))
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
await this.loadAll()
|
||||
},
|
||||
methods: {
|
||||
cellKey(type, classCode, colName) {
|
||||
return `${type}:${classCode}:${colName}`
|
||||
},
|
||||
|
||||
// --- Data loading ---
|
||||
|
||||
async loadAll() {
|
||||
this.isLoading = true
|
||||
this.error = ''
|
||||
try {
|
||||
const [classesResp, categoriesResp, schoolsResp, epTEResp, epLEResp, spellLevelResp] = await Promise.all([
|
||||
API.get('/api/maintenance/character-classes'),
|
||||
API.get('/api/maintenance/skill-categories'),
|
||||
API.get('/api/maintenance/spell-schools'),
|
||||
API.get('/api/maintenance/class-category-ep-costs'),
|
||||
API.get('/api/maintenance/class-spell-school-ep-costs'),
|
||||
API.get('/api/maintenance/spell-level-le-costs'),
|
||||
])
|
||||
this.characterClasses = classesResp.data?.character_classes || []
|
||||
this.skillCategories = categoriesResp.data?.skill_categories || []
|
||||
this.spellSchools = schoolsResp.data?.spell_schools || []
|
||||
this.epPerTECosts = epTEResp.data?.costs || []
|
||||
this.epPerLECosts = epLEResp.data?.costs || []
|
||||
this.spellLevelCosts = spellLevelResp.data?.costs || []
|
||||
} catch (err) {
|
||||
console.error('Failed to load learning costs:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
// --- EP per TE matrix ---
|
||||
|
||||
getEPPerTE(classCode, categoryName) {
|
||||
const entry = this.epPerTECosts.find(
|
||||
c => (c.characterClass === classCode || c.character_class?.code === classCode) &&
|
||||
(c.skillCategory === categoryName || c.skill_category?.name === categoryName)
|
||||
)
|
||||
return entry?.ep_per_te
|
||||
},
|
||||
|
||||
findEPPerTEEntry(classCode, categoryName) {
|
||||
return this.epPerTECosts.find(
|
||||
c => (c.characterClass === classCode || c.character_class?.code === classCode) &&
|
||||
(c.skillCategory === categoryName || c.skill_category?.name === categoryName)
|
||||
)
|
||||
},
|
||||
|
||||
startEditEPPerTE(classCode, categoryName) {
|
||||
const entry = this.findEPPerTEEntry(classCode, categoryName)
|
||||
if (!entry) return
|
||||
this.cancelEdit()
|
||||
this.editingCell = this.cellKey('te', classCode, categoryName)
|
||||
this.editValue = entry.ep_per_te
|
||||
this.$nextTick(() => {
|
||||
const inputs = this.$refs.cellInput
|
||||
if (inputs) {
|
||||
const el = Array.isArray(inputs) ? inputs[0] : inputs
|
||||
el?.focus()
|
||||
el?.select()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async saveEditEPPerTE(classCode, categoryName) {
|
||||
const entry = this.findEPPerTEEntry(classCode, categoryName)
|
||||
if (!entry || this.editingCell !== this.cellKey('te', classCode, categoryName)) return
|
||||
if (entry.ep_per_te === this.editValue) {
|
||||
this.cancelEdit()
|
||||
return
|
||||
}
|
||||
this.isSaving = true
|
||||
try {
|
||||
const resp = await API.put(`/api/maintenance/class-category-ep-costs/${entry.id}`, {
|
||||
ep_per_te: this.editValue,
|
||||
})
|
||||
const idx = this.epPerTECosts.findIndex(c => c.id === entry.id)
|
||||
if (idx !== -1) this.epPerTECosts.splice(idx, 1, resp.data)
|
||||
this.cancelEdit()
|
||||
} catch (err) {
|
||||
console.error('Failed to save EP/TE cost:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isSaving = false
|
||||
}
|
||||
},
|
||||
|
||||
// --- EP per LE matrix ---
|
||||
|
||||
getEPPerLE(classCode, schoolName) {
|
||||
const entry = this.epPerLECosts.find(
|
||||
c => (c.characterClass === classCode || c.character_class?.code === classCode) &&
|
||||
(c.spellSchool === schoolName || c.spell_school?.name === schoolName)
|
||||
)
|
||||
return entry?.ep_per_le
|
||||
},
|
||||
|
||||
findEPPerLEEntry(classCode, schoolName) {
|
||||
return this.epPerLECosts.find(
|
||||
c => (c.characterClass === classCode || c.character_class?.code === classCode) &&
|
||||
(c.spellSchool === schoolName || c.spell_school?.name === schoolName)
|
||||
)
|
||||
},
|
||||
|
||||
startEditEPPerLE(classCode, schoolName) {
|
||||
const entry = this.findEPPerLEEntry(classCode, schoolName)
|
||||
if (!entry) return
|
||||
this.cancelEdit()
|
||||
this.editingCell = this.cellKey('le', classCode, schoolName)
|
||||
this.editValue = entry.ep_per_le
|
||||
this.$nextTick(() => {
|
||||
const inputs = this.$refs.cellInput
|
||||
if (inputs) {
|
||||
const el = Array.isArray(inputs) ? inputs[0] : inputs
|
||||
el?.focus()
|
||||
el?.select()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async saveEditEPPerLE(classCode, schoolName) {
|
||||
const entry = this.findEPPerLEEntry(classCode, schoolName)
|
||||
if (!entry || this.editingCell !== this.cellKey('le', classCode, schoolName)) return
|
||||
if (entry.ep_per_le === this.editValue) {
|
||||
this.cancelEdit()
|
||||
return
|
||||
}
|
||||
this.isSaving = true
|
||||
try {
|
||||
const resp = await API.put(`/api/maintenance/class-spell-school-ep-costs/${entry.id}`, {
|
||||
ep_per_le: this.editValue,
|
||||
})
|
||||
const idx = this.epPerLECosts.findIndex(c => c.id === entry.id)
|
||||
if (idx !== -1) this.epPerLECosts.splice(idx, 1, resp.data)
|
||||
this.cancelEdit()
|
||||
} catch (err) {
|
||||
console.error('Failed to save EP/LE cost:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isSaving = false
|
||||
}
|
||||
},
|
||||
|
||||
// --- Spell Level LE costs ---
|
||||
|
||||
startEditSpellLevel(cost) {
|
||||
this.cancelEdit()
|
||||
this.editingSpellLevelId = cost.id
|
||||
this.editSpellLevelValue = cost.le_required
|
||||
},
|
||||
|
||||
cancelEditSpellLevel() {
|
||||
this.editingSpellLevelId = null
|
||||
this.editSpellLevelValue = 0
|
||||
},
|
||||
|
||||
async saveEditSpellLevel() {
|
||||
if (this.editingSpellLevelId == null) return
|
||||
this.isSaving = true
|
||||
try {
|
||||
const resp = await API.put(`/api/maintenance/spell-level-le-costs/${this.editingSpellLevelId}`, {
|
||||
le_required: this.editSpellLevelValue,
|
||||
})
|
||||
const idx = this.spellLevelCosts.findIndex(c => c.id === this.editingSpellLevelId)
|
||||
if (idx !== -1) this.spellLevelCosts.splice(idx, 1, resp.data)
|
||||
this.cancelEditSpellLevel()
|
||||
} catch (err) {
|
||||
console.error('Failed to save spell level LE cost:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isSaving = false
|
||||
}
|
||||
},
|
||||
|
||||
// --- Shared ---
|
||||
|
||||
cancelEdit() {
|
||||
this.editingCell = null
|
||||
this.editValue = 0
|
||||
this.editingSpellLevelId = null
|
||||
this.editSpellLevelValue = 0
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,117 +1,200 @@
|
||||
<template>
|
||||
<div class="header-section">
|
||||
<h2>{{ $t('maintenance') }} - {{ $t('skillimprovement.title') }}</h2>
|
||||
<button class="btn-primary" @click="startCreate">{{ $t('newEntry') }}</button>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error-box">{{ error }}</div>
|
||||
|
||||
<div class="cd-view">
|
||||
<div class="cd-list">
|
||||
<table class="cd-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cd-table-header">{{ $t('skillimprovement.id') }}</th>
|
||||
<th class="cd-table-header">{{ $t('skillimprovement.level') }}</th>
|
||||
<th class="cd-table-header">{{ $t('skillimprovement.te') }}</th>
|
||||
<th class="cd-table-header">{{ $t('skillimprovement.category') }}</th>
|
||||
<th class="cd-table-header">{{ $t('skillimprovement.difficulty') }}</th>
|
||||
<th class="cd-table-header"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="isLoading">
|
||||
<td colspan="6">{{ $t('common.loading') }}</td>
|
||||
</tr>
|
||||
<tr v-if="creatingNew">
|
||||
<td>New</td>
|
||||
<td colspan="5">
|
||||
<div class="edit-form">
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('skillimprovement.level') }}</label>
|
||||
<input v-model.number="newItem.current_level" type="number" />
|
||||
<label class="inline-label">{{ $t('skillimprovement.te') }}</label>
|
||||
<input v-model.number="newItem.te_required" type="number" />
|
||||
</div>
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('skillimprovement.category') }}</label>
|
||||
<select v-model.number="newItem.category_id">
|
||||
<option v-for="cat in categoryOptions" :key="cat.id" :value="cat.id">
|
||||
{{ cat.label }}
|
||||
</option>
|
||||
</select>
|
||||
<label class="inline-label">{{ $t('skillimprovement.difficulty') }}</label>
|
||||
<select v-model.number="newItem.difficulty_id">
|
||||
<option v-for="diff in difficultyOptions" :key="diff.id" :value="diff.id">
|
||||
{{ diff.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="edit-actions">
|
||||
<button class="btn-primary btn-save" :disabled="isSaving" @click="saveCreate">
|
||||
<span v-if="!isSaving">{{ $t('common.save') }}</span>
|
||||
<span v-else>{{ $t('common.saving') }}</span>
|
||||
</button>
|
||||
<button class="btn-cancel" :disabled="isSaving" @click="cancelCreate">
|
||||
{{ $t('common.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<template v-for="cost in costs" :key="cost.id">
|
||||
<tr v-if="editingId !== cost.id">
|
||||
<td>{{ cost.id }}</td>
|
||||
<td>{{ cost.current_level }}</td>
|
||||
<td>{{ cost.te_required }}</td>
|
||||
<td>{{ displayCategory(cost) }}</td>
|
||||
<td>{{ displayDifficulty(cost) }}</td>
|
||||
<td><button @click="startEdit(cost)">{{ $t('common.edit') }}</button></td>
|
||||
<p class="si-description">{{ $t('skillimprovement.description') }}</p>
|
||||
|
||||
<div class="si-category-tabs">
|
||||
<button
|
||||
v-for="cat in availableCategories"
|
||||
:key="cat.id"
|
||||
:class="{ active: selectedCategoryId === cat.id }"
|
||||
@click="selectedCategoryId = cat.id"
|
||||
>
|
||||
{{ cat.name }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="isLoading" class="cd-view">
|
||||
<p>{{ $t('common.loading') }}</p>
|
||||
</div>
|
||||
|
||||
<template v-if="!isLoading && selectedCategoryId">
|
||||
<!-- Lernen section -->
|
||||
<div class="cd-view">
|
||||
<h3>{{ $t('skillimprovement.lernen') }}</h3>
|
||||
<div class="cd-list">
|
||||
<table class="cd-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cd-table-header">{{ $t('skillimprovement.difficulty') }}</th>
|
||||
<th class="cd-table-header">{{ $t('skillimprovement.le') }}</th>
|
||||
<th class="cd-table-header">{{ $t('skillimprovement.skills') }}</th>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>{{ cost.id }}</td>
|
||||
<td colspan="5">
|
||||
<div class="edit-form">
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('skillimprovement.level') }}</label>
|
||||
<input v-model.number="editedItem.current_level" type="number" />
|
||||
<label class="inline-label">{{ $t('skillimprovement.te') }}</label>
|
||||
<input v-model.number="editedItem.te_required" type="number" />
|
||||
</div>
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('skillimprovement.category') }}</label>
|
||||
<select v-model.number="editedItem.category_id">
|
||||
<option v-for="cat in categoryOptions" :key="cat.id" :value="cat.id">
|
||||
{{ cat.label }}
|
||||
</option>
|
||||
</select>
|
||||
<label class="inline-label">{{ $t('skillimprovement.difficulty') }}</label>
|
||||
<select v-model.number="editedItem.difficulty_id">
|
||||
<option v-for="diff in difficultyOptions" :key="diff.id" :value="diff.id">
|
||||
{{ diff.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="edit-actions">
|
||||
<button class="btn-primary btn-save" :disabled="isSaving" @click="saveEdit">
|
||||
<span v-if="!isSaving">{{ $t('common.save') }}</span>
|
||||
<span v-else>{{ $t('common.saving') }}</span>
|
||||
</button>
|
||||
<button class="btn-cancel" :disabled="isSaving" @click="cancelEdit">
|
||||
{{ $t('common.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in lernenRows" :key="row.key">
|
||||
<td>{{ row.difficultyName }}</td>
|
||||
<td
|
||||
class="si-cell"
|
||||
@click="startEditLernen(row)"
|
||||
>
|
||||
<template v-if="editingLernenKey === row.key">
|
||||
<input
|
||||
v-model.number="editLernenValue"
|
||||
type="number"
|
||||
min="0"
|
||||
class="si-input"
|
||||
@keyup.enter="saveLernenEdit(row)"
|
||||
@keyup.escape="cancelLernenEdit"
|
||||
@blur="saveLernenEdit(row)"
|
||||
ref="lernenInput"
|
||||
/>
|
||||
<span class="si-le-unit">LE</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ row.learnCost }} LE
|
||||
</template>
|
||||
</td>
|
||||
<td>{{ row.skillsDisplay }}</td>
|
||||
</tr>
|
||||
<tr v-if="lernenRows.length === 0">
|
||||
<td colspan="3" class="si-empty">{{ $t('skillimprovement.noLernenData') }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="si-hint">{{ $t('skillimprovement.clickLeToEdit') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Verbessern (TE) section -->
|
||||
<div class="cd-view">
|
||||
<h3>{{ $t('skillimprovement.verbessern') }}</h3>
|
||||
<div class="cd-list">
|
||||
<table class="cd-table si-matrix">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cd-table-header si-sticky-col">{{ $t('skillimprovement.difficulty') }}</th>
|
||||
<th v-for="level in matrixLevels" :key="level" class="cd-table-header">
|
||||
+{{ level }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="diff in matrixDifficulties" :key="diff.id">
|
||||
<td class="si-sticky-col si-row-header">{{ diff.name }}</td>
|
||||
<td
|
||||
v-for="level in matrixLevels"
|
||||
:key="level"
|
||||
class="si-cell"
|
||||
@click="startEditTE(diff.id, level)"
|
||||
>
|
||||
<template v-if="editingCell === cellKey(diff.id, level)">
|
||||
<input
|
||||
v-model.number="editValue"
|
||||
type="number"
|
||||
min="0"
|
||||
class="si-input"
|
||||
@keyup.enter="saveEditTE(diff.id, level)"
|
||||
@keyup.escape="cancelEdit"
|
||||
@blur="saveEditTE(diff.id, level)"
|
||||
ref="cellInput"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ displayTE(diff.id, level) }}
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
<tr v-if="matrixDifficulties.length === 0">
|
||||
<td :colspan="matrixLevels.length + 1" class="si-empty">{{ $t('skillimprovement.noVerbessernData') }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="si-hint">{{ $t('skillimprovement.clickToEdit') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.si-description {
|
||||
margin: 8px 0 4px;
|
||||
font-style: italic;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.si-category-tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin: 10px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.si-category-tabs button {
|
||||
padding: 6px 14px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
background: var(--color-background-soft);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.si-category-tabs button:hover {
|
||||
background: var(--color-background-mute);
|
||||
}
|
||||
.si-category-tabs button.active {
|
||||
background: var(--color-background-mute);
|
||||
font-weight: bold;
|
||||
border-color: var(--color-text);
|
||||
}
|
||||
.si-matrix {
|
||||
min-width: max-content;
|
||||
}
|
||||
.si-matrix th,
|
||||
.si-matrix td {
|
||||
text-align: center;
|
||||
min-width: 50px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.si-sticky-col {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
background: var(--color-background);
|
||||
z-index: 1;
|
||||
}
|
||||
.si-row-header {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
padding-right: 12px;
|
||||
}
|
||||
.si-cell {
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
.si-cell:hover {
|
||||
background: var(--color-background-mute);
|
||||
}
|
||||
.si-input {
|
||||
width: 55px;
|
||||
text-align: center;
|
||||
padding: 2px 4px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.si-hint {
|
||||
margin-top: 6px;
|
||||
font-size: 0.85em;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.si-le-unit {
|
||||
margin-left: 2px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.si-empty {
|
||||
font-style: italic;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.error-box {
|
||||
margin: 10px 0;
|
||||
padding: 10px 12px;
|
||||
@@ -120,24 +203,6 @@
|
||||
border: 1px solid #f5c2c2;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.edit-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.edit-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
.edit-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.inline-label {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -147,127 +212,226 @@ export default {
|
||||
name: 'SkillImprovementCostView',
|
||||
data() {
|
||||
return {
|
||||
costs: [],
|
||||
editingId: null,
|
||||
editedItem: null,
|
||||
creatingNew: false,
|
||||
newItem: null,
|
||||
categories: [],
|
||||
difficulties: [],
|
||||
skillCatDiffs: [],
|
||||
improvementCosts: [],
|
||||
selectedCategoryId: null,
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
error: '',
|
||||
editingCell: null,
|
||||
editValue: 0,
|
||||
editingLernenKey: null,
|
||||
editLernenValue: 0,
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadCosts()
|
||||
},
|
||||
computed: {
|
||||
categoryOptions() {
|
||||
const seen = new Map()
|
||||
this.costs.forEach(c => {
|
||||
const id = c.category_id ?? c.skillCategoryId
|
||||
const name = c.category_name || c.skillCategoryName
|
||||
if (id != null && !seen.has(id)) {
|
||||
seen.set(id, name ? `${name} (${id})` : `${id}`)
|
||||
}
|
||||
})
|
||||
return Array.from(seen.entries()).map(([id, label]) => ({ id, label }))
|
||||
availableCategories() {
|
||||
const catIdsWithSKD = new Set(this.skillCatDiffs.map(d => d.skill_category?.id))
|
||||
const catIdsWithIC = new Set(this.improvementCosts.map(c => c.skillCategoryId))
|
||||
return this.categories.filter(cat =>
|
||||
catIdsWithSKD.has(cat.id) || catIdsWithIC.has(cat.id)
|
||||
)
|
||||
},
|
||||
difficultyOptions() {
|
||||
const seen = new Map()
|
||||
this.costs.forEach(c => {
|
||||
const id = c.difficulty_id ?? c.skillDifficultyId
|
||||
const name = c.difficulty_name || c.skillDifficultyName
|
||||
if (id != null && !seen.has(id)) {
|
||||
seen.set(id, name ? `${name} (${id})` : `${id}`)
|
||||
|
||||
lernenRows() {
|
||||
if (!this.selectedCategoryId) return []
|
||||
|
||||
const items = this.skillCatDiffs.filter(d =>
|
||||
d.skill_category?.id === this.selectedCategoryId
|
||||
)
|
||||
|
||||
const groups = new Map()
|
||||
for (const item of items) {
|
||||
const diffName = item.skill_difficulty?.name || item.skillDifficulty || ''
|
||||
const diffId = item.skill_difficulty?.id || 0
|
||||
const lc = item.learn_cost
|
||||
const key = `${diffId}:${lc}`
|
||||
|
||||
if (!groups.has(key)) {
|
||||
groups.set(key, {
|
||||
key,
|
||||
difficultyId: diffId,
|
||||
difficultyName: diffName,
|
||||
learnCost: lc,
|
||||
itemIds: [],
|
||||
skills: [],
|
||||
})
|
||||
}
|
||||
})
|
||||
return Array.from(seen.entries()).map(([id, label]) => ({ id, label }))
|
||||
|
||||
groups.get(key).itemIds.push(item.id)
|
||||
const skill = item.skill
|
||||
if (skill) {
|
||||
const be = skill.bonuseigenschaft || '-'
|
||||
groups.get(key).skills.push(`${skill.name}+${skill.initialwert} (${be})`)
|
||||
}
|
||||
}
|
||||
|
||||
const rows = Array.from(groups.values())
|
||||
rows.sort((a, b) => a.difficultyId - b.difficultyId || a.learnCost - b.learnCost)
|
||||
|
||||
for (const row of rows) {
|
||||
row.skills.sort()
|
||||
row.skillsDisplay = row.skills.join(', ')
|
||||
}
|
||||
return rows
|
||||
},
|
||||
|
||||
categoryImprovementCosts() {
|
||||
if (!this.selectedCategoryId) return []
|
||||
return this.improvementCosts.filter(c => c.skillCategoryId === this.selectedCategoryId)
|
||||
},
|
||||
|
||||
matrixLevels() {
|
||||
const levels = [...new Set(this.categoryImprovementCosts.map(c => c.current_level))]
|
||||
levels.sort((a, b) => a - b)
|
||||
return levels
|
||||
},
|
||||
|
||||
matrixDifficulties() {
|
||||
const seen = new Map()
|
||||
for (const c of this.categoryImprovementCosts) {
|
||||
const id = c.skillDifficultyId
|
||||
if (!seen.has(id)) {
|
||||
const name = c.difficulty_name || this.difficulties.find(d => d.id === id)?.name || `${id}`
|
||||
seen.set(id, { id, name })
|
||||
}
|
||||
}
|
||||
return Array.from(seen.values()).sort((a, b) => a.id - b.id)
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
await this.loadAll()
|
||||
},
|
||||
methods: {
|
||||
displayCategory(cost) {
|
||||
return cost.category_name || cost.skillCategoryName || cost.skillCategoryId || cost.category_id
|
||||
cellKey(diffId, level) {
|
||||
return `${diffId}:${level}`
|
||||
},
|
||||
displayDifficulty(cost) {
|
||||
return cost.difficulty_name || cost.skillDifficultyName || cost.skillDifficultyId || cost.difficulty_id
|
||||
},
|
||||
async loadCosts() {
|
||||
|
||||
async loadAll() {
|
||||
this.isLoading = true
|
||||
this.error = ''
|
||||
try {
|
||||
const resp = await API.get('/api/maintenance/skill-improvement-cost2')
|
||||
this.costs = resp.data?.costs || []
|
||||
const [catResp, diffResp, scdResp, icResp] = await Promise.all([
|
||||
API.get('/api/maintenance/skill-categories'),
|
||||
API.get('/api/maintenance/skill-difficulties'),
|
||||
API.get('/api/maintenance/skill-category-difficulties'),
|
||||
API.get('/api/maintenance/skill-improvement-cost2'),
|
||||
])
|
||||
this.categories = catResp.data?.skill_categories || []
|
||||
this.difficulties = diffResp.data?.skill_difficulties || []
|
||||
this.skillCatDiffs = scdResp.data?.items || []
|
||||
this.improvementCosts = icResp.data?.costs || []
|
||||
|
||||
if (this.availableCategories.length > 0 && !this.selectedCategoryId) {
|
||||
this.selectedCategoryId = this.availableCategories[0].id
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load costs:', err)
|
||||
console.error('Failed to load skill improvement data:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
startEdit(cost) {
|
||||
this.editingId = cost.id
|
||||
this.editedItem = {
|
||||
...cost,
|
||||
category_id: cost.category_id ?? cost.skillCategoryId,
|
||||
difficulty_id: cost.difficulty_id ?? cost.skillDifficultyId,
|
||||
}
|
||||
|
||||
findImprovementCost(diffId, level) {
|
||||
return this.categoryImprovementCosts.find(
|
||||
c => c.skillDifficultyId === diffId && c.current_level === level
|
||||
)
|
||||
},
|
||||
cancelEdit() {
|
||||
this.editingId = null
|
||||
this.editedItem = null
|
||||
|
||||
displayTE(diffId, level) {
|
||||
const entry = this.findImprovementCost(diffId, level)
|
||||
if (!entry) return '-'
|
||||
return entry.te_required === 0 ? '-' : entry.te_required
|
||||
},
|
||||
startCreate() {
|
||||
|
||||
startEditTE(diffId, level) {
|
||||
const entry = this.findImprovementCost(diffId, level)
|
||||
if (!entry) return
|
||||
this.cancelEdit()
|
||||
const defaultCategory = this.categoryOptions[0]?.id ?? null
|
||||
const defaultDifficulty = this.difficultyOptions[0]?.id ?? null
|
||||
this.newItem = {
|
||||
current_level: 0,
|
||||
te_required: 0,
|
||||
category_id: defaultCategory,
|
||||
difficulty_id: defaultDifficulty,
|
||||
}
|
||||
this.creatingNew = true
|
||||
this.editingCell = this.cellKey(diffId, level)
|
||||
this.editValue = entry.te_required
|
||||
this.$nextTick(() => {
|
||||
const inputs = this.$refs.cellInput
|
||||
if (inputs) {
|
||||
const el = Array.isArray(inputs) ? inputs[0] : inputs
|
||||
el?.focus()
|
||||
el?.select()
|
||||
}
|
||||
})
|
||||
},
|
||||
cancelCreate() {
|
||||
this.creatingNew = false
|
||||
this.newItem = null
|
||||
},
|
||||
async saveEdit() {
|
||||
if (!this.editedItem) return
|
||||
const payload = {
|
||||
current_level: this.editedItem.current_level,
|
||||
te_required: this.editedItem.te_required,
|
||||
category_id: this.editedItem.category_id,
|
||||
difficulty_id: this.editedItem.difficulty_id,
|
||||
|
||||
async saveEditTE(diffId, level) {
|
||||
const entry = this.findImprovementCost(diffId, level)
|
||||
if (!entry || this.editingCell !== this.cellKey(diffId, level)) return
|
||||
if (entry.te_required === this.editValue) {
|
||||
this.cancelEdit()
|
||||
return
|
||||
}
|
||||
this.isSaving = true
|
||||
try {
|
||||
const resp = await API.put(`/api/maintenance/skill-improvement-cost2/${this.editingId}`, payload)
|
||||
const idx = this.costs.findIndex(c => c.id === this.editingId)
|
||||
if (idx !== -1) this.costs.splice(idx, 1, resp.data)
|
||||
const resp = await API.put(`/api/maintenance/skill-improvement-cost2/${entry.id}`, {
|
||||
current_level: entry.current_level,
|
||||
te_required: this.editValue,
|
||||
category_id: entry.skillCategoryId,
|
||||
difficulty_id: entry.skillDifficultyId,
|
||||
})
|
||||
const idx = this.improvementCosts.findIndex(c => c.id === entry.id)
|
||||
if (idx !== -1) this.improvementCosts.splice(idx, 1, resp.data)
|
||||
this.cancelEdit()
|
||||
} catch (err) {
|
||||
console.error('Failed to save cost:', err)
|
||||
console.error('Failed to save TE cost:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isSaving = false
|
||||
}
|
||||
},
|
||||
async saveCreate() {
|
||||
if (!this.newItem) return
|
||||
const payload = {
|
||||
current_level: this.newItem.current_level,
|
||||
te_required: this.newItem.te_required,
|
||||
category_id: this.newItem.category_id,
|
||||
difficulty_id: this.newItem.difficulty_id,
|
||||
|
||||
cancelEdit() {
|
||||
this.editingCell = null
|
||||
this.editValue = 0
|
||||
},
|
||||
|
||||
startEditLernen(row) {
|
||||
this.cancelEdit()
|
||||
this.editingLernenKey = row.key
|
||||
this.editLernenValue = row.learnCost
|
||||
this.$nextTick(() => {
|
||||
const inputs = this.$refs.lernenInput
|
||||
if (inputs) {
|
||||
const el = Array.isArray(inputs) ? inputs[0] : inputs
|
||||
el?.focus()
|
||||
el?.select()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
cancelLernenEdit() {
|
||||
this.editingLernenKey = null
|
||||
this.editLernenValue = 0
|
||||
},
|
||||
|
||||
async saveLernenEdit(row) {
|
||||
if (this.editingLernenKey !== row.key) return
|
||||
if (row.learnCost === this.editLernenValue) {
|
||||
this.cancelLernenEdit()
|
||||
return
|
||||
}
|
||||
const newValue = this.editLernenValue
|
||||
this.cancelLernenEdit()
|
||||
this.isSaving = true
|
||||
try {
|
||||
const resp = await API.post('/api/maintenance/skill-improvement-cost2', payload)
|
||||
this.costs.push(resp.data)
|
||||
this.cancelCreate()
|
||||
for (const id of row.itemIds) {
|
||||
const resp = await API.put(`/api/maintenance/skill-category-difficulties/${id}`, {
|
||||
learn_cost: newValue,
|
||||
})
|
||||
const idx = this.skillCatDiffs.findIndex(d => d.id === id)
|
||||
if (idx !== -1) this.skillCatDiffs.splice(idx, 1, resp.data)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to create cost:', err)
|
||||
console.error('Failed to save learn cost:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isSaving = false
|
||||
|
||||
+29
-10
@@ -306,6 +306,7 @@ export default {
|
||||
litsource:'Literaturquellen',
|
||||
misc:'Sonstige',
|
||||
skillimprovement:'Steigerungskosten',
|
||||
learningcost:'Lernkosten',
|
||||
},
|
||||
believe: {
|
||||
title: 'Glaubensrichtungen',
|
||||
@@ -364,17 +365,35 @@ export default {
|
||||
saving: 'Speichern...',
|
||||
cancel: 'Abbrechen'
|
||||
},
|
||||
learningcost: {
|
||||
title: 'Lernkosten',
|
||||
headerEPPerTE: 'EP-Kosten für 1 Trainingseinheit (TE)',
|
||||
headerEPPerLE: 'EP-Kosten für 1 Lerneinheit (LE) für Zauber',
|
||||
headerSpellLevelLE: 'Benötigte LE pro Zauberstufe',
|
||||
class: 'Klasse',
|
||||
epPerTEDesc: '1 Lerneinheit (LE) kostet das Dreifache an EP (+ 6 EP für Elfen).',
|
||||
epPerLEDesc: 'EP-Kosten für 1 Lerneinheit (LE) für Zauber nach Charakterklasse und Zauberschule (+ 6 EP für Elfen).',
|
||||
spellLevelLEDesc: 'Benötigte Lerneinheiten (LE) pro Zauberstufe.',
|
||||
goldCostTE: 'Geldkosten: 20 GS je TE, 200 GS je LE.',
|
||||
goldCostLE: 'Geldkosten: 100 GS je LE.',
|
||||
maSpecialNote: '* Ma erhalten LE für Sprüche aus ihrem Spezialgebiet für 30 EP.',
|
||||
scrollLearnNote: 'Lernen von Spruchrollen: 1/3 EP je LE bei Erfolg, pauschal 20 GS je Lernversuch.',
|
||||
spellLevel: 'Zauberstufe',
|
||||
leRequired: 'LE erforderlich',
|
||||
clickToEdit: 'Klicke auf einen Wert um ihn zu bearbeiten.',
|
||||
},
|
||||
skillimprovement: {
|
||||
title: 'Fertigkeitssteigerungskosten',
|
||||
id: 'ID',
|
||||
level: 'Aktueller Wert',
|
||||
te: 'TE erforderlich',
|
||||
category: 'Kategorie-ID',
|
||||
difficulty: 'Schwierigkeits-ID',
|
||||
edit: 'Bearbeiten',
|
||||
save: 'Speichern',
|
||||
saving: 'Speichern...',
|
||||
cancel: 'Abbrechen'
|
||||
title: 'Lern- und Trainingslisten',
|
||||
description: 'Die Zahl an Lern- und Trainingseinheiten f\u00fcr ein und dieselbe Fertigkeit stimmen in allen Gruppen \u00fcberein. Die Gruppenzugeh\u00f6rigkeit entscheidet nur dar\u00fcber, wie viele EP der Abenteurer abh\u00e4ngig von seinem Typ f\u00fcr LE und TE bezahlen muss.',
|
||||
lernen: 'Lernen',
|
||||
verbessern: 'Verbessern (TE)',
|
||||
difficulty: 'Schwierigkeit',
|
||||
le: 'LE',
|
||||
skills: 'Fertigkeiten',
|
||||
noLernenData: 'Keine Lerndaten f\u00fcr diese Kategorie vorhanden.',
|
||||
noVerbessernData: 'Keine Verbesserungsdaten f\u00fcr diese Kategorie vorhanden.',
|
||||
clickToEdit: 'Klicke auf einen Wert um ihn zu bearbeiten.',
|
||||
clickLeToEdit: 'Klicke auf einen LE-Wert um ihn zu bearbeiten.',
|
||||
},
|
||||
search:'Suche',
|
||||
newEntry:'Neuer Eintrag',
|
||||
|
||||
+29
-10
@@ -302,6 +302,7 @@ export default {
|
||||
litsource:'Sources',
|
||||
misc:'Misc',
|
||||
skillimprovement:'Improvement Costs',
|
||||
learningcost:'Learning Costs',
|
||||
},
|
||||
believe: {
|
||||
title: 'Beliefs',
|
||||
@@ -360,17 +361,35 @@ export default {
|
||||
saving: 'Saving...',
|
||||
cancel: 'Cancel'
|
||||
},
|
||||
learningcost: {
|
||||
title: 'Learning Costs',
|
||||
headerEPPerTE: 'EP Costs per Training Unit (TE)',
|
||||
headerEPPerLE: 'EP Costs per Learning Unit (LE) for Spells',
|
||||
headerSpellLevelLE: 'Required LE per Spell Level',
|
||||
class: 'Class',
|
||||
epPerTEDesc: '1 Learning Unit (LE) costs triple the EP (+ 6 EP for Elves).',
|
||||
epPerLEDesc: 'EP costs for 1 Learning Unit (LE) for spells by character class and spell school (+ 6 EP for Elves).',
|
||||
spellLevelLEDesc: 'Required learning units (LE) per spell level.',
|
||||
goldCostTE: 'Gold costs: 20 GS per TE, 200 GS per LE.',
|
||||
goldCostLE: 'Gold costs: 100 GS per LE.',
|
||||
maSpecialNote: '* Magicians receive LE for spells in their specialization for 30 EP.',
|
||||
scrollLearnNote: 'Learning from spell scrolls: 1/3 EP per LE on success, flat 20 GS per attempt.',
|
||||
spellLevel: 'Spell Level',
|
||||
leRequired: 'LE required',
|
||||
clickToEdit: 'Click a value to edit it.',
|
||||
},
|
||||
skillimprovement: {
|
||||
title: 'Skill Improvement Costs',
|
||||
id: 'ID',
|
||||
level: 'Current level',
|
||||
te: 'TE required',
|
||||
category: 'Category ID',
|
||||
difficulty: 'Difficulty ID',
|
||||
edit: 'Edit',
|
||||
save: 'Save',
|
||||
saving: 'Saving...',
|
||||
cancel: 'Cancel'
|
||||
title: 'Learning & Training Lists',
|
||||
description: 'The number of learning and training units for any given skill is the same across all groups. Group membership only determines how many EP the adventurer must pay for LE and TE based on their type.',
|
||||
lernen: 'Learning',
|
||||
verbessern: 'Improving (TE)',
|
||||
difficulty: 'Difficulty',
|
||||
le: 'LE',
|
||||
skills: 'Skills',
|
||||
noLernenData: 'No learning data for this category.',
|
||||
noVerbessernData: 'No improvement data for this category.',
|
||||
clickToEdit: 'Click a value to edit it.',
|
||||
clickLeToEdit: 'Click an LE value to edit it.',
|
||||
},
|
||||
search:'Search',
|
||||
newEntry:'New Entry',
|
||||
|
||||
Reference in New Issue
Block a user