ENH: Initiative, shield, generation

master
U~man 2020-07-20 22:40:07 +02:00
parent 0745092b31
commit 63567b45b1
15 changed files with 172 additions and 92 deletions

View File

@ -17,7 +17,8 @@
"OSE.dialog.dealXP": "Deal XP",
"OSE.dialog.generator": "Character generator",
"OSE.dialog.generateSaves": "Generate Saves",
"OSE.dialog.generateScore": "Rolling {score}",
"OSE.dialog.generateScores": "Generate Scores",
"OSE.dialog.generateScore": "Rolling {score} ({count})",
"OSE.Formula": "Formula",
"OSE.SitMod": "Situational Modifier",
@ -158,8 +159,11 @@
"OSE.category.description": "Description",
"OSE.category.equipment": "Equipment",
"OSE.Setting.IndividualInit": "Individual Initiative",
"OSE.Setting.IndividualInitHint": "Initiative is rolled for each actor and modified by its DEX score",
"OSE.Setting.Initiative": "Initiative",
"OSE.Setting.InitiativeHint": "Grouped or individual initiative. Unique individual is only rolled at the start of the combat",
"OSE.Setting.InitiativeOnce": "Unique individual Initiative",
"OSE.Setting.InitiativeReroll": "Individual Initiative per Round",
"OSE.Setting.InitiativeGroup": "Grouped Initiative",
"OSE.Setting.AscendingAC": "Ascending Armor Class",
"OSE.Setting.AscendingACHint": "The more the better",
"OSE.Setting.Morale": "Enable monsters Morale Rating",
@ -248,6 +252,7 @@
"OSE.messages.InflictsDamage": "Inflicts damage!",
"OSE.messages.applyDamage": "Apply Damage",
"OSE.messages.applyHealing": "Apply Healing",
"OSE.messages.creationFinished": "Character {name} creation finished",
"OSE.colors.green": "Green",
"OSE.colors.red": "Red",

View File

@ -17,7 +17,8 @@
"OSE.dialog.dealXP": "Donner XP",
"OSE.dialog.generator": "Générateur de personnage",
"OSE.dialog.generateSaves": "Générer les Sauvegardes",
"OSE.dialog.generateScore": "Création: {score}",
"OSE.dialog.generateScores": "Générer les Scores",
"OSE.dialog.generateScore": "Création: {score} ({count})",
"OSE.Formula": "Formule",
"OSE.SitMod": "Mod. de situation",
@ -52,6 +53,7 @@
"OSE.details.experience.base": "Expérience",
"OSE.details.experience.bonus": "Ajustement d'XP",
"OSE.details.experience.next": "Prochain Niveau",
"OSE.details.experience.share": "Part d'Expérience",
"OSE.details.experience.award": "XP de Récompense",
"OSE.details.treasure": "Type de Trésor",
"OSE.details.treasureTable": "Table",
@ -157,8 +159,11 @@
"OSE.category.description": "Descriptions",
"OSE.category.equipment": "Équipement",
"OSE.Setting.IndividualInit": "Initiative Individuelle",
"OSE.Setting.IndividualInitHint": "L'Initiative est lancée pour chaque acteur, modifié par son score de DEX",
"OSE.Setting.Initiative": "Initiative",
"OSE.Setting.InitiativeHint": "Initiative groupée ou individuelle. L'initiative unique est tirée une seule fois en début de combat.",
"OSE.Setting.InitiativeOnce": "Initiative unique individuelle",
"OSE.Setting.InitiativeReroll": "Initiative individuelle",
"OSE.Setting.InitiativeGroup": "Initiative groupée",
"OSE.Setting.AscendingAC": "Classe d'Armure Ascendante",
"OSE.Setting.AscendingACHint": "Le plus est le mieux",
"OSE.Setting.Morale": "Activer le Score de Moral",
@ -241,12 +246,13 @@
"OSE.exploration.ft.abrev": "DP",
"OSE.messages.GetExperience": "{name} a gagné {value} points d'expérience !",
"OSE.messages.InflictsDamage": "Inflige des dégâts !",
"OSE.messages.applyDamage": "Appliquer les dégâts",
"OSE.messages.applyHealing": "Appliquer les soins",
"OSE.messages.AttackSuccess": "<b>Touche une CA de {result}!</b> ({bonus})",
"OSE.messages.AttackAscendingSuccess": "<b>Touche une CAA de {result}!</b>",
"OSE.messages.AttackFailure": "<b>L'Attaque échoue</b> ({bonus})",
"OSE.messages.InflictsDamage": "Inflige des dégâts !",
"OSE.messages.applyDamage": "Appliquer les dégâts",
"OSE.messages.applyHealing": "Appliquer les soins",
"OSE.messages.creationFinished": "Création de {name} terminée",
"OSE.colors.green": "Vert",
"OSE.colors.red": "Rouge",

View File

@ -34,20 +34,12 @@ export class OseActorSheetCharacter extends OseActorSheet {
});
}
/**
* Character creation helpers
* @param {...any} args
*/
async _render(...args) {
super._render(...args).then(() => {
if (this.actor.isNew()) {
generateScores() {
new OseCharacterCreator(this.actor, {
top: this.position.top + 40,
left: this.position.left + (this.position.width - 400) / 2,
}).render(true);
}
});
}
/**
* Prepare data for rendering the Actor sheet
@ -57,9 +49,10 @@ export class OseActorSheetCharacter extends OseActorSheet {
const data = super.getData();
data.config.ascendingAC = game.settings.get("ose", "ascendingAC");
data.config.individualInit = game.settings.get("ose", "individualInit");
data.config.initiative = game.settings.get("ose", "initiative") != "group";
data.config.encumbrance = game.settings.get("ose", "encumbranceOption");
data.isNew = this.actor.isNew();
return data;
}
@ -249,6 +242,10 @@ export class OseActorSheetCharacter extends OseActorSheet {
this._onShowModifiers(ev);
});
html.find("a[data-action='generate-scores']").click((ev) => {
this.generateScores(ev);
});
// Handle default listeners last so system listeners are triggered first
super.activateListeners(html);
}

View File

@ -17,7 +17,7 @@ export class OseActor extends Actor {
this.computeTreasure();
// Determine Initiative
if (game.settings.get("ose", "individualInit")) {
if (game.settings.get("ose", "initiative") != "group") {
data.initiative.value = data.initiative.mod;
if (this.data.type == "character") {
data.initiative.value += data.scores.dex.mod;
@ -594,7 +594,8 @@ export class OseActor extends Actor {
// Compute AC
let baseAc = 9;
let baseAac = 10;
let shield = 0;
let AcShield = 0;
let AacShield = 0;
const data = this.data.data;
data.aac.naked = baseAc + data.scores.dex.mod;
data.ac.naked = baseAc - data.scores.dex.mod;
@ -604,12 +605,14 @@ export class OseActor extends Actor {
baseAc = a.data.ac.value;
baseAac = a.data.aac.value;
} else if (a.data.equipped && a.data.type == "shield") {
shield = a.data.ac.value;
AcShield = a.data.ac.value;
AacShield = a.data.aac.value;
}
});
data.aac.value = baseAac + data.scores.dex.mod + shield + data.aac.mod;
data.ac.value = baseAc - data.scores.dex.mod - shield - data.ac.mod;
data.shield = shield;
data.aac.value = baseAac + data.scores.dex.mod + AacShield + data.aac.mod;
data.ac.value = baseAc - data.scores.dex.mod - AcShield - data.ac.mod;
data.ac.shield = AcShield;
data.aac.shield = AacShield;
}
computeModifiers() {

View File

@ -34,35 +34,38 @@ export class OseActorSheetMonster extends OseActorSheet {
/**
* Monster creation helpers
* @param {...any} args
*/
async _render(...args) {
super._render(...args).then(() => {
if (this.actor.isNew()) {
const template = `
<form>
<div class="form-group">
<label>Hit dice count</label>
<input name="total" placeholder="Hit Dice" type="text"/>
</div>
</form>`;
async generateSave() {
let choices = CONFIG.OSE.monster_saves;
let templateData = { choices: choices },
dlg = await renderTemplate(
"/systems/ose/templates/actors/dialogs/monster-saves.html",
templateData
);
//Create Dialog window
new Dialog({
title: game.i18n.localize("OSE.dialog.generateSaves"),
content: template,
content: dlg,
buttons: {
set: {
icon: '<i class="fas fa-dice"></i>',
label: game.i18n.localize("OSE.dialog.generateSaves"),
ok: {
label: game.i18n.localize("OSE.Ok"),
icon: '<i class="fas fa-check"></i>',
callback: (html) => {
let hd = html.find('input[name="total"]').val();
let hd = html.find('select[name="choice"]').val();
this.actor.generateSave(hd);
},
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: game.i18n.localize("OSE.Cancel"),
},
},
default: "ok",
}, {
width: 250
}).render(true);
}
});
}
/**
* Prepare data for rendering the Actor sheet
@ -74,6 +77,7 @@ export class OseActorSheetMonster extends OseActorSheet {
// Settings
data.config.morale = game.settings.get("ose", "morale");
data.data.details.treasure.link = TextEditor.enrichHTML(data.data.details.treasure.table);
data.isNew = this.actor.isNew();
return data;
}
@ -95,7 +99,7 @@ export class OseActorSheetMonster extends OseActorSheet {
} else {
link = `@RollTable[${data.id}]`;
}
this.actor.update({"data.details.treasure.table": link});
this.actor.update({ "data.details.treasure.table": link });
}
/* -------------------------------------------- */
@ -264,6 +268,7 @@ export class OseActorSheetMonster extends OseActorSheet {
})
});
html.find('button[data-action="generate-saves"]').click(() => this.generateSave());
// Handle default listeners last so system listeners are triggered first
super.activateListeners(html);
}

View File

@ -80,8 +80,8 @@ export class OseCombat {
? '<i class="fas fa-dizzy"></i>'
: span.innerHTML;
});
let init = game.settings.get("ose", "individualInit");
if (init) {
let init = game.settings.get("ose", "initiative") == "group";
if (!init) {
return;
}
@ -112,13 +112,13 @@ export class OseCombat {
}
static updateCombatant(combat, combatant, data) {
let init = game.settings.get("ose", "individualInit");
let init = game.settings.get("ose", "initiative");
// Why do you reroll ?
if (combatant.actor.data.data.isSlow) {
data.initiative = -789;
return;
}
if (data.initiative && !init) {
if (data.initiative && init == "group") {
let groupInit = data.initiative;
// Check if there are any members of the group with init
combat.combatants.forEach((ct) => {

View File

@ -90,6 +90,7 @@ export const OSE = {
},
monster_saves: {
0: {
label: "Normal Human",
d: 14,
w: 15,
p: 16,
@ -97,6 +98,7 @@ export const OSE = {
s: 18
},
1: {
label: "1-3",
d: 12,
w: 13,
p: 14,
@ -104,6 +106,7 @@ export const OSE = {
s: 16
},
4: {
label: "4-6",
d: 10,
w: 11,
p: 12,
@ -111,6 +114,7 @@ export const OSE = {
s: 14
},
7: {
label: "7-9",
d: 8,
w: 9,
p: 10,
@ -118,6 +122,7 @@ export const OSE = {
s: 12
},
10: {
label: "10-12",
d: 6,
w: 7,
p: 8,
@ -125,6 +130,7 @@ export const OSE = {
s: 10
},
13: {
label: "13-15",
d: 4,
w: 5,
p: 6,
@ -132,6 +138,7 @@ export const OSE = {
s: 8
},
16: {
label: "16-18",
d: 2,
w: 3,
p: 4,
@ -139,6 +146,7 @@ export const OSE = {
s: 6
},
19: {
label: "19-21",
d: 2,
w: 2,
p: 2,
@ -146,6 +154,7 @@ export const OSE = {
s: 4
},
22: {
label: "22+",
d: 2,
w: 2,
p: 2,

View File

@ -32,6 +32,15 @@ export class OseCharacterCreator extends FormApplication {
let data = this.object.data;
data.user = game.user;
data.config = CONFIG.OSE;
data.counters = {
str: 0,
wis: 0,
dex: 0,
int: 0,
cha: 0,
con: 0,
gold: 0
}
return data;
}
@ -61,7 +70,10 @@ export class OseCharacterCreator extends FormApplication {
}
rollScore(score, options={}) {
const label = game.i18n.localize(`OSE.scores.${score}.long`);
// Increase counter
this.object.data.counters[score]++;
const label = score != "gold" ? game.i18n.localize(`OSE.scores.${score}.long`) : "Gold";
const rollParts = ["3d6"];
const data = {
...this.object.data,
@ -78,8 +90,17 @@ export class OseCharacterCreator extends FormApplication {
data: data,
skipDialog: true,
speaker: ChatMessage.getSpeaker({ actor: this }),
flavor: game.i18n.format('OSE.dialog.generateScore', {score: label}),
title: game.i18n.format('OSE.dialog.generateScore', {score: label}),
flavor: game.i18n.format('OSE.dialog.generateScore', {score: label, count: this.object.data.counters[score]}),
title: game.i18n.format('OSE.dialog.generateScore', {score: label, count: this.object.data.counters[score]}),
});
}
async close() {
super.close();
const speaker = ChatMessage.getSpeaker({ actor: this });
ChatMessage.create({
content: game.i18n.format("OSE.messages.creationFinished", {name: this.object.name}),
speaker,
});
}
@ -96,7 +117,7 @@ export class OseCharacterCreator extends FormApplication {
html.find('a.gold-roll').click((ev) => {
let el = ev.currentTarget.parentElement.parentElement.parentElement;
this.rollScore("Gold", {event: ev}).then(r => {
this.rollScore("gold", {event: ev}).then(r => {
$(el).find('.gold-value').val(r.total * 10);
});
});

View File

@ -1,11 +1,17 @@
export const registerSettings = function () {
game.settings.register("ose", "individualInit", {
name: game.i18n.localize("OSE.Setting.IndividualInit"),
hint: game.i18n.localize("OSE.Setting.IndividualInitHint"),
default: false,
game.settings.register("ose", "initiative", {
name: game.i18n.localize("OSE.Setting.Initiative"),
hint: game.i18n.localize("OSE.Setting.InitiativeHint"),
default: "group",
scope: "world",
type: Boolean,
type: String,
config: true,
choices: {
disabled: "OSE.Setting.InitiativeOnce",
rerolled: "OSE.Setting.InitiativeReroll",
group: "OSE.Setting.InitiativeGroup",
},
onChange: _ => window.location.reload()
});

View File

@ -109,8 +109,8 @@ Hooks.on("renderSidebarTab", async (object, html) => {
});
Hooks.on("preCreateCombatant", (combat, data, options, id) => {
let init = game.settings.get("ose", "individualInit");
if (!init) {
let init = game.settings.get("ose", "initiative");
if (init == "group") {
OseCombat.addCombatant(combat, data, options, id);
}
});
@ -124,13 +124,13 @@ Hooks.on("renderCombatTracker", (object, html, data) => {
});
Hooks.on("preUpdateCombat", async (combat, data, diff, id) => {
let init = game.settings.get("ose", "individualInit");
let init = game.settings.get("ose", "initiative");
if (!data.round) {
return;
}
if (!init) {
if (init == "group") {
OseCombat.rollInitiative(combat, data, diff, id);
} else {
} else if (init == "rerolled") {
OseCombat.individualInitiative(combat, data, diff, id);
}
});

View File

@ -1,8 +1,20 @@
@import "./variables.scss";
@keyframes notify {
from {
text-shadow: none;
}
to {
text-shadow: -1px -1px 4px $colorOlive, 1px -1px 4px $colorOlive, -1px 1px 4px $colorOlive, 1px 1px 4px $colorOlive;
}
}
.ose.sheet.actor {
$detailsHeight: 44px;
.blinking {
font-weight: bold;
animation: 0.8s ease-in 1s infinite alternate notify;
}
.panel {
border: 1px solid $colorDark;
.panel-title {
@ -66,14 +78,6 @@
bottom: 0;
left: 12px;
}
@keyframes notify {
from {
text-shadow: none;
}
to {
text-shadow: -1px -1px 4px $colorOlive, 1px -1px 4px $colorOlive, -1px 1px 4px $colorOlive, 1px 1px 4px $colorOlive;
}
}
&.notify {
input {
font-weight: bold;

View File

@ -161,7 +161,7 @@
}
}
}
@keyframes notify {
@keyframes activated {
from {
background: none;
}
@ -171,7 +171,7 @@
}
.results {
.table-result.active {
animation: 0.7s infinite alternate notify;
animation: 0.7s infinite alternate activated;
}
}
}

View File

@ -0,0 +1,14 @@
<form class="ose dialog">
<div class="form-group">
<label>{{localize 'OSE.HitDice'}}</label>
<div class="form-fields">
<select name="choice">
{{#select choices}}
{{#each choices as |saves mode|}}
<option value="{{mode}}">{{saves.label}}</option>
{{/each}}
{{/select}}
</select>
</div>
</div>
</div>

View File

@ -2,7 +2,11 @@
{{!-- Scores --}}
<div class="attribute-group">
<div class="modifiers-btn">
{{#unless isNew}}
<a data-action="modifiers" title="{{localize 'OSE.Modifiers'}}"><i class="fas fa-book"></i></a>
{{else}}
<a data-action="generate-scores" title="{{localize 'OSE.dialog.generateScores'}}"><i class="fas fa-dice blinking"></i></a>
{{/unless}}
</div>
<ul class="attributes">
<li class="attribute ability-score" data-score="str">
@ -82,13 +86,15 @@
<div class="health-value health-top" title="{{localize 'OSE.ArmorClass'}}">{{data.aac.value}}</div>
<div class="health-value health-bottom" title="{{localize 'OSE.ArmorClassNaked'}}">
{{data.aac.naked}}</div>
{{#if data.aac.shield}}<div class="shield" title="{{localize 'OSE.items.hasShield'}} ({{data.aac.shield}})"><i
class="fas fa-shield-alt"></i></div>{{/if}}
{{else}}
<div class="health-value health-top" title="{{localize 'OSE.ArmorClass'}}">{{data.ac.value}}</div>
<div class="health-value health-bottom" title="{{localize 'OSE.ArmorClassNaked'}}">
{{data.ac.naked}}</div>
{{/if}}
{{#if data.shield}}<div class="shield" title="{{localize 'OSE.items.hasShield'}} ({{data.shield}})"><i
{{#if data.ac.shield}}<div class="shield" title="{{localize 'OSE.items.hasShield'}} ({{data.ac.shield}})"><i
class="fas fa-shield-alt"></i></div>{{/if}}
{{/if}}
</div>
</div>
<div class="flexrow">
@ -102,7 +108,7 @@
data-dtype="String" />
</div>
</li>
{{#if config.individualInit}}
{{#if config.initiative}}
<li class="attribute">
<h4 class="attribute-name box-title" title="{{ localize 'OSE.Initiative' }}">
{{ localize "OSE.InitiativeShort" }}</h4>

View File

@ -155,8 +155,12 @@
<div class="attribute-group">
<ul class="attributes">
<li class="attacks-description">
{{#unless isNew}}
<label>{{ localize "OSE.movement.details" }}</label>
<input name="data.movement.value" type="text" value="{{data.movement.value}}" data-dtype="String" />
{{else}}
<button data-action="generate-saves">{{localize "OSE.dialog.generateSaves"}}</button>
{{/unless}}
</li>
<li class="attribute saving-throw" data-save="death">
<h4 class="attribute-name box-title">