Skip to content

Commit

Permalink
feat: Improve compatibility with CE and Clement Sector rules (#1534)
Browse files Browse the repository at this point in the history
This release includes a few improvements:
* Cepheus Engine encumbrance modifiers now follow medium and heavy rules.  Up to the "Fraction of maximum encumbrance..." setting, the modifier is zero.  Between that setting and two times that setting, the DM is the roll modifier setting for encumbered.  Above two times the maximum fraction setting, the modifier is two times the DM for encumbered. Thanks to @RealDuce suggestion.  Default settings for CE rules have been updated to reflect this change.

* New display -> ship setting that lets one customize the jump drive abbreviation on the ship sheet.  E.G., you can now make this "Z-drive" to better fit the Clement Sector. Thanks to @RealDuce suggestion.

* Fix some skill group issues.  A) Skill groups now listed alphabetically.  B) No longer force title case with skill group names. Thanks to the suggestions of @Amantiado and @Zee

* ES localization improvements thanks to @ForjaSalvaje
  • Loading branch information
marvin9257 authored Jan 18, 2024
1 parent e807917 commit 94668b8
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 52 deletions.
16 changes: 8 additions & 8 deletions src/module/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const RULESETS = Object.freeze({
displayReactionMorale: true,
showComponentRating: true,
showComponentDM: true,
encumbranceFraction: "0.33",
encumbranceFraction: "0.1667",
encumbranceModifier: -1,
useDegreesOfSuccess: 'CE',
targetDMList: "Aiming +1, Cover (half) -1, Cover (three quarter) -2, Cover (full) -4, Movement -1, Dodges -1, Prone (ranged) -2, Prone (melee) +2, Recoil in Zero G -2"
Expand Down Expand Up @@ -114,7 +114,7 @@ const RULESETS = Object.freeze({
displayReactionMorale: false,
showComponentRating: true,
showComponentDM: true,
encumbranceFraction: "0.33",
encumbranceFraction: "0.334",
encumbranceModifier: -1,
useDegreesOfSuccess: 'none',
targetDMList: "Obscured -1, Cover (hard) -2, Cover (heavy) -3, Cover (total) -4, Running -1, Prone (ranged) -2, Darkness -2, Dim Light -1, Shield -1, Overwatch w/Shield -2"
Expand Down Expand Up @@ -155,7 +155,7 @@ const RULESETS = Object.freeze({
displayReactionMorale: false,
showComponentRating: false,
showComponentDM: false,
encumbranceFraction: "0.33",
encumbranceFraction: "0.334",
encumbranceModifier: 0,
useDegreesOfSuccess: 'none',
targetDMList: "Obscured -1, Cover (hard) -2, Cover (heavy) -3, Cover (total) -4, Running -1, Prone (ranged) -2, Darkness -2, Dim Light -1, Shield -1, Overwatch w/Shield -2"
Expand Down Expand Up @@ -278,7 +278,7 @@ const RULESETS = Object.freeze({
displayReactionMorale: false,
showComponentRating: false,
showComponentDM: false,
encumbranceFraction: "0.33",
encumbranceFraction: "0.334",
encumbranceModifier: 0,
useDegreesOfSuccess: 'none',
targetDMList: ""
Expand Down Expand Up @@ -327,7 +327,7 @@ const RULESETS = Object.freeze({
displayReactionMorale: true,
showComponentRating: true,
showComponentDM: true,
encumbranceFraction: "0.33",
encumbranceFraction: "0.334",
encumbranceModifier: -2,
useDegreesOfSuccess: 'none',
targetDMList: "Obscured -1, Cover (hard) -2, Cover (heavy) -3, Cover (total) -4, Running -1, Prone (ranged) -2, Darkness -2, Dim Light -1, Shield -1, Overwatch w/Shield -2"
Expand Down Expand Up @@ -376,7 +376,7 @@ const RULESETS = Object.freeze({
displayReactionMorale: true,
showComponentRating: true,
showComponentDM: true,
encumbranceFraction: "0.33",
encumbranceFraction: "0.334",
encumbranceModifier: -2,
useDegreesOfSuccess: 'none',
targetDMList: "Obscured -1, Cover (hard) -2, Cover (heavy) -3, Cover (total) -4, Running -1, Prone (ranged) -2, Darkness -2, Dim Light -1, Shield -1, Overwatch w/Shield -2"
Expand Down Expand Up @@ -425,7 +425,7 @@ const RULESETS = Object.freeze({
displayReactionMorale: true,
showComponentRating: true,
showComponentDM: true,
encumbranceFraction: "0.33",
encumbranceFraction: "0.334",
encumbranceModifier: -2,
useDegreesOfSuccess: 'none',
targetDMList: "Obscured -1, Cover (hard) -2, Cover (heavy) -3, Cover (total) -4, Running -1, Prone (ranged) -2, Darkness -2, Dim Light -1, Shield -1, Overwatch w/Shield -2"
Expand Down Expand Up @@ -475,7 +475,7 @@ const RULESETS = Object.freeze({
displayReactionMorale: true,
showComponentRating: false,
showComponentDM: false,
encumbranceFraction: "0.33",
encumbranceFraction: "0.334",
encumbranceModifier: -1,
useDegreesOfSuccess: 'none',
targetDMList: "Obscured -1, Cover (good) -2, Cover (heavy) -3, Cover (total) -4, Running -1, Prone (ranged) -2, Darkness -2, Dim Light -1, Shield -1"
Expand Down
10 changes: 7 additions & 3 deletions src/module/entities/TwodsixActor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,15 +361,19 @@ export default class TwodsixActor extends Actor {
calcShipStats.power.max += powerForItem;
} else {
switch (anComponent.subtype) {
case 'drive':
if (item.name?.toLowerCase().includes('j-drive') || item.name?.toLowerCase().includes('j drive')) {
case 'drive': {
const componentName = item.name?.toLowerCase() ?? "";
const jDriveLabel = (game.i18n.localize(game.settings.get('twodsix', 'jDriveLabel'))).toLowerCase();
const mDriveLabel = game.i18n.localize("TWODSIX.Ship.MDrive").toLowerCase();
if (componentName.includes('j-drive') || componentName.includes('j drive') || componentName.includes(jDriveLabel)) {
calcShipStats.power.jDrive += powerForItem;
} else if (item.name?.toLowerCase().includes('m-drive') || item.name?.toLowerCase().includes('m drive')) {
} else if (componentName.includes('m-drive') || componentName.includes('m drive') || componentName.includes(mDriveLabel)) {
calcShipStats.power.mDrive += powerForItem;
} else {
calcShipStats.power.systems += powerForItem;
}
break;
}
case 'sensor':
calcShipStats.power.sensors += powerForItem;
break;
Expand Down
120 changes: 84 additions & 36 deletions src/module/hooks/showStatusIcons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,6 @@ function checkForDamageStat (update: any, actorType: string): boolean {
return false;
}

/*function checkForEncumbered(systemUpdates: Record<string, any>): boolean {
if (systemUpdates !== undefined) {
if (systemUpdates.equipped) {
return true;
}
}
return false;
}*/

export const DAMAGECOLORS = Object.freeze({
minorWoundTint: '#FFFF00', // Yellow
seriousWoundTint: '#FF0000', // Red
Expand All @@ -95,6 +86,11 @@ export const effectType = Object.freeze({
encumbered: 'EFFECT.StatusEncumbered'
});

/**
* Determine whether wounded effect applies to actor. Update encumbered AE & tint, if necessary.
* @param {TwodsixActor} selectedActor The actor to check
* @public
*/
async function applyWoundedEffect(selectedActor: TwodsixActor): Promise<void> {
const tintToApply = getIconTint(selectedActor);
const oldWoundState = selectedActor.effects.find(eff => eff.statuses.has("wounded"));
Expand Down Expand Up @@ -130,54 +126,69 @@ async function applyWoundedEffect(selectedActor: TwodsixActor): Promise<void> {
}
}

/**
* Determine whether encumbered effect applies to actor. Update encumbered AE, if necessary.
* @param {TwodsixActor} selectedActor The actor to check
* @public
*/
export async function applyEncumberedEffect(selectedActor: TwodsixActor): Promise<void> {

//Clean-up a localization issue
/*if (game.i18n.localize(effectType.encumbered) !== "Encumbered") {
const oldEncumbered = await selectedActor.effects.filter(eff => eff.statuses.has("encumbered"));
if (oldEncumbered.length > 0) {
const oldIdList = await oldEncumbered.map(i => i.id);
await selectedActor.deleteEmbeddedDocuments("ActiveEffect", oldIdList);
}
}*/

const isCurrentlyEncumbered = await selectedActor.effects.filter(eff => eff.statuses.has('encumbered'));

let state = false;
let ratio = 0;
let idToKeep = "";
const maxEncumbrance = selectedActor.system.encumbrance.max; //selectedActor.getMaxEncumbrance()

//Determined whether encumbered
if (maxEncumbrance === 0 && selectedActor.system.encumbrance.value > 0) {
state = true;
ratio = 1;
} else if (maxEncumbrance > 0) {
const ratio = /*selectedActor.getActorEncumbrance()*/ selectedActor.system.encumbrance.value / maxEncumbrance;
state = (ratio > parseFloat(await game.settings.get('twodsix', 'encumbranceFraction')));
ratio = /*selectedActor.getActorEncumbrance()*/ selectedActor.system.encumbrance.value / maxEncumbrance;
state = (ratio > parseFloat(game.settings.get('twodsix', 'encumbranceFraction'))); //remove await
}

// Delete encumbered AE's if uneeded or more than one
if (isCurrentlyEncumbered.length > 0) {
const idList = await isCurrentlyEncumbered.map(i => i.id);
const idList = isCurrentlyEncumbered.map(i => i.id); //remove await???
if (state === true) {
await idList.pop();
idToKeep = idList.pop();
}
if(idList.length > 0) {
await selectedActor.deleteEmbeddedDocuments("ActiveEffect", idList);
}
} else if (state === true && isCurrentlyEncumbered.length === 0) {
const modifier = game.settings.get('twodsix', 'encumbranceModifier');
}

//Define AE if actor is encumbered
if (state === true) {
const modifier = getEncumbranceModifier(ratio);
const changeData = [{
key: "system.conditions.encumberedEffect",
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
value: modifier.toString()
}];
await selectedActor.createEmbeddedDocuments("ActiveEffect", [{
name: game.i18n.localize(effectType.encumbered),
icon: "systems/twodsix/assets/icons/weight.svg",
changes: changeData,
statuses: ["encumbered"]
}]);
//const newEffect = selectedActor.effects.find(eff => eff.name === effectType.encumbered);
//await newEffect?.setFlag("core", "statusId", "weakened"); //Kludge to make icon appear on token
if (isCurrentlyEncumbered.length === 0) {
await selectedActor.createEmbeddedDocuments("ActiveEffect", [{
name: game.i18n.localize(effectType.encumbered),
icon: "systems/twodsix/assets/icons/weight.svg",
changes: changeData,
statuses: ["encumbered"]
}]);
} else {
if (changeData[0].value !== selectedActor.effects.get(idToKeep)?.changes[0].value && !!idToKeep) {
await selectedActor.updateEmbeddedDocuments('ActiveEffect', [{ _id: idToKeep, changes: changeData }]);
}
}
}
}

async function checkUnconsciousness(selectedActor: TwodsixActor, oldWoundState: ActiveEffect | undefined, tintToApply: string) {

/**
* Determine whether actor becomes unconscious based on ruleset. Depending on ruleset, may make endurance roll.
* @param {TwodsixActor} selectedActor The actor to check
* @param {ActiveEffect | undefined} oldWoundState The current wounded AE for actor
* @param {string} tintToApply The wounded state tint to be applied (the updated tint)
*/
async function checkUnconsciousness(selectedActor: TwodsixActor, oldWoundState: ActiveEffect | undefined, tintToApply: string): Promise<void> {
const isAlreadyUnconscious = selectedActor.effects.find(eff => eff.statuses.has('unconscious'));
const isAlreadyDead = selectedActor.effects.find(eff => eff.statuses.has('dead'));
const rulesSet = game.settings.get('twodsix', 'ruleset').toString();
Expand All @@ -200,6 +211,12 @@ async function checkUnconsciousness(selectedActor: TwodsixActor, oldWoundState:
}
}

/**
* Toggles an effect status/condition on an actor.
* @param {string} effectStatus status/condition name to change
* @param {TwodsixActor} targetActor The actor to update
* @param {boolean} state Whether the status is enabled
*/
async function setConditionState(effectStatus: string, targetActor: TwodsixActor, state: boolean): Promise<void> {
const isAlreadySet = targetActor.effects.filter(eff => eff.statuses.has(effectStatus));
const targetEffect = CONFIG.statusEffects.find(statusEffect => (statusEffect.id === effectStatus));
Expand Down Expand Up @@ -238,6 +255,12 @@ async function setConditionState(effectStatus: string, targetActor: TwodsixActor
}
}

/**
* Determine whether wounded effect applies to actor. Update wounded AE, if necessary.
* @param {TwodsixActor} targetActor The actor to check
* @param {boolean} state whether wounded effect applies
* @param {string} tint The wounded tint color (as a hex code string). Color indicates the severity of wounds. DAMAGECOLORS.minorWoundTint and DAMAGECOLORS.seriousWoundTint
*/
async function setWoundedState(targetActor: TwodsixActor, state: boolean, tint: string): Promise<void> {
const isAlreadySet = await targetActor?.effects.filter(eff => eff.statuses.has('wounded'));
let currentEffectId = "";
Expand Down Expand Up @@ -280,6 +303,11 @@ async function setWoundedState(targetActor: TwodsixActor, state: boolean, tint:
}
}

/**
* Determine the wounded tint that applies to actor. Depends on ruleset.
* @param {TwodsixActor} selectedActor The actor to check
* @returns {string} The wounded tint color (as a hex code string). Color indicates the severity of wounds. DAMAGECOLORS.minorWoundTint and DAMAGECOLORS.seriousWoundTint
*/
export function getIconTint(selectedActor: TwodsixActor): string {
const selectedTraveller = <Traveller>selectedActor.system;
if ((selectedActor.type === 'animal' && game.settings.get('twodsix', 'animalsUseHits')) || (selectedActor.type === 'robot' && game.settings.get('twodsix', 'robotsUseHits'))) {
Expand Down Expand Up @@ -395,4 +423,24 @@ export function getCEAWoundTint(selectedTraveller: Traveller): string {
return returnVal;
}


/**
* Determine the encumbrance modifier based on the ratio of encumbrance to the maximum encumbrance.
* @param {number} ratio encumbrance/max encumbrance
* @return {number} encumbrance roll modifier value that gets applied to the encumbered AE
* @function
*/
function getEncumbranceModifier(ratio:number):number {
if (ratio === 0 ) {
return 0; //Shoudn't get here
} else if (game.settings.get("twodsix", "ruleset") === 'CE') {
if (ratio <= game.settings.get('twodsix', 'encumbranceFraction')) {
return 0;
} else if (ratio <= game.settings.get('twodsix', 'encumbranceFraction') * 2) {
return game.settings.get('twodsix', 'encumbranceModifier');
} else if (ratio <= 1) {
return game.settings.get('twodsix', 'encumbranceModifier') * 2;
}
} else {
return game.settings.get('twodsix', 'encumbranceModifier');
}
}
9 changes: 8 additions & 1 deletion src/module/settings/DisplaySettings.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck This turns off *all* typechecking, make sure to remove this once foundry-vtt-types are updated to cover v10.
import AdvancedSettings from "./AdvancedSettings";
import {booleanSetting, colorSetting, stringChoiceSetting} from "./settingsUtils";
import {booleanSetting, colorSetting, stringChoiceSetting, stringSetting} from "./settingsUtils";
import {TWODSIX} from "../config";

export default class DisplaySettings extends AdvancedSettings {
Expand Down Expand Up @@ -59,6 +59,7 @@ export default class DisplaySettings extends AdvancedSettings {
settings.actor.push(booleanSetting('omitPSIifZero', false));
settings.actor.push(stringChoiceSetting('equippedToggleStates', "default", true, TWODSIX.EQUIPPED_TOGGLE_OPTIONS));
settings.actor.push(booleanSetting('showSkillGroups', false));
settings.ship.push(stringSetting('jDriveLabel', "TWODSIX.Ship.JDrive", false, "world", updateJDrive, true));
return settings;
}
}
Expand Down Expand Up @@ -100,3 +101,9 @@ export const setDocumentPartials = function () {
}
ui.items.render();
};

export const updateJDrive = function (value) {
if (value === "") {
game.settings.set('twodsix', 'jDriveLabel', "TWODSIX.Ship.JDrive");
}
};
2 changes: 1 addition & 1 deletion src/module/sheets/AbstractTwodsixActorSheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ export abstract class AbstractTwodsixActorSheet extends ActorSheet {
sheetData.container.equipmentAndTools = actor.itemTypes.equipment.concat(actor.itemTypes.tool).concat(actor.itemTypes.computer);
sheetData.container.storageAndJunk = actor.itemTypes.storage.concat(actor.itemTypes.junk);
sheetData.container.skills = skillsList;
sheetData.container.skillGroups = skillGroups;
sheetData.container.skillGroups = sortObj(skillGroups);
if (actor.type === "traveller") {
sheetData.numberOfSkills = numberOfSkills + (sheetData.jackOfAllTrades > 0 ? 1 : 0);
sheetData.numberListedSkills = numberOfSkills;
Expand Down
3 changes: 2 additions & 1 deletion src/module/sheets/TwodsixShipSheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ export class TwodsixShipSheet extends AbstractTwodsixActorSheet {
allowDragDropOfLists: game.settings.get('twodsix', 'allowDragDropOfLists'),
maxComponentHits: game.settings.get('twodsix', 'maxComponentHits'),
usePDFPager: game.settings.get('twodsix', 'usePDFPagerForRefs'),
showActorReferences: game.settings.get('twodsix', 'showActorReferences')
showActorReferences: game.settings.get('twodsix', 'showActorReferences'),
jDriveLabel: game.settings.get('twodsix', 'jDriveLabel') || "TWODSIX.Ship.JDrive"
};

if (context.settings.useProseMirror) {
Expand Down
1 change: 1 addition & 0 deletions src/types/twodsix.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ declare global {
'twodsix.targetDMList': string;
'twodsix.showSkillGroups': boolean;
'twodsix.NoDuplicatesOnHotbar': boolean;
'twodsix.jDriveLabel': string;
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions static/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,10 @@
"NoDuplicatesOnHotbar": {
"hint": "Prevent duplicate item macros when drag-dropping items onto hotbar",
"name": "No duplicate item macros on hotbar"
},
"jDriveLabel": {
"hint": "The display label for the jump drive on the power management settings",
"name": "Jump Drive Label"
}
},
"CloseAndCreateNew": "Close and create new",
Expand Down
2 changes: 1 addition & 1 deletion static/templates/actors/parts/actor/actor-skills.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
{{#each container.skillGroups}}
<div class="grid-columns-single-row skill-header" data-parent-key="{{@key}}">
<ol class="ol-no-indent header-row">
{{twodsix_titleCase @key}} {{#unless (lookup ../actor.system.displaySkillGroup @key)}}<i class="fa-solid fa-eye"></i>{{else}}<i class="fa-solid fa-eye-slash" ></i>{{/unless}}
{{@key}} {{#unless (lookup ../actor.system.displaySkillGroup @key)}}<i class="fa-solid fa-eye"></i>{{else}}<i class="fa-solid fa-eye-slash" ></i>{{/unless}}
</ol>
</div>
{{#if (lookup ../actor.system.displaySkillGroup @key)}}
Expand Down
2 changes: 1 addition & 1 deletion static/templates/actors/ship-sheet.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
{{else}}
<div class="ship-stat systems-power"><label for="system.reqPower.systems">{{localize "TWODSIX.Ship.Systems"}}</label><input {{#if settings.useShipAutoCalc}}readonly{{/if}} type="number" value="{{actor.system.reqPower.systems}}" name="system.reqPower.systems" id="system.reqPower.systems"/><span class = "small-font">{{localize "TWODSIX.Ship.PP"}}</span></div>
<div class="ship-stat m-drive-power"><label for="system.reqPower.m-drive">{{localize "TWODSIX.Ship.MDrive"}}</label><input {{#if settings.useShipAutoCalc}}readonly{{/if}} type="number" value="{{actor.system.reqPower.m-drive}}" name="system.reqPower.m-drive" id="system.reqPower.m-drive"/><span class = "small-font">{{localize "TWODSIX.Ship.PP"}}</span></div>
<div class="ship-stat j-drive-power"><label for="system.reqPower.j-drive">{{localize "TWODSIX.Ship.JDrive"}}</label><input {{#if settings.useShipAutoCalc}}readonly{{/if}} type="number" value="{{actor.system.reqPower.j-drive}}" name="system.reqPower.j-drive" id="system.reqPower.j-drive"/><span class = "small-font">{{localize "TWODSIX.Ship.PP"}}</span></div>
<div class="ship-stat j-drive-power"><label for="system.reqPower.j-drive">{{localize settings.jDriveLabel}}</label><input {{#if settings.useShipAutoCalc}}readonly{{/if}} type="number" value="{{actor.system.reqPower.j-drive}}" name="system.reqPower.j-drive" id="system.reqPower.j-drive"/><span class = "small-font">{{localize "TWODSIX.Ship.PP"}}</span></div>
<div class="ship-stat sensors-power"><label for="system.reqPower.sensors">{{localize "TWODSIX.Ship.Sensors"}}</label><input {{#if settings.useShipAutoCalc}}readonly{{/if}} type="number" value="{{actor.system.reqPower.sensors}}" name="system.reqPower.sensors" id="system.reqPower.sensors"/><span class = "small-font">{{localize "TWODSIX.Ship.PP"}}</span></div>
<div class="ship-stat weapons-power"><label for="system.reqPower.weapons">{{localize "TWODSIX.Ship.Weapons"}}</label><input {{#if settings.useShipAutoCalc}}readonly{{/if}} type="number" value="{{actor.system.reqPower.weapons}}" name="system.reqPower.weapons" id="system.reqPower.weapons"><span class = "small-font">{{localize "TWODSIX.Ship.PP"}}</span></div>
{{/if}}
Expand Down

0 comments on commit 94668b8

Please sign in to comment.