diff --git a/RolemasterUnified_Official/rolemasterunified.css b/RolemasterUnified_Official/rolemasterunified.css index 8eb083b086..7f35e9b0fa 100644 --- a/RolemasterUnified_Official/rolemasterunified.css +++ b/RolemasterUnified_Official/rolemasterunified.css @@ -1706,6 +1706,12 @@ div.attacks { } + +img.avatar { + width: 4em; + height: 4em; +} + .repitem:nth-of-type(even) .trackedcharacter { background-color: var(--fancylightcolor); } diff --git a/RolemasterUnified_Official/rolemasterunified.html b/RolemasterUnified_Official/rolemasterunified.html index 087db5b2ba..d9523bc6a5 100644 --- a/RolemasterUnified_Official/rolemasterunified.html +++ b/RolemasterUnified_Official/rolemasterunified.html @@ -151,7 +151,7 @@ - +
{{whom}} added to template.
@@ -2617,7 +2617,7 @@ Lists Selected / 6 @@ -3002,12 +3002,15 @@ Lists Selected / 6
+ + +
@@ -3015,10 +3018,74 @@ Lists Selected / 6 -
-
+
+
+ Token + + + +
+ +
+
+ + +
+ +
+
+ + + + + + + Chan ; + Ess ; + Ment ; + Arcane ; + Fear; + Phys ; + + + AT (DB) + + + Parry + Other + DB Mod + + Last Init: + +
+
+ + +
+
+ + @@ -3122,11 +3189,11 @@ Lists Selected / 6
- Chan ; - Ess ; - Ment ; - Fear; - Phys ; + Chan ; + Ess ; + Ment ; + Fear; + Phys ;
@@ -6683,8 +6750,12 @@ bleed and penalties you need side and location. A Z ends the list.

Tokens

-

Active Token ID: | -Target Character ID:

+

Active Token ID: | +Target Character ID: | +Target Listeners: | +Tracker +Tracker Prefix +


@@ -6793,7 +6864,7 @@ Sheet Version:
-Revision c87c395f3b03746b0e9462fe1b395a3196849103 +Revision bd680cf8d34be84dc8dbebb09ccac98380a3450a
@@ -8380,6 +8451,54 @@ function onCheck(str, func) { on(str, func); } +/** Sets the character ID and returns the current one.*/ +function setActiveCharacterId(id) { + const ocid = getActiveCharacterId(); + const data = { + id: '0', + type: 'setActiveCharacter', + data: id, + }; + if (self.dispatchEvent) { + const event = new CustomEvent('message'); + event.data = data; + self.dispatchEvent(event); + } + else { + self.onmessage({ data }); + } + return ocid; +} + +function asCharacter(id, func) { + const origid = setActiveCharacterId(id); + const rv = func() + setActiveCharacterId(origid); + return rv; +} + + +function rmuSetAttrsRemote(characterid, attrs, done) { + // FIXME: Validate the damn args + const myid = setActiveCharacterId(characterid); + if (myid == characterid) { + rmuerror("Set active and current character to the same", characterid); + } + rmuSetAttrs(attrs, done); + setActiveCharacterId(myid); +} + +function rmuGetAttrsRemote(characterid, attrs, done) { + // FIXME: Validate the damn args + const myid = setActiveCharacterId(characterid); + if (myid == characterid) { + rmuerror("Set active and current character to the same", characterid); + } + rmuGetAttrs(attrs, done); + setActiveCharacterId(myid); +} + + onCheck("clicked:resetworkers", () => { pendingReset(); }); @@ -8445,6 +8564,7 @@ const knownproperties = { // Roll20 Meta Stuff selectedcharacterid: { type: "string" }, + target_listeners: { type: "string" }, }; function validateAttributes(attrs) { @@ -8502,13 +8622,13 @@ function VrmuSetAttrs(arg, fn) { } function rmuGetAttrs(toget, handler) { - validateAttributes(toget); + //validateAttributes(toget); return getAttrs(toget,handler); } function rmuSetAttrs(toset, handler) { - validateAttributes(toset); + //validateAttributes(toset); return setAttrs(toset, handler); } @@ -9023,7 +9143,7 @@ function lookupSkillResult(skill, skillinfo, umroll, total) { update[stat.aname + "_info"] = logmessage; // Update local cache statCache_[stat.abbr] = statBonus; - setAttrs(update); + //setAttrs(update); }); function updateAllSkills() { @@ -18698,6 +18818,7 @@ function doRollInitiative() { finishRoll(roll.rollId, {}); const activetokenid = attacks.getDataFromRoll(roll.results.activetokenid.expression); const selectedcharacterid = attacks.getDataFromRoll(roll.results.selectedcharacterid.expression); + rmuSetAttrs({lastinitiative: roll.results.total.result}); const mycharacterid = getActiveCharacterId() // So are we the selected token? If so, update the token id console.log("Selected Char ID/My Char ID/Active Token ID",selectedcharacterid, mycharacterid, activetokenid) @@ -18706,10 +18827,25 @@ function doRollInitiative() { rmuSetAttrs({ activetokenid : activetokenid }); console.log("My tokenid is", activetokenid); } - }); } +function doRollInitiativeNoTracker() { + // roll20 treats floor as round to negative infinity, so ceil since they should be negative + startRoll('@{whispertype} &{template:rmuinit} [[ [[ 2d10 ]] + @{initiative} + [[ceil(@{statuspenalty} / 10)]] ]] ' + + '{\{actor=@{character_name} }} ' + + '{\{bonus=@{initiative} }} ' + + '{\{total=$[[2]]}} {\{roll=$[[0]]}} ' + + '{\{maneuver=$[[1]] }}', + (roll) => { + rmuSetAttrs({lastinitiative: roll.results.total.result}); + finishRoll(roll.rollId, {}); + }); +} + + + + /**/ function updatetokenmarkers() { console.log("updatetokenmarkers start"); @@ -20544,6 +20680,41 @@ function messageHackery() { let tracker = {} +tracker.repeatingprefix = "repeating_trackercharacter"; + +/* +Movement +Attacks +Movement skills +Footwork +Spells. +Initiative +Perception*/ + +tracker.allAttributes = [ + "hp", "hp_max", "pp", "pp_max", + "hp_percent", "pp_percent", + "character_token", + "character_name", + "actionselect", + "aptrack_loaded", + "aptrack", + "lastinitiative", + "rr_channeling_bonus", + "rr_arcane_bonus", + "rr_essence_bonus", + "rr_mentalism_bonus", + "rr_fear_bonus", + "rr_physical_bonus", + "parry_mod", + "other_mod", + "dbmod", + "activedb", + "at" +] + + + onCheck("clicked:trackedreturntocharacter", () => { setAttrs({sheetselect: "playersheet"}); }); @@ -20553,29 +20724,114 @@ onCheck("clicked:trackerinit", () => { setAttrs({sheetselect: "trackersheet"}); }); + +// Somone clicked the add target button. so lets add them +// We just get the token_id and the character_id first. +// Then we start getting all the other fields. onCheck("clicked:trackeraddcreature", () =>{ - tracker.dataGet('target', generateRowID()); +// tracker.dataGet('target', generateRowID()); + const roll_string = "&{template:rmutrackeradd} [[1d20]] " + + "{\{who=Tracker}} " + + "{\{targettokenid=[[0[@{target|token_id}]]]}} " + + "{\{targetname=[[0[@{target|character_name}]]]}} " + + "{\{targetcharacterid=[[0[@{target|character_id}]]]}}"; + console.log(roll_string); + startRoll(roll_string, roll => { + console.log(roll); + const tokenid = attacks.getDataFromRoll(roll.results.targettokenid.expression); + const characterid = attacks.getDataFromRoll(roll.results.targetcharacterid.expression); + const targetname = attacks.getDataFromRoll(roll.results.targetname.expression); + const updates = { targetname: targetname }; + console.log("Got tokenid and characterid", tokenid, characterid, roll); + + tracker.addTarget(targetname, characterid, tokenid); + finishRoll(roll.rollId, updates); + }); }); -onCheck("clicked:repeating_trackercharacter:trackedrefresh", (ev) =>{ - const basename = ev.sourceAttribute.replace("_trackedrefresh", ""); - let rowid = basename.match("-.*"); - getAttrs([`${basename}_trackedname`], (data) => { - tracker.dataGet(data[`${basename}_trackedname`], rowid); +tracker.appendPrefix = function(prefix, object) { + return Object.fromEntries( + Object.entries(object).map(([key, value]) => [prefix + key, value]) + ); +} + +// So when the AP per phase here triggers, set all the sheets +// This si one way - tracker sets teh other sheets. +onCheck("change:apperphase", (ev) => { + console.log(ev) + tracker.setAllSheets({actionapperphase:ev.newValue}); +}); + +/** + * This is the main call to add items to the tracker. + */ +// FIXME: Should check for duplicates +// FIXME: Should warn if no character id +tracker.addTarget = function(name, characterid, tokenid) { + const rowid = generateRowID(); + const prefix = `repeating_trackercharacter_${rowid}_`; + + // First pass: Lets just add the basic item + const updates = {} + updates.trackedname = name; + updates.tokenid = tokenid; + updates.characterid = characterid; + + console.log(tracker.appendPrefix(prefix, updates)); + setAttrs(tracker.appendPrefix(prefix, updates)); + + const trackerid = getActiveCharacterId(); + + // So as the character I need to let it know what tracker it has + rmuSetAttrsRemote(characterid, {trackerid: trackerid, trackerprefix: prefix}); + + rmuGetAttrsRemote(characterid, tracker.allAttributes, (data) => { + // this call back is in the context of the character. + rmuSetAttrsRemote(trackerid, tracker.appendPrefix(prefix, data)); + }); +} + + +tracker.setAllSheets = function(values) { + tracker.forAllSheets((id) => { + rmuSetAttrsRemote(id, values); + }); +} + +tracker.forAllSheets = function(func) { + getSectionIDs(tracker.repeatingprefix, (ids) => { + const toget = ids.map((x) => tracker.repeatingprefix + "_" + x + "_characterid"); + getAttrs(toget, (characterids) => { + for (row in characterids) { + func(characterids[row]) + } }); + }); +} + +onCheck("clicked:trackerrollinitiative", (_) => { + tracker.forAllSheets((id) => { + asCharacter(id, () => {doRollInitiativeNoTracker()}); + }); }); + +// old call onCheck("clicked:repeating_trackercharacter:deletetracked", (ev) => { const basename = ev.sourceAttribute.replace("_deletetracked", ""); removeRepeatingRow(basename); }); +// old call; now borked onCheck("clicked:repeating_trackercharacter:selecttarget", (ev) => { // We are keeping the last '_' as getTargetData doesn;t know about it const basename = ev.sourceAttribute.replace("selecttarget", ""); attacks.getTargetData("target", basename) -}); + }); + + +// old call const fields = { // Type is the type of data. // Field is what we pull on the source character @@ -20598,6 +20854,7 @@ const fields = { } /**/ +// old call tracker.extractResults = function(roll, prefix) { const results = {} const rr = roll.results; @@ -20620,6 +20877,7 @@ tracker.extractResults = function(roll, prefix) { return results; } +// old call tracker.buildString = function(whom) { let reqs = ["&{template:rmuattacktarget} [[1d20]]"]; for (const field in fields) { @@ -20636,6 +20894,7 @@ tracker.buildString = function(whom) { } +// old call tracker.dataGet = function(whom, rowid) { const roll_string = tracker.buildString(whom); startRoll(roll_string, roll => { @@ -20655,6 +20914,7 @@ tracker.dataGet = function(whom, rowid) { } +// old call tracker.handleSkillString = function(string, updates) { console.log("Handle skill string", string); if (!string || string == '') return; @@ -20675,6 +20935,7 @@ tracker.handleSkillString = function(string, updates) { // updates[`repeat_trackedskill${rowid}_bonus`] = match[2]; } } +// old call tracker.handleMoveString = function(string, updates) { console.log("Handle move string", string); if (!string || string == '') return; @@ -20694,6 +20955,7 @@ tracker.handleMoveString = function(string, updates) { } } +// old call tracker.handleAttackString = function(string, updates) { console.log("Handle attack string", string); if (!string || string == '') return; @@ -20706,6 +20968,33 @@ tracker.handleAttackString = function(string, updates) { } +/** + * Sheet Events + * + * These should all be triggered in the context of the sheet. + * And then send the data to the tracker, if any. + */ +onCheck("change:lastinitiative", () => { + getAttrs(["lastinitiative", "trackerid", "trackerprefix"], (data) => { + if (data.trackerid && data.trackerprefix) { + rmuSetAttrsRemote(data.trackerid, + { [data.trackerprefix + "lastinitiative"] : data.lastinitiative }); + } + }); +}); + +for (attr of tracker.allAttributes) { + onCheck("change:" + attr, (ev) => { + getAttrs(["trackerid", "trackerprefix"], (data) => { + if (data.trackerid && data.trackerprefix) { + rmuSetAttrsRemote(data.trackerid, + { [data.trackerprefix + ev.sourceAttribute] : ev.newValue } + ); + } + }); + }); +} + @@ -20807,6 +21096,65 @@ onCheck("clicked:initiative-action", (ev) => { // vim: set sw=2 sts=2 syn=javascript : +/**/ + +const target = {} + + +onCheck("change:target_characterid", (change) => { + console.log("Change of target characgter id", change); + if (change.newValue == change.previousValue) { + return; + } + + let oldidtarget = change.oldValue; + let newidtarget = change.newValue; + const myid = getActiveCharacterId(); + + // Remove myself from the old list + /* + rmuGetAttrsRemote(oldidtarget, ["target_listeners"], (remote) => { + const result = remote.target_listeners + .split(",") + .filter(item => item !== myid) + .join(","); + setAttrs({target_listeners: result}); + });*/ + + // Add myself to the new list + + rmuGetAttrsRemote(newidtarget, ["target_listeners"], (remote) => { + let result = remote.target_listeners + if (result) { + result += ',' + myid; + } else { + result = myid; + } + setAttrs({target_listeners: result}) + }); +}); + +onCheck("change:db", (db) => { + getAttrs(["target_listeners"], (listen) => { + console.log("db change", db, listen); + if (!listen.target_listeners || listen.target_listeners.length < 2) { + console.log("Seems empty; stopping"); + return; + } + listen.target_listeners.split(",").forEach((who) => { + rmuSetAttrsRemote(who, {target_db: db.newValue}); + }); + + + }); +}); + + + + + +/* vim:set sw=4 sts=4 syn=javascript : */ + function getStatFromAName(aname) { diff --git a/RolemasterUnified_Official/sheet.json b/RolemasterUnified_Official/sheet.json index 188e1bb98a..c332e3e15e 100644 --- a/RolemasterUnified_Official/sheet.json +++ b/RolemasterUnified_Official/sheet.json @@ -123,5 +123,5 @@ "description": "Set the version of the sheet in use. You typically want latest" } ], - "version": "1767856195" + "version": "1768234574" } diff --git a/RolemasterUnified_Official/updates.md b/RolemasterUnified_Official/updates.md index 3a40611d1c..0cfbbfa7c0 100644 --- a/RolemasterUnified_Official/updates.md +++ b/RolemasterUnified_Official/updates.md @@ -1,6 +1,22 @@ +# 2026-01-15 + +- Add tracker back +- Add general "asCharacter" helpers +- Tracker: + - Can add data using new as character + - Characters now remember their tracker + - for All Characters methods to tracker + - Tracker can set everyones AP/round message + - Tracker can roll everyones initiative (not to turn tracker though) + - Track base attributes +- Add option for non-tracker initiative. +- Clean up build so compiled/processed files go to build + # 2026-01-08 - Fix for creatures max PP +- Fix fetch for targetted creatures +- Partial fix for grapple # 2026-01-06