Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 62 additions & 27 deletions src/module/actor/__tests__/entity-actor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,34 +472,69 @@ export default ({
];
const levelSpread = Array.from({ length: 9 }, (_el, idx) => idx + 1);

conScoreSpread.forEach((con, idx) => {
const conMod = conBonusSpread[idx];
const expectedTerms = conMod >= 0 ? 5 : 6;
const modSign = conMod < 0 ? "-" : "+";
const modUnsigned = modSign === "-" ? conMod * -1 : conMod;
levelSpread.forEach((level) => {
it(`constructs the roll terms correctly with level ${level} and con ${con}`, async () => {
const actor = (await createMockActor("character")) as OseActor;
await actor?.update({
system: { details: { level }, scores: { con: { value: con } } },
describe("monster", () => {
it("constructs the roll terms correctly for monster actor", async () => {
const actor = (await createMockActor("monster")) as OseActor;
await actor?.update({ "system.hp.hd": "5d8" });

const roll = await actor.rollHitDice();

expect(roll.terms.length).equal(1);
expect(roll.terms[0].faces).equal(8);
expect(roll.terms[0].results.length).equal(5);

await actor?.delete();
});
});

describe("character", () => {
conScoreSpread.forEach((con, idx) => {
const conMod = conBonusSpread[idx];
const expectedTerms = conMod >= 0 ? 5 : 6;
const modSign = conMod < 0 ? "-" : "+";
const modUnsigned = modSign === "-" ? conMod * -1 : conMod;
levelSpread.forEach((level) => {
it(`constructs the roll terms correctly with level ${level} and con ${con} for character actor`, async () => {
const actor = (await createMockActor("character")) as OseActor;
await actor?.update({
system: { details: { level }, scores: { con: { value: con } } },
});
const roll = await actor.rollHitDice();

// Expect the roll constructed to look like for a character
// {level}{hd.type} + {con.mod}*{level}
// Expect the roll constructed to look like for a monster
// {hd}

// Check the die type & amount of HD
expect(roll.terms.length).equal(expectedTerms);
expect(roll.terms[0].faces).equal(
parseInt(
actor?.system.hp.hd.slice(actor.system.hp.hd.indexOf("d") + 1),
10
)
);
expect(roll.terms[0].results.length).equal(
actor?.system.details.level
);

// Check the con mod
if (conMod < 0) {
expect(roll.terms[expectedTerms - 5].operator).equal("+");
}
expect(roll.terms[expectedTerms - 4].operator).equal(modSign);
expect(roll.terms[expectedTerms - 3].expression).equal(
modUnsigned.toString()
);

// Check the level multiplier with level
expect(roll.terms[expectedTerms - 2].operator).equal("*");
expect(roll.terms[expectedTerms - 1].number).equal(level);

// Verify the con mod on the actor
expect(actor?.system.scores.con.mod).equal(conMod);
await actor?.delete();
});
const roll = await actor.rollHitDice();

expect(roll.terms.length).equal(expectedTerms);
expect(roll.terms[0].expression).equal(actor?.system.hp.hd);
if (conMod < 0) {
expect(roll.terms[expectedTerms - 5].operator).equal("+");
}
expect(roll.terms[expectedTerms - 4].operator).equal(modSign);
expect(roll.terms[expectedTerms - 3].expression).equal(
modUnsigned.toString()
);
expect(roll.terms[expectedTerms - 2].operator).equal("+");
expect(roll.terms[expectedTerms - 1].expression).equal(
level.toString()
);
expect(actor?.system.scores.con.mod).equal(conMod);
await actor?.delete();
});
});
});
Expand Down
24 changes: 18 additions & 6 deletions src/module/actor/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,19 @@ export default class OseActor extends Actor {
const actorData = this.system;

const label = game.i18n.localize(`OSE.roll.hd`);
const rollParts = [actorData.hp.hd];

const rollParts = [];

if (actorType === "character") {
rollParts.push(actorData.scores.con.mod * actorData.details.level);
const hd = `${actorData.details.level}${actorData.hp.hd.slice(
actorData.hp.hd.toLowerCase().indexOf("d")
)}`;
rollParts.push(hd);
rollParts.push(
`${actorData.scores.con.mod} * ${actorData.details.level}`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see the discussion here going all the way to the end

https://discord.com/channels/929420928528568390/929511321488203819/1061817746238738482

Particularly here, where the Moldvay Basic wording of the rules is written, and we're given to understand that the OSE rules are meant to emulate the Moldvay Basic rules procedure without repeating them word-for-word

image

There was a later conversation that I can't find, where I finally understood the rules

In short: our current implementation is actually incorrect, due to me choosing a different reading of the rules than was apparently intended. We should not be multiplying the con bonus by level

);
} else {
rollParts.push(actorData.hp.hd);
}

const data = {
Expand Down Expand Up @@ -397,14 +407,14 @@ export default class OseActor extends Actor {
};

const dmgParts = [];
if (attData.roll.dmg) {
if (attData.roll?.dmg) {
dmgParts.push(attData.roll.dmg);
} else {
dmgParts.push("1d6");
}

// Add Str to damage
if (attData.roll.type === "melee") {
if (attData.roll?.type === "melee") {
dmgParts.push(data.scores.str.mod);
}

Expand Down Expand Up @@ -499,8 +509,10 @@ export default class OseActor extends Actor {
}

/**
* @param {number | string} amount
* @param {1 | -1} multiplier
* Applies damage to an actor
*
* @param {number | string} amount - Amount of damage, negative for healing
* @param {1 | -1} multiplier - Multiplier to damage, negative for healing
* @returns
*/
async applyDamage(amount = 0, multiplier = 1) {
Expand Down