Adds gear and spells as items.

Adds custom roll logic, inclusing chat message.
This commit is contained in:
mstein
2022-06-19 00:18:41 +02:00
parent ca82fa119f
commit 8b6bfd2efb
33 changed files with 1334 additions and 482 deletions
+28 -4
View File
@@ -19,8 +19,32 @@ export interface M5Attribute {
}
export interface M5RollData {
c: any,
i: any,
rolls: any,
res: any
c: any
i: any
iType: string
rolls: {}
res: {
label: string
}
}
export interface M5RollResult {
formula: string
label: string
type: string
total: number
totalStr: string
result: string
dice: {}
css: string
}
export enum M5EwResult {
TBD = "",
FUMBLE = "roll-ew-result-fumble",
CRITICAL = "roll-ew-result-critical",
HIGH = "roll-ew-result-high",
FAIL = "roll-ew-result-fail",
PASS = "roll-ew-result-pass"
}
+81 -6
View File
@@ -25,10 +25,12 @@ export interface M5CharacterCalculatedData {
resistanceBody: number
spellCasting: number
brawl: number
brawlEw: number
poisonResistance: number
enduranceBonus: number
}
skills: {
innate: {}
general: {}
combat: {}
language: {}
@@ -36,9 +38,11 @@ export interface M5CharacterCalculatedData {
}
gear: {
weapons: {}
defensiveWeapons: {}
armor: {}
items: {}
}
spells: {}
}
export class M5Character extends Actor {
@@ -68,7 +72,7 @@ export class M5Character extends Actor {
return -2
}
derivedData(skipSkills: boolean, skipWeapons: boolean): M5CharacterCalculatedData {
derivedData(skip: { skills?: boolean, weapons?: boolean, defensiveWeapons?: boolean, armor?: boolean, items?: boolean, spells?: boolean } = {} ): M5CharacterCalculatedData {
let ret: M5CharacterCalculatedData = {
level: 0,
attributes: {
@@ -93,10 +97,12 @@ export class M5Character extends Actor {
resistanceBody: 0,
spellCasting: 0,
brawl: 0,
brawlEw: 0,
poisonResistance: 0,
enduranceBonus: 0
},
skills: {
innate: {},
general: {},
combat: {},
language: {},
@@ -104,9 +110,11 @@ export class M5Character extends Actor {
},
gear: {
weapons: {},
defensiveWeapons: {},
armor: {},
items: {}
}
},
spells: {}
} as M5CharacterCalculatedData
const context = (this as any).data
@@ -143,10 +151,11 @@ export class M5Character extends Actor {
ret.stats.resistanceBody = ret.stats.defense + 1
ret.stats.spellCasting = (data.info.magicUsing ? M5Character.spellCastingFromLevel(ret.level) : 3) + ret.attributes.zt.bonus
ret.stats.brawl = Math.floor((ret.attributes.st.value + ret.attributes.gw.value) / 20)
ret.stats.brawlEw = ret.stats.brawl + ret.stats.attackBonus
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 (!skipSkills) {
if (!skip.skills) {
context.items?.filter(item => item.data.type === "skill").forEach(item => {
item.prepareDerivedData()
const skillMap = ret.skills[item.data.data.type]
@@ -159,7 +168,7 @@ export class M5Character extends Actor {
})
}
if (!skipWeapons) {
if (!skip.weapons) {
context.items?.filter(item => item.data.type === "weapon").forEach(item => {
item.prepareDerivedData()
@@ -175,25 +184,91 @@ export class M5Character extends Actor {
ret.gear.weapons[item.data._id] = {
label: label,
skillId: item.data.data.skillId,
defensive: item.data.data.defensive,
magic: item.data.data.magic,
calc: item.data.data.calc
}
})
}
if (!skip.defensiveWeapons) {
context.items?.filter(item => item.data.type === "defensiveWeapon").forEach(item => {
item.prepareDerivedData()
let label = item.data.name
if (item.data.data.magic) {
label += "*("
+ (item.data.data.stats.defenseBonus < 0 ? "" : "+")
+ item.data.data.stats.defenseBonus + ")"
}
ret.gear.defensiveWeapons[item.data._id] = {
label: label,
skillId: item.data.data.skillId,
magic: item.data.data.magic,
calc: item.data.data.calc
}
})
}
if (!skip.armor) {
context.items?.filter(item => item.data.type === "armor").forEach(item => {
item.prepareDerivedData()
let label = item.data.name
if (item.data.data.magic) {
label += "*"
}
ret.gear.armor[item.data._id] = {
label: label,
magic: item.data.data.magic,
calc: item.data.data.calc
}
})
}
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()
ret.spells[item.data._id] = {
label: item.data.name,
process: "midgard5.spell-process-" + item.data.data.process,
calc: item.data.data.calc
}
})
}
return ret
}
prepareDerivedData() {
const context = (this as any).data
context.data.calc = this.derivedData(false, false)
context.data.calc = this.derivedData({})
}
override getRollData(): any {
return {
c: (this as any).data.data,
i: null,
iType: null,
rolls: {},
res: {}
} as M5RollData
+53 -20
View File
@@ -1,5 +1,6 @@
import { M5Character } from "../actors/M5Character"
import { M5RollData, M5Skill } from "../M5Base"
import { M5RollData, M5RollResult, M5Skill } from "../M5Base"
import { M5Roll } from "../rolls/M5Roll"
export class M5Item extends Item {
static readonly SKILL = "skill"
@@ -13,22 +14,21 @@ export class M5Item extends Item {
if (context.type === "skill") {
calc.bonus = 0
if (context.data.attribute && context.data.attribute !== "") {
if (context.data?.attribute && context.data?.attribute !== "" && character) {
const attribute = character.attribute(context.data.attribute)
calc.bonus += M5Character.attributeBonus(attribute)
}
if (context._id === actor.data?.data?.skills.preferredCombatSkill) {
calc.bonus += 2
}
calc.ew = context.data.fw + calc.bonus
} else if (context.type === "weapon") {
calc.ew = context.data.stats.attackBonus
calc.fw = 0
calc.bonus = 0
calc.special = context.data.special ? 2 : 0
calc.ew = calc.special + context.data.stats.attackBonus
calc.combatSkills = null
if (actor) {
const actorCalc = character.derivedData(false, true)
const actorCalc = character.derivedData({ weapons: true, defensiveWeapons: true, armor: true, items: true, spells: true })
calc.ew += actorCalc.stats.attackBonus
const skill = character.getSkill(context.data.skillId) as any
@@ -37,10 +37,41 @@ export class M5Item extends Item {
skill.prepareDerivedData()
const skillData = skill.data.data
calc.ew += skillData.calc.ew
calc.bonus += skillData.calc.bonus
calc.fw += skillData.fw
}
calc.combatSkills = actorCalc.skills.combat
}
} else if (context.type === "defensiveWeapon") {
calc.fw = 0
calc.bonus = 0
calc.special = context.data.special ? 2 : 0
calc.ew = calc.special + context.data.stats.defenseBonus
calc.combatSkills = null
if (actor) {
const actorCalc = character.derivedData({ weapons: true, defensiveWeapons: true, armor: true, items: true, spells: true })
calc.ew += actorCalc.stats.defense + actorCalc.stats.defenseBonus
const skill = character.getSkill(context.data.skillId) as any
//console.log("M5Item.prepareDerivedData:weapon", context.data, skill?.data?.data)
if (skill) {
skill.prepareDerivedData()
const skillData = skill.data.data
calc.ew += skillData.calc.ew
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
}
}
}
@@ -51,11 +82,13 @@ export class M5Item extends Item {
let ret: M5RollData = actor?.getRollData() ?? {
c: null,
i: null,
iType: null,
rolls: {},
res: {}
}
ret.i = context.data
ret.iType = context.type
return ret
}
@@ -71,21 +104,21 @@ export class M5Item extends Item {
const formulaNames = item.data.rolls?.formulas ? Object.keys(item.data.rolls.formulas) : []
if (formulaNames.length > 0) {
const rollData = this.getRollData()
let ret = []
formulaNames.forEach(formulaName => {
const formula = item.data.rolls.formulas[formulaName]
const roll = new Roll(formula.formula, rollData)
roll.toMessage({
speaker: speaker,
rollMode: rollMode,
flavor: label,
})
ret.push(roll)
rollData.rolls[formulaName] = {
formula: formula.formula,
label: formula.label,
type: formula.type,
result: "",
total: 0,
totalStr: "",
dice: {}
} as M5RollResult
})
return ret
const roll = new M5Roll(rollData, this.actor, item.name)
return roll.toMessage()
} else {
ChatMessage.create({
speaker: speaker,
+160
View File
@@ -0,0 +1,160 @@
import { Evaluated } from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/client/dice/roll";
import { M5Character } from "../actors/M5Character";
import { M5EwResult, M5RollData, M5RollResult } from "../M5Base";
export class M5Roll { // extends Roll<M5RollData>
static readonly TEMPLATE_PATH = "systems/midgard5/templates/chat/roll-m5.hbs"
public _evaluated: boolean = false
public _total: number = 0
public pool: PoolTerm = null
constructor(public data: M5RollData, public actor: any, public label: string) {
//super(null)
//this.data = rollData
}
// @ts-ignore
//override evaluate(options?: InexactPartial<RollTerm.EvaluationOptions>): Evaluated<Roll<M5RollData>> | Promise<Evaluated<Roll<M5RollData>>> {
evaluate() {
const rollNames = Object.keys(this.data.rolls)
const rolls = rollNames.map(rollName => {
const formula = this.data.rolls[rollName]
const roll = new Roll(formula.formula, this.data)
return roll
})
this.pool = PoolTerm.fromRolls(rolls)
return this.pool.evaluate({ async: true }).then(results => {
this._total = 0
results.rolls.forEach((roll, index) => {
const rollResult = this.data.rolls[index.toString()] as M5RollResult
rollResult.result = roll.result
rollResult.total = roll.total
rollResult.totalStr = roll.total.toString()
this._total += roll.total
let rowRes = M5EwResult.TBD
roll.dice.forEach((d, dIndex) => {
rollResult.dice[dIndex.toString()] = d.total
if (rowRes === M5EwResult.TBD && dIndex === 0) {
if (rollResult.type === "ew") {
if (d.total === 1)
rowRes = M5EwResult.FUMBLE
else if (d.total === 20)
rowRes = M5EwResult.CRITICAL
else if (d.total >= 16)
rowRes = M5EwResult.HIGH
} else if (rollResult.type === "pw") {
if (d.total === 1)
rowRes = M5EwResult.FUMBLE
else if (d.total === 20)
rowRes = M5EwResult.CRITICAL
}
}
})
if (rollResult.type === "ew") {
if (roll.total < 20) {
if (rowRes === M5EwResult.TBD || rowRes === M5EwResult.HIGH)
rowRes = M5EwResult.FAIL
} else {
if (rowRes === M5EwResult.TBD)
rowRes = M5EwResult.PASS
}
} else if (rollResult.type === "pw") {
if (roll.total < 0) {
if (rowRes === M5EwResult.TBD)
rowRes = M5EwResult.FAIL
} else {
if (rowRes === M5EwResult.TBD)
rowRes = M5EwResult.PASS
}
}
rollResult.css = rowRes
})
this.data.res.label = this.label
this._evaluated = true
return this
})
}
async render(): Promise<string> {
return renderTemplate(M5Roll.TEMPLATE_PATH, this.data)
}
async toMessage() {
if (!this._evaluated)
await this.evaluate()
const rMode = (game as Game).settings.get("core", "rollMode")
const chatData = {
type: CONST.CHAT_MESSAGE_TYPES.ROLL,
content: await this.render(),
speaker: ChatMessage.getSpeaker({actor: this.actor}),
sound: CONFIG.sounds.dice,
roll: Roll.fromTerms([this.pool])
}
ChatMessage.applyRollMode(chatData, rMode)
return ChatMessage.create(chatData)
}
static fromAttribute(actor: any, attributeKey: string) {
const character = actor as M5Character
const attribute = character.attribute(attributeKey)
const rollData = actor.getRollData() as M5RollData
rollData.i = attribute.value + attribute.bonus
rollData.rolls["0"] = {
formula: "@i - 1d100",
type: "pw",
label: (game as Game).i18n.localize("midgard5.pw"),
result: "",
total: 0,
totalStr: "",
dice: {},
css: ""
} as M5RollResult
return new M5Roll(rollData, actor, (game as Game).i18n.localize(`midgard5.actor-${attributeKey}-long`))
}
static brawl(actor: any) {
const rollData = actor.getRollData() as M5RollData
rollData.i = {
attackBonus: 0,
damageBonus: 0
}
rollData.rolls["0"] = {
formula: "1d20 + @c.calc.stats.brawl + @c.calc.stats.attackBonus + @i.attackBonus",
type: "ew",
label: (game as Game).i18n.localize("midgard5.attack"),
result: "",
total: 0,
totalStr: "",
dice: {},
css: ""
} as M5RollResult
rollData.rolls["1"] = {
formula: "1d6 - 4 + @c.calc.stats.damageBonus + @i.damageBonus",
type: "dmg",
label: (game as Game).i18n.localize("midgard5.damage"),
result: "",
total: 0,
totalStr: "",
dice: {},
css: ""
} as M5RollResult
return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.brawl"))
}
}
+22 -35
View File
@@ -2,13 +2,14 @@ import Logger from "../../utils/Logger"
import { M5Character } from "../actors/M5Character"
import { M5Item } from "../items/M5Item"
import { M5SkillLearned, M5SkillUnlearned } from "../M5Base"
import { M5Roll } from "../rolls/M5Roll"
export default class M5CharacterSheet extends ActorSheet {
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
template: "systems/midgard5/templates/sheets/character/main.hbs",
width: 600,
width: 800,
height: 800,
classes: ["midgard5", "sheet", "character"],
tabs: [{ navSelector: ".sheet-navigation", contentSelector: ".sheet-content", initial: "base_values" }]
@@ -37,29 +38,28 @@ export default class M5CharacterSheet extends ActorSheet {
override activateListeners(html: JQuery) {
super.activateListeners(html)
html.find(".roll-attribute-button").on("click", async (event) => {
const attributeStr = event.target.dataset["attribute"]
const roll = M5Roll.fromAttribute(this.actor, attributeStr)
console.log("roll-attribute-button", roll)
await roll.toMessage()
})
html.find(".edit-item").on("click", async (event) => {
const row = event.target.parentElement
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.sheet.render(true)
})
html.find(".change-special-combat-skill").on("click", async (event) => {
const row = event.target.parentElement.parentElement
let skillId = row.dataset["item"]
const actor = this.actor as any
actor.update({
data: {
skills: {
preferredCombatSkill: skillId
}
}
})
})
html.find(".roll-learned-button").on("click", async (event) => {
const row = event.target.parentElement.parentElement
let skillId = row.dataset["item"]
@@ -72,24 +72,6 @@ export default class M5CharacterSheet extends ActorSheet {
await item.roll()
})
html.find(".create-skill-button").on("click", async (event) => {
const button = event.target
const skillType = button.dataset["skilltype"]
const character = this.actor as M5Character
character.createSkill((game as Game).i18n.localize("midgard5.new-skill")).then(skill => {
const item = skill as any
item.update({
data: {
fw: 5,
attribute: "",
skill: "",
type: skillType
}
})
})
})
html.find(".roll-general-button").on("click", async (event) => {
const row = event.target.parentElement.parentElement
let skillName = row.dataset["skill"]
@@ -151,6 +133,11 @@ export default class M5CharacterSheet extends ActorSheet {
await item.roll()
})
html.find(".roll-brawl-button").on("click", async (event) => {
const roll = M5Roll.brawl(this.actor)
await roll.toMessage()
})
// Drag & Drop
const dragDrop = new DragDrop({
dragSelector: ".items-list .item",
@@ -175,7 +162,7 @@ export default class M5CharacterSheet extends ActorSheet {
const item = this.actor.items.get(li.dataset.itemId)
// limit transfer on personal weapons/armour/gear
if (["weapon"].includes(item.data.type)) {
if (["skill", "item", "weapon", "defensiveWeapon", "armor", "spell"].includes(item.data.type)) {
const dragData = {
type: "Transfer",
actorId: this.actor.id,