import {InitiativeTrackerConst} from "./dmscreen-initiativetracker-consts.js"; import { InitiativeTrackerRowStatsColDataSerializer, InitiativeTrackerStatColumnDataSerializer, } from "./dmscreen-initiativetracker-serial.js"; import {InitiativeTrackerUtil} from "../../initiativetracker/initiativetracker-utils.js"; export const GROUP_BASE_STATS = "baseStats"; export const GROUP_SAVES = "saves"; export const GROUP_ABILITY_BONUS = "abilityBonus"; export const GROUP_ABILITY_SCORE = "abilityScore"; export const GROUP_SKILL = "skill"; export const GROUP_CHECKBOX = "checkbox"; export const GROUP_CUSTOM = "custom"; export const GROUP_DISPLAY_NAMES = { [GROUP_BASE_STATS]: "General", [GROUP_SAVES]: "Saving Throw", [GROUP_ABILITY_BONUS]: "Ability Bonus", [GROUP_ABILITY_SCORE]: "Ability Score", [GROUP_SKILL]: "Skill", [GROUP_CHECKBOX]: "Checkbox", [GROUP_CUSTOM]: "Custom", }; export const IS_PLAYER_VISIBLE_NONE = 0; export const IS_PLAYER_VISIBLE_ALL = 1; export const IS_PLAYER_VISIBLE_PLAYER_UNITS_ONLY = 2; export const INITIATIVE_APPLICABILITY_NOT_APPLICABLE = 0; const _INITIATIVE_APPLICABILITY_APPLICABLE = 1; const _INITIATIVE_APPLICABILITY_EXACT = 2; /** @abstract */ class _InitiativeTrackerStatColumnBase { static _SERIALIZER_MAPPINGS = { "entity.value": "v", }; static _registerSerializerMappings () { Object.entries(this._SERIALIZER_MAPPINGS) .forEach(([kFull, kSerial]) => InitiativeTrackerRowStatsColDataSerializer.registerMapping({kFull, kSerial, isAllowDuplicates: true})); return null; } static _ = this._registerSerializerMappings(); /* -------------------------------------------- */ /** Functions as an ID for the column type. */ static get POPULATE_WITH () { throw new Error("Unimplemented!"); } /** UI group the column type belongs to. */ static GROUP; static NAME; static ABV_DEFAULT = ""; constructor ( { id, isEditable, isPlayerVisible, abbreviation, } = {}, ) { this._id = id ?? CryptUtil.uid(); this._isEditable = isEditable ?? true; this._isPlayerVisible = isPlayerVisible ?? false; this._abbreviation = abbreviation ?? this.constructor.ABV_DEFAULT; } /** * @param {?object} mon * @param {?object} fluff * @return {?*} * @abstract */ _getInitialCellObj ({mon, fluff}) { throw new Error("Unimplemented!"); } _getAsData () { return { id: this._id, isEditable: this._isEditable, isPlayerVisible: this._isPlayerVisible, populateWith: this.constructor.POPULATE_WITH, abbreviation: this._abbreviation, }; } getAsStateData () { return this._getAsData(); } getAsCollectionRowStateData () { const data = this._getAsData(); const out = { id: data.id, entity: { ...data, }, }; delete out.entity.id; return out; } getPlayerFriendlyState ({cell}) { return {id: cell.id, entity: cell.entity}; } /* -------------------------------------------- */ /** * @return {?object} `undefined` if the column should not auto-set at the start of the turn, or, a value the column should * be auto-set to at the start of the turn. */ _getAutoTurnStartObject ({state, mon}) { return undefined; } /** * @return {?object} `undefined` if the column should not auto-set at the start of the round, or, a value the column should * be auto-set to at the start of the round. */ _getAutoRoundStartObject ({state, mon}) { return undefined; } _isNonNegativeDirection ({direction}) { return ![InitiativeTrackerConst.DIR_FORWARDS, InitiativeTrackerConst.DIR_NEUTRAL].includes(direction); } onTurnStart ({state, direction, mon}) { if (this._isNonNegativeDirection({direction})) return; const obj = this._getAutoTurnStartObject({state, mon}); if (obj !== undefined) Object.assign(state.entity, obj); } onRoundStart ({state, direction, mon}) { if (this._isNonNegativeDirection({direction})) return; const obj = this._getAutoRoundStartObject({state, mon}); if (obj !== undefined) Object.assign(state.entity, obj); } $getRenderedHeader () { return $(`
${this._abbreviation}
`); } $getRendered ({comp, mon, networking = null}) { const $ipt = ComponentUiUtil.$getIptStr(comp, "value") .removeClass("input-xs") .addClass("input-sm") .addClass("dm-init__stat_ipt") .addClass("ve-text-center") .on("click", () => $ipt.select()); if (mon) { comp._addHookBase("isEditable", () => { $ipt.prop("disabled", !comp._state.isEditable); })(); } return $$`
${$ipt}
`; } /* -------------------------------------------- */ /** * @param {?object} mon * @param {?object} fluff * @param {?object} obj */ getInitialCellStateData ({mon = null, fluff = null, obj = null} = {}) { return { id: this._id, entity: { ...(obj ?? (this._getInitialCellObj({mon, fluff}) || {})), isEditable: this._isEditable, }, }; } /* -------------------------------------------- */ getInitiativeInfo ({state}) { return { applicability: INITIATIVE_APPLICABILITY_NOT_APPLICABLE, initiative: null, }; } } class InitiativeTrackerStatColumn_HpFormula extends _InitiativeTrackerStatColumnBase { static get POPULATE_WITH () { return "hpFormula"; } static GROUP = GROUP_BASE_STATS; static NAME = "HP Formula"; _getInitialCellObj ({mon, fluff}) { if (!mon) return {value: null}; return {value: (mon.hp || {}).formula || ""}; } } class InitiativeTrackerStatColumn_ArmorClass extends _InitiativeTrackerStatColumnBase { static get POPULATE_WITH () { return "armorClass"; } static GROUP = GROUP_BASE_STATS; static NAME = "Armor Class"; static ABV_DEFAULT = "AC"; _getInitialCellObj ({mon, fluff}) { if (!mon) return {value: null}; return {value: mon.ac[0] ? (mon.ac[0].ac || mon.ac[0]) : null}; } } class InitiativeTrackerStatColumn_PassivePerception extends _InitiativeTrackerStatColumnBase { static get POPULATE_WITH () { return "passivePerception"; } static GROUP = GROUP_BASE_STATS; static NAME = "Passive Perception"; static ABV_DEFAULT = "PP"; _getInitialCellObj ({mon, fluff}) { if (!mon) return {value: null}; return {value: mon.passive}; } } class InitiativeTrackerStatColumn_Speed extends _InitiativeTrackerStatColumnBase { static get POPULATE_WITH () { return "speed"; } static GROUP = GROUP_BASE_STATS; static NAME = "Speed"; static ABV_DEFAULT = "SPD"; _getInitialCellObj ({mon, fluff}) { if (!mon) return {value: null}; return { value: Math.max(0, ...Object.values(mon.speed || {}) .map(it => it.number ? it.number : it) .filter(it => typeof it === "number")), }; } } class InitiativeTrackerStatColumn_SpellDc extends _InitiativeTrackerStatColumnBase { static get POPULATE_WITH () { return "spellDc"; } static GROUP = GROUP_BASE_STATS; static NAME = "Spell DC"; static ABV_DEFAULT = "DC"; _getInitialCellObj ({mon, fluff}) { if (!mon) return {value: null}; return { value: Math.max( 0, ...(mon.spellcasting || []) .filter(it => it.headerEntries) .map(it => { return it.headerEntries .map(it => { const found = [0]; it .replace(/DC (\d+)/g, (...m) => found.push(Number(m[1]))) .replace(/{@dc (\d+)}/g, (...m) => found.push(Number(m[1]))); return Math.max(...found); }) .filter(Boolean); }) .flat(), ), }; } } class InitiativeTrackerStatColumn_Initiative extends _InitiativeTrackerStatColumnBase { static get POPULATE_WITH () { return "initiative"; } static GROUP = GROUP_BASE_STATS; static NAME = "Initiative"; static ABV_DEFAULT = "INIT"; _getInitialCellObj ({mon, fluff}) { if (!mon) return {value: null}; return { value: Parser.getAbilityModifier(mon.dex), }; } /* -------------------------------------------- */ getInitiativeInfo ({state}) { return { applicability: _INITIATIVE_APPLICABILITY_EXACT, initiative: isNaN(state.entity?.value) ? 0 : Number(state.entity.value), }; } } class InitiativeTrackerStatColumn_LegendaryActions extends _InitiativeTrackerStatColumnBase { static _SERIALIZER_MAPPINGS = { "entity.current": "cur", "entity.max": "max", }; static _ = this._registerSerializerMappings(); /* -------------------------------------------- */ static get POPULATE_WITH () { return "legendaryActions"; } static GROUP = GROUP_BASE_STATS; static NAME = "Legendary Actions"; static ABV_DEFAULT = "LA"; getPlayerFriendlyState ({cell}) { return { ...super.getPlayerFriendlyState({cell}), entity: { value: `${cell.entity.current || 0}/${cell.entity.max || 0}`, }, }; } _getInitialCellObj ({mon, fluff}) { if (!mon) return {current: null, max: null}; const cnt = mon.legendaryActions ?? (mon.legendary ? 3 : null); return { current: cnt, max: cnt, }; } _getAutoRoundStartObject ({state, mon}) { return {current: state.entity.max}; } $getRenderedHeader () { return super.$getRenderedHeader() .addClass("w-48p"); } $getRendered ({comp, mon}) { const $iptCurrent = ComponentUiUtil.$getIptNumber( comp, "current", null, { isAllowNull: true, fallbackOnNaN: null, html: ``, }, ) .on("click", () => $iptCurrent.select()); const $iptMax = ComponentUiUtil.$getIptNumber( comp, "max", null, { isAllowNull: true, fallbackOnNaN: null, html: ``, }, ) .on("click", () => $iptMax.select()); if (mon) { comp._addHookBase("isEditable", () => { $iptCurrent.prop("disabled", !comp._state.isEditable); $iptMax.prop("disabled", !comp._state.isEditable); })(); } return $$`
${$iptCurrent}
/
${$iptMax}
`; } } class InitiativeTrackerStatColumn_Image extends _InitiativeTrackerStatColumnBase { static _SERIALIZER_MAPPINGS = { "entity.tokenUrl": "urlt", "entity.imageHref": "hrefi", }; static _ = this._registerSerializerMappings(); /* -------------------------------------------- */ static get POPULATE_WITH () { return "image"; } static GROUP = GROUP_BASE_STATS; static NAME = "Image"; static ABV_DEFAULT = "IMG"; getPlayerFriendlyState ({cell}) { return { ...super.getPlayerFriendlyState({cell}), entity: { type: "image", tokenUrl: cell.entity.tokenUrl, imageHref: cell.entity.imageHref, }, }; } _getInitialCellObj ({mon, fluff}) { if (!mon) { return { tokenUrl: UrlUtil.link(Renderer.get().getMediaUrl("img", "blank-friendly.webp")), imageHref: null, }; } return { tokenUrl: Renderer.monster.getTokenUrl(mon), imageHref: fluff?.images?.[0].href, }; } $getRendered ({comp, mon, networking = null}) { const $ele = $$`
Token Image
`; if (networking != null) { $ele.title("Click to Show to Connected Players"); $ele .on("click", () => { networking.sendShowImageMessageToClients({ imageHref: InitiativeTrackerUtil.getImageOrTokenHref({imageHref: comp._state.imageHref, tokenUrl: comp._state.tokenUrl}), }); }); // If no networking, assume this is an "edit" modal, and avoid binding events Renderer.monster.hover.bindFluffImageMouseover({mon, $ele}); } return $ele; } } class InitiativeTrackerStatColumn_Save extends _InitiativeTrackerStatColumnBase { static _ATT; static get POPULATE_WITH () { return `${this._ATT}Save`; } static GROUP = GROUP_SAVES; static get NAME () { return `${Parser.attAbvToFull(this._ATT)} Save`; } static get ABV_DEFAULT () { return this._ATT.toUpperCase(); } _getInitialCellObj ({mon, fluff}) { if (!mon) return {value: null}; return { value: mon.save?.[this.constructor._ATT] ? mon.save[this.constructor._ATT] : Parser.getAbilityModifier(mon[this.constructor._ATT]), }; } } class InitiativeTrackerStatColumn_AbilityBonus extends _InitiativeTrackerStatColumnBase { static _ATT; static get POPULATE_WITH () { return `${this._ATT}Bonus`; } static GROUP = GROUP_ABILITY_BONUS; static get NAME () { return `${Parser.attAbvToFull(this._ATT)} Bonus`; } static get ABV_DEFAULT () { return this._ATT.toUpperCase(); } _getInitialCellObj ({mon, fluff}) { if (!mon) return {value: null}; return { value: Parser.getAbilityModifier(mon[this.constructor._ATT]), }; } /* -------------------------------------------- */ getInitiativeInfo ({state}) { if (this.constructor._ATT !== "dex") return super.getInitiativeInfo({state}); return { applicability: _INITIATIVE_APPLICABILITY_APPLICABLE, initiative: isNaN(state.entity?.value) ? 0 : Number(state.entity.value), }; } } class InitiativeTrackerStatColumn_AbilityScore extends _InitiativeTrackerStatColumnBase { static _ATT; static get POPULATE_WITH () { return `${this._ATT}Score`; } static GROUP = GROUP_ABILITY_SCORE; static get NAME () { return `${Parser.attAbvToFull(this._ATT)} Score`; } static get ABV_DEFAULT () { return this._ATT.toUpperCase(); } _getInitialCellObj ({mon, fluff}) { if (!mon) return {value: null}; return { value: mon[this.constructor._ATT], }; } /* -------------------------------------------- */ getInitiativeInfo ({state}) { if (this.constructor._ATT !== "dex") return super.getInitiativeInfo({state}); return { applicability: _INITIATIVE_APPLICABILITY_APPLICABLE, initiative: isNaN(state.entity?.value) ? 0 : Parser.getAbilityModifier(Number(state.entity.value)), }; } } class InitiativeTrackerStatColumn_Skill extends _InitiativeTrackerStatColumnBase { static _SKILL; static get POPULATE_WITH () { return this._SKILL.toCamelCase(); } static GROUP = GROUP_SKILL; static get NAME () { return this._SKILL.toTitleCase(); } static get ABV_DEFAULT () { return Parser.skillToShort(this._SKILL).toUpperCase(); } _getInitialCellObj ({mon, fluff}) { if (!mon) return {value: null}; return { value: mon.skill?.[this.constructor._SKILL] ? mon.skill[this.constructor._SKILL] : Parser.getAbilityModifier(mon[Parser.skillToAbilityAbv(this.constructor._SKILL)]), }; } } class _InitiativeTrackerStatColumnCheckboxBase extends _InitiativeTrackerStatColumnBase { static GROUP = GROUP_CHECKBOX; static _AUTO_VALUE = undefined; _getInitialCellObj ({mon, fluff}) { return {value: false}; } $getRendered ({comp, mon}) { const $cb = ComponentUiUtil.$getCbBool(comp, "value") .addClass("dm-init__stat_ipt"); if (mon) { comp._addHookBase("isEditable", () => { $cb.prop("disabled", !comp._state.isEditable); })(); } return $$``; } } class _InitiativeTrackerStatColumnCheckboxTurnBase extends _InitiativeTrackerStatColumnCheckboxBase { _getAutoTurnStartObject ({state, mon}) { return {value: this.constructor._AUTO_VALUE}; } } class _InitiativeTrackerStatColumnCheckboxRoundBase extends _InitiativeTrackerStatColumnCheckboxBase { _getAutoRoundStartObject ({state, mon}) { return {value: this.constructor._AUTO_VALUE}; } } class InitiativeTrackerStatColumn_Checkbox extends _InitiativeTrackerStatColumnCheckboxBase { static get POPULATE_WITH () { return "cbNeutral"; } static NAME = "Checkbox"; } class InitiativeTrackerStatColumn_CheckboxAutoTurnLow extends _InitiativeTrackerStatColumnCheckboxTurnBase { static get POPULATE_WITH () { return "cbAutoLow"; } static NAME = "Checkbox; clears at start of turn"; static _AUTO_VALUE = false; } class InitiativeTrackerStatColumn_CheckboxAutoTurnHigh extends _InitiativeTrackerStatColumnCheckboxTurnBase { static get POPULATE_WITH () { return "cbAutoHigh"; } static NAME = "Checkbox; ticks at start of turn"; static _AUTO_VALUE = true; _getInitialCellObj ({mon, fluff}) { return {value: true}; } } class InitiativeTrackerStatColumn_CheckboxAutoRoundLow extends _InitiativeTrackerStatColumnCheckboxRoundBase { static get POPULATE_WITH () { return "cbAutoRoundLow"; } static NAME = "Checkbox; clears at start of round"; static _AUTO_VALUE = false; } class InitiativeTrackerStatColumn_CheckboxAutoRoundHigh extends _InitiativeTrackerStatColumnCheckboxRoundBase { static get POPULATE_WITH () { return "cbAutoRoundHigh"; } static NAME = "Checkbox; ticks at start of round"; static _AUTO_VALUE = true; _getInitialCellObj ({mon, fluff}) { return {value: true}; } } export class InitiativeTrackerStatColumn_Custom extends _InitiativeTrackerStatColumnBase { static get POPULATE_WITH () { return ""; } static GROUP = GROUP_CUSTOM; static NAME = "(Custom)"; _getInitialCellObj ({mon, fluff}) { return {value: ""}; } } export class InitiativeTrackerStatColumnFactory { static _COL_CLS_LOOKUP = {}; static _initLookup () { [ InitiativeTrackerStatColumn_HpFormula, InitiativeTrackerStatColumn_ArmorClass, InitiativeTrackerStatColumn_PassivePerception, InitiativeTrackerStatColumn_Speed, InitiativeTrackerStatColumn_SpellDc, InitiativeTrackerStatColumn_Initiative, InitiativeTrackerStatColumn_LegendaryActions, InitiativeTrackerStatColumn_Image, ].forEach(Cls => this._initLookup_addCls(Cls)); Parser.ABIL_ABVS .forEach(abv => { this._initLookup_addCls(class extends InitiativeTrackerStatColumn_Save { static _ATT = abv; }); }); Parser.ABIL_ABVS .forEach(abv => { this._initLookup_addCls(class extends InitiativeTrackerStatColumn_AbilityBonus { static _ATT = abv; }); }); Parser.ABIL_ABVS .forEach(abv => { this._initLookup_addCls(class extends InitiativeTrackerStatColumn_AbilityScore { static _ATT = abv; }); }); Object.keys(Parser.SKILL_TO_ATB_ABV) .sort(SortUtil.ascSort) .forEach(skill => { this._initLookup_addCls(class extends InitiativeTrackerStatColumn_Skill { static _SKILL = skill; }); }); [ InitiativeTrackerStatColumn_Checkbox, InitiativeTrackerStatColumn_CheckboxAutoTurnLow, InitiativeTrackerStatColumn_CheckboxAutoTurnHigh, InitiativeTrackerStatColumn_CheckboxAutoRoundLow, InitiativeTrackerStatColumn_CheckboxAutoRoundHigh, ].forEach(Cls => this._initLookup_addCls(Cls)); } static _initLookup_addCls (Cls) { this._COL_CLS_LOOKUP[Cls.POPULATE_WITH] = Cls; } /* -------------------------------------------- */ static getGroupedByUi () { const out = [ [InitiativeTrackerStatColumn_Custom], ]; let groupPrev = GROUP_CUSTOM; Object.values(this._COL_CLS_LOOKUP) .forEach(Cls => { if (groupPrev !== Cls.GROUP) out.push([]); out.last().push(Cls); groupPrev = Cls.GROUP; }); return out; } /* -------------------------------------------- */ /** * @param dataSerial * @param data * @return {_InitiativeTrackerStatColumnBase} */ static fromStateData ({dataSerial, data}) { if (dataSerial && data) throw new Error(`Only one of "dataSerial" and "data" may be provided!`); data = data ?? InitiativeTrackerStatColumnDataSerializer.fromSerial(dataSerial); const Cls = this._COL_CLS_LOOKUP[data.populateWith] ?? InitiativeTrackerStatColumn_Custom; return new Cls(data); } /** * @param colName * @return {_InitiativeTrackerStatColumnBase} */ static fromEncounterAdvancedColName ({colName}) { colName = colName.toLowerCase().trim(); const Cls = Object.values(this._COL_CLS_LOOKUP) .find(Cls => Cls.ABV_DEFAULT.toLowerCase() === colName) || InitiativeTrackerStatColumn_Custom; return new Cls({ id: CryptUtil.uid(), isEditable: true, isPlayerVisible: IS_PLAYER_VISIBLE_PLAYER_UNITS_ONLY, abbreviation: colName, }); } /** * @param populateWith * @return {_InitiativeTrackerStatColumnBase} */ static fromPopulateWith ({populateWith}) { const Cls = this._COL_CLS_LOOKUP[populateWith] ?? InitiativeTrackerStatColumn_Custom; return new Cls({}); } /** * @param data * @return {_InitiativeTrackerStatColumnBase} */ static fromCollectionRowStateData ({data}) { const flat = {id: data.id, ...data.entity}; return this.fromStateData({data: flat}); } } InitiativeTrackerStatColumnFactory._initLookup();