Adds mod management (WIP)

This commit is contained in:
mstein
2022-07-05 00:10:08 +02:00
parent 8b6bfd2efb
commit c8d59588e0
18 changed files with 1181 additions and 171 deletions
+100 -1
View File
@@ -1,4 +1,48 @@
export interface M5CharacterCalculatedData {
level: number
attributes: {
st: { value: number, bonus: number }
gs: { value: number, bonus: number }
gw: { value: number, bonus: number }
ko: { value: number, bonus: number }
in: { value: number, bonus: number }
zt: { value: number, bonus: number }
au: { value: number, bonus: number }
pa: { value: number, bonus: number }
wk: { value: number, bonus: number }
}
stats: {
armor: number
defense: number
damageBonus: number
attackBonus: number
defenseBonus: number
movementBonus: number
resistanceMind: number
resistanceBody: number
spellCasting: number
brawl: number
brawlEw: number
poisonResistance: number
enduranceBonus: number
}
skills: {
innate: {}
general: {}
combat: {}
language: {}
custom: {}
}
gear: {
weapons: {}
defensiveWeapons: {}
armor: {}
items: {}
}
spells: {}
}
export interface M5Skill {
fw: number
attribute: string
@@ -13,6 +57,11 @@ export interface M5SkillLearned extends M5Skill {
type: string
}
export interface M5SkillCalculated extends M5Skill {
label: string,
calc: any
}
export interface M5Attribute {
value: number
bonus: number
@@ -47,4 +96,54 @@ export enum M5EwResult {
HIGH = "roll-ew-result-high",
FAIL = "roll-ew-result-fail",
PASS = "roll-ew-result-pass"
}
}
export enum M5Attributes {
ST = "st",
GW = "gw",
GS = "gs",
KO = "ko",
IN = "in",
ZT = "zt",
AU = "au",
PA = "pa",
WK = "wk"
}
export enum M5Stats {
DEFENSE = "defense",
ATTACK = "attack",
DAMAGE = "damage",
MOVEMENT = "movement",
RESISTANCE_MIND = "resistanceMind",
RESISTANCE_BODY = "resistanceBody",
SPELL_CASTING = "spellCasting",
BRAWL = "brawl",
POISON_RESISTANCE = "poisonResistance",
LP = "lp",
AP = "ap"
}
export enum M5ModType {
ATTRIBUTE = "attribute",
STAT = "stat",
SKILL = "skill"
}
export enum M5ModOperation {
ADD_100 = "add100",
ADD = "add",
SET = "set",
FIXED = "fixed"
}
export interface M5ItemMod {
type: M5ModType
id: string
operation: M5ModOperation
value: number
}
export function enumKeys<O extends object, K extends keyof O = keyof O>(obj: O): K[] {
return Object.keys(obj).filter(k => Number.isNaN(+k)) as K[]
}
+31 -67
View File
@@ -1,49 +1,5 @@
import { M5Item } from "../items/M5Item"
import { M5Attribute, M5RollData, M5Skill, M5SkillLearned } from "../M5Base"
export interface M5CharacterCalculatedData {
level: number
attributes: {
st: { value: number, bonus: number }
gs: { value: number, bonus: number }
gw: { value: number, bonus: number }
ko: { value: number, bonus: number }
in: { value: number, bonus: number }
zt: { value: number, bonus: number }
au: { value: number, bonus: number }
pa: { value: number, bonus: number }
wk: { value: number, bonus: number }
}
stats: {
armor: number
defense: number
damageBonus: number
attackBonus: number
defenseBonus: number
movementBonus: number
resistanceMind: number
resistanceBody: number
spellCasting: number
brawl: number
brawlEw: number
poisonResistance: number
enduranceBonus: number
}
skills: {
innate: {}
general: {}
combat: {}
language: {}
custom: {}
}
gear: {
weapons: {}
defensiveWeapons: {}
armor: {}
items: {}
}
spells: {}
}
import { M5Attribute, M5CharacterCalculatedData, M5RollData, M5Skill, M5SkillCalculated, M5SkillLearned } from "../M5Base"
export class M5Character extends Actor {
@@ -118,7 +74,13 @@ export class M5Character extends Actor {
} as M5CharacterCalculatedData
const context = (this as any).data
if (!context)
return null
const data = context.data
if (!data)
return null
ret.level = M5Character.levelFromExp(data.es)
ret.attributes.st.value = M5Character.attributeMinMax(data.attributes.st) // TODO item effects
@@ -155,6 +117,23 @@ export class M5Character extends Actor {
ret.stats.poisonResistance = 30 + Math.floor(ret.attributes.ko.value / 2)
ret.stats.enduranceBonus = Math.floor(ret.attributes.ko.value/10) + Math.floor(ret.attributes.st.value/20)
if (!skip.items) {
context.items?.filter(item => item.data.type === "item").forEach(item => {
item.prepareDerivedData()
let label = item.data.name
if (item.data.data.magic) {
label += "*"
}
ret.gear.items[item.data._id] = {
label: label,
magic: item.data.data.magic,
calc: item.data.data.calc
}
})
}
if (!skip.skills) {
context.items?.filter(item => item.data.type === "skill").forEach(item => {
item.prepareDerivedData()
@@ -164,7 +143,7 @@ export class M5Character extends Actor {
fw: item.data.data.fw,
attribute: item.data.data.attribute,
calc: item.data.data.calc
}
} as M5SkillCalculated
})
}
@@ -227,23 +206,6 @@ export class M5Character extends Actor {
})
}
if (!skip.items) {
context.items?.filter(item => item.data.type === "item").forEach(item => {
item.prepareDerivedData()
let label = item.data.name
if (item.data.data.magic) {
label += "*"
}
ret.gear.items[item.data._id] = {
label: label,
magic: item.data.data.magic,
calc: item.data.data.calc
}
})
}
if (!skip.spells) {
context.items?.filter(item => item.data.type === "spell").forEach(item => {
item.prepareDerivedData()
@@ -323,7 +285,7 @@ export class M5Character extends Actor {
attribute(name: string): M5Attribute {
const context = (this as any).data
return context.data.attributes[name]
return context?.data?.attributes[name]
}
createSkill(skillName: string): Promise<M5Item> {
@@ -334,12 +296,14 @@ export class M5Character extends Actor {
return (this as any).createEmbeddedDocuments("Item", [itemData]).then(docs => {
const item = docs[0]
return item as M5Item
return item
})
}
getSkill(skillId: string): M5Skill {
return (this as any).getEmbeddedDocument("Item", skillId) as M5Skill
getItem(itemId: string): any {
if (!(this as any).data?.items)
return null
return (this as any).getEmbeddedDocument("Item", itemId)
}
}
+98 -12
View File
@@ -1,5 +1,7 @@
import { ItemData } from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/module.mjs"
import { ConstructorDataType } from "@league-of-foundry-developers/foundry-vtt-types/src/types/helperTypes"
import { M5Character } from "../actors/M5Character"
import { M5RollData, M5RollResult, M5Skill } from "../M5Base"
import { enumKeys, M5Attributes, M5ItemMod, M5ModType, M5RollData, M5RollResult, M5Skill, M5Stats } from "../M5Base"
import { M5Roll } from "../rolls/M5Roll"
export class M5Item extends Item {
@@ -16,7 +18,8 @@ export class M5Item extends Item {
if (context.data?.attribute && context.data?.attribute !== "" && character) {
const attribute = character.attribute(context.data.attribute)
calc.bonus += M5Character.attributeBonus(attribute)
if (attribute)
calc.bonus += M5Character.attributeBonus(attribute)
}
calc.ew = context.data.fw + calc.bonus
@@ -29,9 +32,12 @@ export class M5Item extends Item {
if (actor) {
const actorCalc = character.derivedData({ weapons: true, defensiveWeapons: true, armor: true, items: true, spells: true })
calc.ew += actorCalc.stats.attackBonus
if (actorCalc) {
calc.ew += actorCalc.stats.attackBonus
calc.combatSkills = actorCalc.skills.combat
}
const skill = character.getSkill(context.data.skillId) as any
const skill = character.getItem(context.data.skillId)
//console.log("M5Item.prepareDerivedData:weapon", context.data, skill?.data?.data)
if (skill) {
skill.prepareDerivedData()
@@ -40,8 +46,6 @@ export class M5Item extends Item {
calc.bonus += skillData.calc.bonus
calc.fw += skillData.fw
}
calc.combatSkills = actorCalc.skills.combat
}
} else if (context.type === "defensiveWeapon") {
calc.fw = 0
@@ -52,9 +56,12 @@ export class M5Item extends Item {
if (actor) {
const actorCalc = character.derivedData({ weapons: true, defensiveWeapons: true, armor: true, items: true, spells: true })
calc.ew += actorCalc.stats.defense + actorCalc.stats.defenseBonus
if (actorCalc) {
calc.ew += actorCalc.stats.defense + actorCalc.stats.defenseBonus
calc.combatSkills = actorCalc.skills.combat
}
const skill = character.getSkill(context.data.skillId) as any
const skill = character.getItem(context.data.skillId)
//console.log("M5Item.prepareDerivedData:weapon", context.data, skill?.data?.data)
if (skill) {
skill.prepareDerivedData()
@@ -63,15 +70,90 @@ export class M5Item extends Item {
calc.bonus += skillData.calc.bonus
calc.fw += skillData.fw
}
calc.combatSkills = actorCalc.skills.combat
}
} else if (context.type === "spell") {
calc.ew = context.data.bonus
if (actor) {
const actorCalc = character.derivedData({ weapons: true, defensiveWeapons: true, armor: true, items: true, spells: true })
calc.ew += actorCalc.stats.spellCasting
if (actorCalc) {
calc.ew += actorCalc.stats.spellCasting
}
}
} else if (context.type === "mod") {
const parent = (this as any).parent
const actor = parent?.actor
const character = actor as M5Character
calc.ids = {}
switch (context.data.type as M5ModType) {
case M5ModType.ATTRIBUTE: {
for (const key of enumKeys(M5Attributes)) {
const val = M5Attributes[key]
calc.ids[key] = (game as Game).i18n.localize(`midgard5.actor-${val}-long`)
}
break
}
case M5ModType.STAT: {
for (const key of enumKeys(M5Stats)) {
const val = M5Stats[key]
calc.ids[key] = (game as Game).i18n.localize(`midgard5.mod-stat-${val}`)
}
break
}
case M5ModType.SKILL: {
if (character) {
const actorCalc = character.derivedData({ weapons: true, defensiveWeapons: true, armor: true, items: true, spells: true })
if (actorCalc) {
let category = (game as Game).i18n.localize("midgard5.innate-ability")
Object.keys(actorCalc.skills.innate).forEach(skillId => {
const skill = character.getItem(skillId)
if (skill)
calc.ids[skillId] = `${category}: ${skill.data.name}`
})
}
}
break
}
}
} else if (context.type === "item") {
calc.mods = {}
Object.keys(context.data?.mods).forEach(key => {
const mod = context.data.mods[key]
const modCalc = {}
switch (mod.type) {
case M5ModType.ATTRIBUTE: {
for (const key of enumKeys(M5Attributes)) {
const val = M5Attributes[key]
modCalc[key] = (game as Game).i18n.localize(`midgard5.actor-${val}-long`)
}
break
}
case M5ModType.STAT: {
for (const key of enumKeys(M5Stats)) {
const val = M5Stats[key]
modCalc[key] = (game as Game).i18n.localize(`midgard5.mod-stat-${val}`)
}
break
}
case M5ModType.SKILL: {
if (character) {
const actorCalc = character.derivedData({ weapons: true, defensiveWeapons: true, armor: true, items: true, spells: true })
if (actorCalc) {
let category = (game as Game).i18n.localize("midgard5.innate-ability")
Object.keys(actorCalc.skills.innate).forEach(skillId => {
const skill = character.getItem(skillId)
if (skill)
modCalc[skillId] = `${category}: ${skill.data.name}`
})
}
}
break
}
}
calc.mods[key] = modCalc
})
}
}
@@ -128,6 +210,10 @@ export class M5Item extends Item {
})
return null
}
}
}
getItem(itemId: string): any {
return (this as any).getEmbeddedDocument("Item", itemId)
}
}
+17 -1
View File
@@ -57,9 +57,26 @@ export default class M5CharacterSheet extends ActorSheet {
const context = this.actor.data
const item = context.items.get(itemId)
console.log("edit-item", item)
item.sheet.render(true)
})
html.find(".item-delete").on("click", async (event) => {
let row = event.target.parentElement
let itemId = row.dataset["item"]
while (!itemId) {
row = row.parentElement
if (!row)
return
itemId = row.dataset["item"]
}
const context = this.actor.data
const item = context.items.get(itemId)
item.delete()
this.render(false)
})
html.find(".roll-learned-button").on("click", async (event) => {
const row = event.target.parentElement.parentElement
let skillId = row.dataset["item"]
@@ -193,7 +210,6 @@ export default class M5CharacterSheet extends ActorSheet {
if (!data.data)
return false
if (data.actorId === this.actor.id)
return false
+105
View File
@@ -1,4 +1,5 @@
import { M5Item } from "../items/M5Item"
import { M5Attributes, M5ItemMod, M5ModOperation, M5ModType } from "../M5Base"
export class M5ItemSheet extends ItemSheet {
@@ -41,4 +42,108 @@ export class M5ItemSheet extends ItemSheet {
})
}
override activateListeners(html: JQuery) {
super.activateListeners(html)
html.find(".add-mod").on("click", async (event) => {
const context = this.object.data
const mods = context.data.mods
const modIndex = Object.keys(mods).length
mods[modIndex.toString()] = {
type: M5ModType.ATTRIBUTE,
id: M5Attributes.ST,
operation: M5ModOperation.ADD,
value: 0
} as M5ItemMod
this.object.update({
data: {
mods: mods
}
})
})
html.find(".item-delete").on("click", async (event) => {
let row = event.target.parentElement
let itemId = row.dataset["item"]
while (!itemId) {
row = row.parentElement
if (!row)
return
itemId = row.dataset["item"]
}
const context = this.item.data
const item = context.items.get(itemId)
item.delete()
this.render(false)
})
// Drag & Drop
if (["item"].includes(this.object.data?.type)) {
const itemToItemAssociation = new DragDrop({
dragSelector: ".item",
dropSelector: null,
permissions: { dragstart: this._canDragStart.bind(this), drop: this._canDragDrop.bind(this) },
callbacks: { drop: this._onDropItem.bind(this) },
})
itemToItemAssociation.bind(html[0])
}
}
_canDragStart(selector) {
console.log("M5ItemSheet._canDragStart", selector)
return this.options.editable && this.object.isOwner
}
_canDragDrop(selector) {
console.log("M5ItemSheet._canDragDrop", selector)
return true
}
async _onDropItem(event) {
let data
const obj = this.object
const li = event.currentTarget
try {
data = JSON.parse(event.dataTransfer.getData("text/plain"))
if (data.type !== "Item")
return false
} catch (err) {
return false
}
// Case 1 - Import from a Compendium pack
let itemObject
if (data.pack) {
const compendiumObject = await (this as any).importItemFromCollection(data.pack, data.id)
itemObject = compendiumObject.data
}
// Case 2 - Import from World entity
else {
const originalItem = await (game as Game).items.get(data.id)
itemObject = duplicate(originalItem)
if (!itemObject)
return
}
if ((itemObject.type === "mod")) {
let mods = obj?.data?.data?.mods
if (!mods)
mods = []
itemObject.id = randomID()
console.log("M5ItemSheet._onDropItem", itemObject)
mods.push(itemObject)
obj.update({
data: {
mods: mods
}
})
}
}
async _onDragItemStart(event) { }
}
+5
View File
@@ -47,6 +47,11 @@
&.center {
text-align: center;
}
&.fixed-value {
width: 3rem;
text-align: center;
}
}
input.skill {
+28 -20
View File
@@ -1,6 +1,6 @@
{
"Actor": {
"types": ["npc", "character"],
"types": ["character"],
"templates": {
"characterDescription": {
"info": {
@@ -118,7 +118,7 @@
}
},
"Item": {
"types": ["skill", "item", "weapon", "defensiveWeapon", "armor", "spell"],
"types": ["skill", "weapon", "defensiveWeapon", "armor", "spell", "item"],
"templates": {
"itemDescription": {
"description": ""
@@ -170,6 +170,10 @@
"equippable": false,
"equipped": true
},
"physical": {
"value": 0,
"magic": false
},
"spellSelection": {
"spellProcessSelection": {
"none": "midgard5.spell-process-none",
@@ -214,31 +218,33 @@
"0": {
"formula": "1d20 + @i.fw + @i.calc.bonus",
"type": "ew",
"label": "EW"
"label": "EW",
"enabled": true
}
},
"output": ""
},
"calc": {}
},
"item": {
"templates": ["itemDescription", "stats", "equippable"],
"quantity": 1,
"mod": {
"type": "",
"id": "",
"operation": "",
"value": 0,
"magic": false,
"onbody": false,
"attributes": {},
"groups": {},
"calc": {}
},
"item": {
"templates": ["itemDescription", "equippable", "physical"],
"rolls": {
"formulas": {},
"output": ""
},
"mods": {},
"calc": {}
},
"weapon": {
"templates": ["itemDescription", "stats", "equippable"],
"templates": ["itemDescription", "stats", "equippable", "physical"],
"special": false,
"magic": false,
"ranged": false,
"skillId": "",
"damageBase": "1d6",
@@ -247,12 +253,14 @@
"0": {
"formula": "1d20 + @i.calc.fw + @i.calc.bonus + @i.calc.special + @c.calc.stats.attackBonus + @i.stats.attackBonus",
"type": "ew",
"label": "Angriff"
"label": "Angriff",
"enabled": true
},
"1": {
"formula": "@i.damageBase + @i.stats.damageBonus + @c.calc.stats.damageBonus",
"type": "dmg",
"label": "Schaden"
"label": "Schaden",
"enabled": true
}
},
"output": ""
@@ -260,16 +268,16 @@
"calc": {}
},
"defensiveWeapon": {
"templates": ["itemDescription", "stats", "equippable"],
"templates": ["itemDescription", "stats", "equippable", "physical"],
"special": false,
"magic": false,
"skillId": "",
"rolls": {
"formulas": {
"0": {
"formula": "1d20 + @i.calc.fw + @i.calc.bonus + @i.calc.special + @c.calc.stats.defense + @c.calc.stats.defenseBonus + @i.stats.defenseBonus",
"type": "ew",
"label": "Abwehr"
"label": "Abwehr",
"enabled": true
}
},
"output": ""
@@ -277,8 +285,7 @@
"calc": {}
},
"armor": {
"templates": ["itemDescription", "stats", "equippable", "attributeMod"],
"magic": false,
"templates": ["itemDescription", "stats", "equippable", "attributeMod", "physical"],
"lpProtection": 0,
"apProtection": 0,
"rolls": {
@@ -304,7 +311,8 @@
"0": {
"formula": "1d20 + @c.calc.stats.spellCasting + @i.bonus",
"type": "ew",
"label": "Zaubern"
"label": "Zaubern",
"enabled": true
}
},
"output": ""