ENH: Party sheet v2

master
U~man 2020-07-14 19:07:00 +02:00
parent a14bd9fd42
commit 853225545d
10 changed files with 202 additions and 162 deletions

View File

@ -47,89 +47,11 @@ export class OseActorSheetCharacter extends OseActorSheet {
);
data.config.ascendingAC = game.settings.get("ose", "ascendingAC");
data.config.individualInit = game.settings.get("ose", "individualInit");
// Compute treasure
let total = 0;
data.owned.items.forEach((item) => {
if (item.data.treasure) {
total += item.data.quantity.value * item.data.cost;
}
});
data.treasure = total;
data.config.encumbrance = game.settings.get("ose", "encumbranceOption");
let basic = data.config.encumbrance == "basic";
// Compute encumbrance
let totalWeight = 0;
Object.values(data.owned).forEach((cat) => {
cat.forEach((item) => {
if (item.type == "item" && (!basic || item.data.treasure)) {
totalWeight += item.data.quantity.value * item.data.weight;
} else if (!basic) {
totalWeight += item.data.weight;
}
});
});
data.encumbrance = {
pct: Math.clamped(
(100 * parseFloat(totalWeight)) / data.data.encumbrance.max,
0,
100
),
max: data.data.encumbrance.max,
encumbered: totalWeight > data.data.encumbrance.max,
value: totalWeight,
};
if (data.data.config.movementAuto) {
this._calculateMovement(data, totalWeight);
}
return data;
}
_calculateMovement(data, weight) {
if (data.config.encumbrance == "disabled") return;
let delta = data.encumbrance.max - 1600;
if (data.config.encumbrance == "detailed") {
if (weight > data.encumbrance.max) {
data.data.movement.base = 0;
} else if (weight > 800 + delta) {
data.data.movement.base = 30;
} else if (weight > 600 + delta) {
data.data.movement.base = 60;
} else if (weight > 400 + delta) {
data.data.movement.base = 90;
} else {
data.data.movement.base = 120;
}
} else if (data.config.encumbrance == "basic") {
let heaviest = 0;
data.owned.armors.forEach((a) => {
if (a.data.equipped) {
if (a.data.type == "light" && heaviest == 0) {
heaviest = 1;
} else if (a.data.type == "heavy") {
heaviest = 2;
}
}
});
switch (heaviest) {
case 0:
data.data.movement.base = 120;
break;
case 1:
data.data.movement.base = 90;
break;
case 2:
data.data.movement.base = 60;
break;
}
if (weight > game.settings.get("ose", "significantTreasure")) {
data.data.movement.base -= 30;
}
}
}
async _chooseLang() {
let choices = CONFIG.OSE.languages;

View File

@ -13,6 +13,8 @@ export class OseActor extends Actor {
this.computeModifiers();
this._isSlow();
this.computeAC();
this.computeEncumbrance();
this.computeTreasure();
// Determine Initiative
if (game.settings.get("ose", "individualInit")) {
@ -24,7 +26,6 @@ export class OseActor extends Actor {
data.initiative.value = 0;
}
data.movement.encounter = data.movement.base / 3;
}
/* -------------------------------------------- */
/* Socket Listeners and Handlers
@ -216,8 +217,8 @@ export class OseActor extends Actor {
data: data,
skipDialog: skip,
speaker: ChatMessage.getSpeaker({ actor: this }),
flavor: game.i18n.format("OSE.roll.attribute", {attribute: label}),
title: game.i18n.format("OSE.roll.attribute", {attribute: label}),
flavor: game.i18n.format("OSE.roll.attribute", { attribute: label }),
title: game.i18n.format("OSE.roll.attribute", { attribute: label }),
});
}
@ -272,8 +273,8 @@ export class OseActor extends Actor {
data: data,
skipDialog: true,
speaker: ChatMessage.getSpeaker({ actor: this }),
flavor: game.i18n.localize('OSE.roll.appearing'),
title: game.i18n.localize('OSE.roll.appearing'),
flavor: game.i18n.localize("OSE.roll.appearing"),
title: game.i18n.localize("OSE.roll.appearing"),
});
}
@ -303,8 +304,8 @@ export class OseActor extends Actor {
data: data,
skipDialog: skip,
speaker: ChatMessage.getSpeaker({ actor: this }),
flavor: game.i18n.format("OSE.roll.exploration", {exploration: label}),
title: game.i18n.format("OSE.roll.exploration", {exploration: label}),
flavor: game.i18n.format("OSE.roll.exploration", { exploration: label }),
title: game.i18n.format("OSE.roll.exploration", { exploration: label }),
});
}
@ -353,7 +354,7 @@ export class OseActor extends Actor {
const data = this.data.data;
const rollParts = ["1d20"];
const dmgParts = [];
let label = game.i18n.format('OSE.roll.attacks', {name: this.data.name})
let label = game.i18n.format("OSE.roll.attacks", { name: this.data.name });
if (
!attData.dmg ||
(!game.settings.get("ose", "variableWeaponDamage") &&
@ -361,7 +362,7 @@ export class OseActor extends Actor {
) {
dmgParts.push("1d6");
} else {
label = game.i18n.format('OSE.roll.attacksWith', {name: attData.label})
label = game.i18n.format("OSE.roll.attacksWith", { name: attData.label });
dmgParts.push(attData.dmg);
}
@ -450,6 +451,101 @@ export class OseActor extends Actor {
});
}
computeEncumbrance() {
if (this.data.type != "character") {
return;
}
const data = this.data.data;
let option = game.settings.get("ose", "encumbranceOption");
let basic = option == "basic";
// Compute encumbrance
let owned = ["weapon", "armor", "item"];
let totalWeight = 0;
Object.values(this.data.items).forEach((item) => {
if (item.type == "item" && (!basic || item.data.treasure)) {
totalWeight += item.data.quantity.value * item.data.weight;
} else if (!basic && owned.includes(item.type)) {
totalWeight += item.data.weight;
}
});
data.encumbrance = {
pct: Math.clamped(
(100 * parseFloat(totalWeight)) / data.encumbrance.max,
0,
100
),
max: data.encumbrance.max,
encumbered: totalWeight > data.encumbrance.max,
value: totalWeight,
};
if (data.config.movementAuto && option != 'disabled') {
this._calculateMovement();
}
}
_calculateMovement() {
const data = this.data.data;
let option = game.settings.get("ose", "encumbranceOption");
let weight = data.encumbrance.value;
let delta = data.encumbrance.max - 1600;
if (option == "detailed") {
if (weight > data.encumbrance.max) {
data.movement.base = 0;
} else if (weight > 800 + delta) {
data.movement.base = 30;
} else if (weight > 600 + delta) {
data.movement.base = 60;
} else if (weight > 400 + delta) {
data.movement.base = 90;
} else {
data.movement.base = 120;
}
} else if (option == "basic") {
const armors = this.data.items.filter(i => i.type == "armor");
let heaviest = 0;
armors.forEach((a) => {
if (a.data.equipped) {
if (a.data.type == "light" && heaviest == 0) {
heaviest = 1;
} else if (a.data.type == "heavy") {
heaviest = 2;
}
}
});
switch (heaviest) {
case 0:
data.movement.base = 120;
break;
case 1:
data.movement.base = 90;
break;
case 2:
data.movement.base = 60;
break;
}
if (weight > game.settings.get("ose", "significantTreasure")) {
data.movement.base -= 30;
}
}
}
computeTreasure() {
if (this.data.type != "character") {
return;
}
const data = this.data.data;
// Compute treasure
let total = 0;
let treasure = this.data.items.filter(i => (i.type == "item" && i.data.treasure))
treasure.forEach((item) => {
total += item.data.quantity.value * item.data.cost;
});
data.treasure = total;
}
computeAC() {
if (this.data.type != "character") {
return;
@ -461,7 +557,7 @@ export class OseActor extends Actor {
const data = this.data.data;
data.aac.naked = baseAc + data.scores.dex.mod;
data.ac.naked = baseAc - data.scores.dex.mod;
const armors = this.data.items.filter(i => i.type == 'armor');
const armors = this.data.items.filter((i) => i.type == "armor");
armors.forEach((a) => {
if (a.data.equipped && a.data.type != "shield") {
baseAc = a.data.ac.value;

View File

@ -4,7 +4,7 @@ export class OsePartySheet extends FormApplication {
(options.classes = ["ose", "dialog", "party-sheet"]),
(options.id = "party-sheet");
options.template = "systems/ose/templates/apps/party-sheet.html";
options.width = 700;
options.width = 280;
return options;
}
@ -25,10 +25,14 @@ export class OsePartySheet extends FormApplication {
* @return {Object}
*/
getData() {
const settings = {
ascending: game.settings.get('ose', 'ascendingAC')
};
let data = {
data: this.object,
config: CONFIG.OSE,
user: game.user
user: game.user,
settings: settings
};
return data;
}
@ -115,5 +119,9 @@ export class OsePartySheet extends FormApplication {
.click(this._selectActors.bind(this));
html.find("button[data-action='deal-xp']").click(this._dealXP.bind(this));
html.find("a.resync").click(() => this.render(true));
html.find(".field-img").click((ev) => {
let actorId = ev.currentTarget.parentElement.dataset.actorId;
game.actors.get(actorId).sheet.render(true);
})
}
}

View File

@ -1,7 +1,7 @@
import { OsePartySheet } from "./dialog/party-sheet.js";
export const addControl = (object, html) => {
let control = `<a class='ose-party-sheet' title='${game.i18n.localize('OSE.dialog.partysheet')}'><i class='fas fa-users'></i></a>`;
let control = `<button class='ose-party-sheet' type="button" title='${game.i18n.localize('OSE.dialog.partysheet')}'><i class='fas fa-users'></i></button>`;
html.find(".fas.fa-search").replaceWith($(control))
html.find('.ose-party-sheet').click(ev => {
showPartySheet(object);
@ -11,7 +11,17 @@ export const addControl = (object, html) => {
export const showPartySheet = (object) => {
event.preventDefault();
new OsePartySheet(object, {
top: window.screen.height / 2,
left:window.screen.width / 2,
top: window.screen.height / 2 - 180,
left:window.screen.width / 2 - 140,
}).render(true);
}
export const update = (actor, data) => {
if (actor.getFlag('ose', 'party')) {
Object.values(ui.windows).forEach(w => {
if (w instanceof OsePartySheet) {
w.render(true);
}
})
}
}

View File

@ -49,6 +49,7 @@ export const registerSettings = function () {
basic: "OSE.Setting.EncumbranceBasic",
detailed: "OSE.Setting.EncumbranceDetailed",
},
onChange: _ => window.location.reload()
});
game.settings.register("ose", "significantTreasure", {
@ -58,5 +59,6 @@ export const registerSettings = function () {
scope: "world",
type: Number,
config: true,
onChange: _ => window.location.reload()
});
};

View File

@ -124,5 +124,5 @@ Hooks.on("preUpdateCombat", async (combat, data, diff, id) => {
Hooks.on("renderChatLog", (app, html, data) => OseItem.chatListeners(html));
Hooks.on("getChatLogEntryContext", chat.addChatMessageContextOptions);
Hooks.on("renderChatMessage", chat.addChatMessageButtons);
Hooks.on("renderRollTableConfig", treasure.augmentTable);
Hooks.on("renderRollTableConfig", treasure.augmentTable);
Hooks.on("updateActor", party.update);

View File

@ -8,7 +8,7 @@
.ose.dialog.party-sheet {
.window-content {
padding: 0;
height: 200px;
height: 400px;
}
.header {
color: whitesmoke;
@ -19,24 +19,25 @@
.actor-list {
margin: 0;
overflow: auto;
height: 180px;
height: 350px;
list-style: none;
padding: 0;
.actor {
&:nth-child(even) {
background-color: rgba(0, 0, 0, 0.1);
}
padding: 2px;
.fas {
padding: 0 2px;
}
padding: 4px;
font-size: 12px;
height: 35px;
text-align: center;
line-height: 35px;
.field-img {
flex: 0 0 32px;
flex: 0 0 50px;
img {
border: none;
width: 32px;
height: 32px;
width: 50px;
height: 50px;
}
}
}
@ -45,24 +46,16 @@
text-align: left;
text-indent: 10px;
}
.field-short {
flex: 0 0 45px;
}
.field-long {
flex: 0 0 80px;
}
.field-longer {
flex: 0 0 180px;
}
}
#sidebar #actors .directory-header .header-search {
.ose-party-sheet {
width: 30px;
width: 32px;
text-align: center;
line-height: 20px;
}
input {
width: calc(100% - 30px);
width: calc(100% - 45px);
}
}

View File

@ -36,7 +36,7 @@
.modifiers-btn {
position: absolute;
left: 0;
top: -5px;
top: -8px;
}
}
}

View File

@ -146,7 +146,7 @@
<li class="item-titles flexrow">
<div class="item-caret"><i class="fas fa-caret-down"></i></div>
<div class="item-name">{{localize "OSE.items.Treasure"}}</div>
<div class="field-long">{{treasure}} <i class="fas fa-circle"></i></div>
<div class="field-long">{{data.treasure}} <i class="fas fa-circle"></i></div>
<div class="field-short"><i class="fas fa-hashtag"></i></div>
<div class="field-short"><i class="fas fa-weight-hanging"></i></div>
<div class="item-controls">
@ -189,7 +189,7 @@
</div>
</section>
<section>
{{#with encumbrance}}
{{#with data.encumbrance}}
<div class="encumbrance {{#if encumbered}}encumbered{{/if}}">
<span class="encumbrance-bar" style="width:{{pct}}%"></span>
<span class="encumbrance-label">{{value}} / {{max}}</span>

View File

@ -1,62 +1,71 @@
<form autocomplete="off">
<header class="flexrow">
{{#if user.isGM}}
<button data-action="select-actors" type="button">{{localize "OSE.dialog.selectActors"}}</button>
<button data-action="deal-xp" type="button">{{localize "OSE.dialog.dealXP"}}</button>
<button data-action="select-actors" type="button">
{{localize "OSE.dialog.selectActors"}}
</button>
<button data-action="deal-xp" type="button">
{{localize "OSE.dialog.dealXP"}}
</button>
{{/if}}
</header>
<div class="actor header flexrow">
<div class="field-name">
<a class="resync"><i class="fas fa-sync"></i></a>
</div>
<div class="field-long">
{{localize 'OSE.Health'}}
</div>
<div class="field-short">
{{localize 'OSE.ArmorClassShort'}}
</div>
<div class="field-short">
{{localize 'OSE.Thac0'}}
</div>
<div class="field-short">
{{localize 'OSE.movement.encounter.short'}}
</div>
<div class="field-longer">
{{localize 'OSE.category.saves'}}
<a class="resync"><i class="fas fa-sync"></i></a>
</div>
</div>
<ol class="actor-list">
{{#each data.entities as |e|}}
{{#if e.data.flags.ose.party}}
{{#each data.entities as |e|}} {{#if e.data.flags.ose.party}}
<li class="actor flexrow" data-actor-id="{{e.id}}">
<div class="field-img">
<img src="{{e.img}}"/>
<a><img src="{{e.img}}" /></a>
</div>
<div class="field-name">
{{e.name}}
</div>
<div class="item-controls field-long">
<div class="item-control reaction-roll" title="Reaction Roll"><a><i class="fas fa-user"></i></a></div>
</div>
<div class="field-long">
{{e.data.data.hp.value}}/{{e.data.data.hp.max}}
</div>
<div class="field-short">
<strong>{{e.data.data.ac.value}}</strong> <sub>{{e.data.data.ac.naked}}</sub>
</div>
<div class="field-short">
<sub>{{e.data.data.thac0.mod.melee}}</sub> <strong>{{e.data.data.thac0.value}}</strong> <sub>{{e.data.data.thac0.mod.missile}}</sub>
</div>
<div class="field-short">
{{e.data.data.movement.encounter}}
</div>
<div class="field-longer flexrow">
{{#each e.data.data.saves as |s i|}}
<span>{{lookup @root.config.saves_short i}} {{s.value}}</span>
{{/each}}
<div>
<div class="flexrow">
<div class="field-name flex2">
<strong>{{e.name}}</strong>
</div>
<div class="field-long">
<i class="fas fa-heart"></i>
{{e.data.data.hp.value}}/{{e.data.data.hp.max}}
</div>
<div class="field-short">
<i class="fas fa-shield-alt"></i>
{{#if @root.settings.ascending}}<strong>{{e.data.data.aac.value}}</strong> <sub>{{e.data.data.aac.naked}}</sub>
{{else}}<strong>{{e.data.data.ac.value}}</strong> <sub>{{e.data.data.ac.naked}}</sub>
{{/if}}
</div>
</div>
<div class="flexrow">
<div class="field-short">
<i class="fas fa-fist-raised"></i>
<sub>{{e.data.data.thac0.mod.melee}}</sub> <strong>{{e.data.data.thac0.value}}</strong> <sub>{{e.data.data.thac0.mod.missile}}</sub>
</div>
<div class="field-short">
<i class="fas fa-shoe-prints"></i>
{{e.data.data.movement.encounter}}
</div>
{{#if (eq e.data.type 'character')}}
<div class="field-short">
<i class="fas fa-weight-hanging"></i>
{{e.data.data.encumbrance.value}}
</div>
<div class="field-short">
<i class="fas fa-circle"></i>
{{e.data.data.treasure}}
</div>
{{/if}}
</div>
<div class="flexrow">
<div class="field-longer flexrow">
{{#each e.data.data.saves as |s i|}}
<span>{{lookup @root.config.saves_short i}} {{s.value}}</span>
{{/each}}
{{#if (eq e.data.type 'character')}}<i class="fas fa-magic"></i>{{mod e.data.data.scores.wis.mod}}{{/if}}
</div>
</div>
</div>
</li>
{{/if}}
{{/each}}
{{/if}} {{/each}}
</ol>
</form>