Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#1] Native Actions List in Tidy 5e Sheets #71

Merged
merged 26 commits into from
Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c679428
Added Actions List settings to the settings config.
kgar Nov 23, 2023
62a82eb
Added ActionsTab component.
kgar Nov 23, 2023
b6771c9
Made adjustment to appease TypeScript.
kgar Nov 23, 2023
160de3a
Merge branch 'main' of github.com:kgar/foundry-tidy5e-sheet-2 into al…
kgar Nov 24, 2023
974415a
Removed Action tab inclusion / enabling logic now that it is being ha…
kgar Nov 24, 2023
1b2719f
Removed deleted settings from the Features Settings tab.
kgar Nov 25, 2023
e1bbb01
Added/lightly adapted actions script.
kgar Nov 25, 2023
6ce1445
Adapted most of the original template. WIP.
kgar Nov 25, 2023
7dcf520
Unlocked item table cell height.
kgar Nov 25, 2023
9632fd2
Added active effects marker override for ItemName component.
kgar Nov 25, 2023
a1fa0d5
Gearing up to figure out the rest of the template.
kgar Nov 25, 2023
6fd99a4
Worked out a solid truncation approach when dealing with flex-columns.
kgar Nov 25, 2023
80cc289
Removed unnecessary style from flex column truncate rule.
kgar Nov 25, 2023
1b62b69
Parsed out Hit/DC section.
kgar Nov 25, 2023
2138773
Parsed out Damage column.
kgar Nov 25, 2023
9a26ae7
Moved stray controls into the controls directory in item-list compone…
kgar Nov 25, 2023
1df7ec1
Applied some additional formatting for the uses section.
kgar Nov 25, 2023
357805c
More fiddling.
kgar Nov 25, 2023
0651a80
kgar Nov 25, 2023
5ae14d2
Added Action Override control to inventory, features, spellbook, NPC …
kgar Nov 25, 2023
9c2dcec
Added generic actorUsesActionFeature function to the actions script.
kgar Nov 25, 2023
ef590ca
Added context menu support to actions tab table rows.
kgar Nov 25, 2023
88bbb6e
Added item-info-card script to begin housing functions that can be ex…
kgar Nov 26, 2023
cbf7d7c
Resolved warnings by moving global Foundry references to FoundryAdapter
kgar Nov 26, 2023
779abcd
Removed rogue logs.
kgar Nov 26, 2023
438e60b
Added spell class to spell small label.
kgar Nov 26, 2023
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
2 changes: 1 addition & 1 deletion public/lang/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"T5EK.LongRest": "Důkladný odpočinek",
"T5EK.HitDiceRollAverage": "Right Click: Average",

"T5EK.Journal": "Deník",
"T5EK.JournalTabName": "Deník",

"T5EK.RestoreOnRest": "Obnovit při odpočinku",

Expand Down
2 changes: 1 addition & 1 deletion public/lang/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"T5EK.LongRest": "Lange Rast",
"T5EK.HitDiceRollAverage": "Rechtsklick: Durchschnitt",

"T5EK.Journal": "Tagebuch",
"T5EK.JournalTabName": "Tagebuch",

"T5EK.RestoreOnRest": "Bei Rast regenerieren",

Expand Down
33 changes: 32 additions & 1 deletion public/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,18 @@
"T5EK.LongRest": "Long Rest",
"T5EK.HitDiceRollAverage": "right click: apply average",

"T5EK.Journal": "Journal",
"T5EK.JournalTabName": "Journal",

"T5EK.Actions": {
"TabName": "Actions",
"OverriddenSetOverrideFalse": "Remove from Actions List (Shift+Click to clear override)",
"OverriddenSetOverrideTrue": "Add to Actions List (Shift+Click to clear override)",
"SetOverrideFalse": "Remove from Actions List",
"SetOverrideTrue": "Add to Actions List",
"ClearOverride": "Clear Action Override"
},

"T5EK.HitDC": "Hit / DC",

"T5EK.RestoreOnRest": "Restore on rest",

Expand Down Expand Up @@ -177,6 +188,9 @@
"labelInventoryLocks": "Inventory Locks",
"labelFeaturesLocks": "Features Locks"
},
"T5EK.Settings.TabFeatures": {
"tabLabel": "Features"
},
"T5EK.Settings.TabInfo": {
"tabLabel": "Info",
"Header": "Info",
Expand Down Expand Up @@ -440,6 +454,23 @@
"name": "Lock Item Quantity",
"hint": "Prevents non-GMs from making changes to item quantity."
},
"T5EK.Settings.ActionsList.Header": "Actions List",
"T5EK.Settings.ActionsListLimitActionsToCantrips": {
"name": "Limit Actions to Cantrips",
"hint": "Instead of showing all spells that deal damage in the Actions tab/panel by default, limits to only cantrips. This is the default D&DBeyond behavior."
},
"T5EK.Settings.ActionsListIncludeMinuteLongSpellsAsActions": {
"name": "Include Minute-long Spells as Actions",
"hint": "Includes spells with a duration of one minute or less (e.g. 1 round) and an activation time of 1 Action or 1 Bonus Action (e.g. Bless, Bane, Command) in the Actions tab/panel by default."
},
"T5EK.Settings.ActionsListIncludeSpellsWithActiveEffects": {
"name": "Include Spells with Active Effects as Actions",
"hint": "Includes spells with active effects attached (e.g. Barkskin) in the Actions tab/panel by default."
},
"T5EK.Settings.ActionsListIncludeConsumables": {
"name": "Include Consumables as Actions",
"hint": "Includes consumables which have an activation cost (Action, Bonus Action, etc) in the Actions list by default."
},
"T5EK.Settings.ColorPickerEnabled": {
"name": "Use Custom Theme Colors",
"hint": "Shows custom theme colors"
Expand Down
2 changes: 1 addition & 1 deletion public/lang/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"T5EK.LongRest": "Descanso largo",
"T5EK.HitDiceRollAverage": "Right Click: Average",

"T5EK.Journal": "Diario",
"T5EK.JournalTabName": "Diario",

"T5EK.RestoreOnRest": "Recuperar tras descanso",

Expand Down
2 changes: 1 addition & 1 deletion public/lang/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"T5EK.LongRest": "Repos Long",
"T5EK.HitDiceRollAverage": "Right Click: Average",

"T5EK.Journal": "Journal",
"T5EK.JournalTabName": "Journal",

"T5EK.RestoreOnRest": "Restaurer lors d'un repos",

Expand Down
2 changes: 1 addition & 1 deletion public/lang/hu.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"T5EK.LongRest": "Hosszú Pihenő",
"T5EK.HitDiceRollAverage": "Jobb Klikk: Átlag",

"T5EK.Journal": "Napló",
"T5EK.JournalTabName": "Napló",

"T5EK.RestoreOnRest": "Pihenéskor visszaáll",

Expand Down
2 changes: 1 addition & 1 deletion public/lang/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"T5EK.LongRest": "Riposo Lungo",
"T5EK.HitDiceRollAverage": "Right Click: Average",

"T5EK.Journal": "Diario",
"T5EK.JournalTabName": "Diario",

"T5EK.RestoreOnRest": "Ripristina dopo un riposo",

Expand Down
2 changes: 1 addition & 1 deletion public/lang/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"T5EK.LongRest": "大休憩",
"T5EK.HitDiceRollAverage": "右クリック:平均",

"T5EK.Journal": "記録",
"T5EK.JournalTabName": "記録",

"T5EK.RestoreOnRest": "休憩時に回復",

Expand Down
2 changes: 1 addition & 1 deletion public/lang/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"T5EK.LongRest": "긴 휴식",
"T5EK.HitDiceRollAverage": "우클릭: 평균",

"T5EK.Journal": "저널",
"T5EK.JournalTabName": "저널",

"T5EK.RestoreOnRest": "휴식시 복원",

Expand Down
2 changes: 1 addition & 1 deletion public/lang/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"T5EK.LongRest": "D. Longo",
"T5EK.HitDiceRollAverage": "Right Click: Average",

"T5EK.Journal": "Diário",
"T5EK.JournalTabName": "Diário",

"T5EK.RestoreOnRest": "Restaurar no Descanso",

Expand Down
2 changes: 1 addition & 1 deletion public/lang/zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"T5EK.LongRest": "長休",
"T5EK.HitDiceRollAverage": "Right Click: Average",

"T5EK.Journal": "日誌",
"T5EK.JournalTabName": "日誌",

"T5EK.RestoreOnRest": "休息時恢復",

Expand Down
235 changes: 235 additions & 0 deletions src/actions/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import { CONSTANTS } from 'src/constants';
import { FoundryAdapter } from 'src/foundry/foundry-adapter';
import { SettingsProvider } from 'src/settings/settings';
import type { Item5e } from 'src/types/item';
import type { Actor5e, ActorActions } from 'src/types/types';
import { debug, error } from 'src/utils/logging';

export type ActionSets = {
action: Set<Item5e>;
bonus: Set<Item5e>;
crew: Set<Item5e>;
lair: Set<Item5e>;
legendary: Set<Item5e>;
mythic: Set<Item5e>;
special: Set<Item5e>;
reaction: Set<Item5e>;
other: Set<Item5e>;
};

const itemTypeSortValues: Record<string, number> = {
weapon: 1,
equipment: 2,
feat: 3,
spell: 4,
consumable: 5,
tool: 6,
backpack: 7,
class: 8,
loot: 9,
};

export function getActorActions(actor: Actor5e): ActorActions {
const filteredItems: any[] = actor.items
.filter(isItemInActionList)
.sort((a: Item5e, b: Item5e) => {
if (a.type !== b.type) {
return itemTypeSortValues[a.type] - itemTypeSortValues[b.type];
}
if (a.type === 'spell' && b.type === 'spell') {
return a.system.level - b.system.level;
}
return (a.sort || 0) - (b.sort || 0);
})
.map((item: Item5e) => {
if (item.labels) {
item.labels.type = FoundryAdapter.localize(
`ITEM.Type${item.type.titleCase()}`
);
}

// removes any in-formula flavor text from the formula in the label
if (item.labels?.derivedDamage?.length) {
item.labels.derivedDamage = item.labels.derivedDamage.map(
({ formula, ...rest }: any) => ({
formula: formula?.replace(/\[.+?\]/, '') || '0',
...rest,
})
);
}
return item;
});

const initial: ActionSets = {
action: new Set(),
bonus: new Set(),
crew: new Set(),
lair: new Set(),
legendary: new Set(),
reaction: new Set(),
other: new Set(),
mythic: new Set(),
special: new Set(),
};
const actionsData = filteredItems.reduce<ActionSets>((acc, item) => {
try {
if (['backpack', 'tool'].includes(item.type)) {
return acc;
}

const activationType = getActivationType(item.system.activation?.type);
acc[activationType].add(item);
return acc;
} catch (e) {
error('error trying to digest item', true, { name: item.name, e });
return acc;
}
}, initial);
return actionsData;
}

export function isItemInActionList(item: Item5e): boolean {
// check our override

const override = FoundryAdapter.tryGetFlag<boolean>(
item,
'action-filter-override'
);

if (override !== undefined && override !== null) {
return override;
}

// perform normal filtering logic
switch (item.type) {
case 'weapon': {
return item.system.equipped;
}
case 'equipment': {
return item.system.equipped && isActiveItem(item.system.activation?.type);
}
case 'consumable': {
return (
SettingsProvider.settings.actionListIncludeConsumables.get() &&
isActiveItem(item.system.activation?.type)
);
}
case 'spell': {
const limitToCantrips =
SettingsProvider.settings.actionListLimitActionsToCantrips.get();

// only exclude spells which need to be prepared but aren't
const notPrepared =
item.system.preparation?.mode === 'prepared' &&
!item.system.preparation?.prepared;
const isCantrip = item.system.level === 0;
if (!isCantrip && (limitToCantrips || notPrepared)) {
return false;
}
const isReaction = item.system.activation?.type === 'reaction';
const isBonusAction = item.system.activation?.type === 'bonus';

//ASSUMPTION: If the spell causes damage, it will have damageParts
const isDamageDealer = item.system.damage?.parts?.length > 0;
let shouldInclude = isReaction || isBonusAction || isDamageDealer;
if (
SettingsProvider.settings.actionListIncludeMinuteLongSpellsAsActions.get()
) {
const isOneMinuter =
item.system?.duration?.units === 'minute' &&
item.system?.duration?.value === 1;
const isOneRounder =
item.system?.duration?.units === 'round' &&
item.system?.duration?.value === 1;
shouldInclude = shouldInclude || isOneMinuter || isOneRounder;
}
if (
SettingsProvider.settings.actionListIncludeSpellsWithActiveEffects.get()
) {
const hasEffects = !!item.effects.size;
shouldInclude = shouldInclude || hasEffects;
}
return shouldInclude;
}
case 'feat': {
return !!item.system.activation?.type;
}
default: {
return false;
}
}
}

function getActivationType(activationType: string) {
switch (activationType) {
case 'action':
case 'bonus':
case 'crew':
case 'lair':
case 'legendary':
case 'mythic':
case 'special':
case 'reaction':
return activationType;
default:
return 'other';
}
}

function isActiveItem(activationType: string) {
if (!activationType) {
return false;
}
if (['minute', 'hour', 'day', 'none'].includes(activationType)) {
return false;
}
return true;
}

export const damageTypeIconMap: Record<string, string> = {
acid: '<i class="fas fa-hand-holding-water"></i>',
bludgeoning: '<i class="fas fa-gavel"></i>',
cold: '<i class="fas fa-snowflake"></i>',
fire: '<i class="fas fa-fire-alt"></i>',
force: '<i class="fas fa-hat-wizard"></i>',
lightning: '<i class="fas fa-bolt"></i>',
necrotic: '<i class="fas fa-skull"></i>',
piercing: '<i class="fas fa-thumbtack"></i>',
poison: '<i class="fas fa-skull-crossbones"></i>',
psychic: '<i class="fas fa-brain"></i>',
radiant: '<i class="fas fa-sun"></i>',
slashing: '<i class="fas fa-cut"></i>',
thunder: '<i class="fas fa-wind"></i>',
healing: '<i class="fas fa-heart"></i>',
temphp: '<i class="fas fa-shield-alt"></i>',
};

export function actorUsesActionFeature(actor: Actor5e) {
const selectedTabIds = FoundryAdapter.tryGetFlag<string[] | undefined>(
actor,
'selected-tabs'
);

if (selectedTabIds) {
return selectedTabIds.includes(CONSTANTS.TAB_ACTOR_ACTIONS);
}

const defaultTabIds =
actor.type === CONSTANTS.SHEET_TYPE_CHARACTER
? SettingsProvider.settings.defaultCharacterSheetTabs.get()
: actor.type === CONSTANTS.SHEET_TYPE_NPC
? SettingsProvider.settings.defaultNpcSheetTabs.get()
: actor.type === CONSTANTS.SHEET_TYPE_VEHICLE
? SettingsProvider.settings.defaultVehicleSheetTabs.get()
: [];

return defaultTabIds.includes(CONSTANTS.TAB_ACTOR_ACTIONS);
}

export function toggleActionFilterOverride(item: Item5e) {
FoundryAdapter.setFlag(
item,
'action-filter-override',
!isItemInActionList(item)
);
}
Loading