diff --git a/CHANGELOG.md b/CHANGELOG.md index e03e1692..d528be92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Releases +## Version 4.0.13 [Hero System 6e (Unofficial) v2](https://github.com/dmdorman/hero6e-foundryvtt) + +- Enhanced Senses improvements: + - SIGHTGROUP visions now respect the BLIND status. [#1590](https://github.com/dmdorman/hero6e-foundryvtt/issues/1590) + - Fixed issue with some enhanced visions not showing map in the dark. +- Adjustment powers now work with POWERDEFENSE. + ## Version 4.0.12 [Hero System 6e (Unofficial) v2](https://github.com/dmdorman/hero6e-foundryvtt) - Fix for SPD purchased as a power not contributing to final values during upload. [#1439](https://github.com/dmdorman/hero6e-foundryvtt/issues/1439) diff --git a/module/actor/actor-token.mjs b/module/actor/actor-token.mjs index b8b70dd9..d451d4f2 100644 --- a/module/actor/actor-token.mjs +++ b/module/actor/actor-token.mjs @@ -75,6 +75,8 @@ export class HeroSystem6eTokenDocument extends TokenDocument { if (!this.isOwner) return; + if (!this.id) return; + if (this.sight.visionMode != "basic") { super._prepareDetectionModes(); return; @@ -135,33 +137,40 @@ export class HeroSystem6eTokenDocument extends TokenDocument { //item.system.OPTIONID === undefined && // DETECT item.isActive, ); + + if (this.name === "Onyx") { + console.log(this); + } + if (SIGHTGROUP && !this.actor?.statuses.has("blind")) { const basicMode = this.detectionModes.find((m) => m.id === "basicSight"); basicMode.range = maxRange; this.sight.range = maxRange; // You can see without a light source } - // GENERIC NON-SIGHTGROUP (not including MENTALGROUP which is unsupported) - // const NONSIGHTGROUP = this.actor?.items.find( - // (item) => - // item.isSense && - // item.system.GROUP !== "SIGHTGROUP" && - // item.system.GROUP !== "MENTALGROUP" && - // item.isActive, - // ); + // A special vision that can see the map (like targeting touch) + let blindVisionItem = this.actor?.items.find( + (i) => + i.isActive && + i.isSense && + i.isRangedSense && + (i.isTargeting || ["TOUCHGROUP", "SMELLGROUP"].includes(i.system.GROUP)) && + (!this.token?.actor?.statuses.has("blind") || i.system.GROUP !== "SIGHTGROUP"), + ); + if (blindVisionItem) { + const basicMode = this.detectionModes.find((m) => m.id === "basicSight"); + basicMode.range = maxRange; + this.sight.range = maxRange; // You can see without a light source + } + + // Assume we can use non-targeting senses to detect tokens const heroDetectSight = this.detectionModes.find((m) => m.id === "heroDetectSight"); - // if (SIGHTGROUP || NONSIGHTGROUP) { if (!heroDetectSight) { this.detectionModes.push({ id: "heroDetectSight", enabled: true, range: maxRange }); } else { heroDetectSight.range = maxRange; heroDetectSight.enabled = true; } - // } else { - // if (heroDetectSight) { - // heroDetectSight.enabled = false; - // } - // } // Update Sight so people don't get confused when looking at the UI if (initialRange !== this.sight.range) { diff --git a/module/actor/actor.mjs b/module/actor/actor.mjs index 6b4c5256..163ccb27 100644 --- a/module/actor/actor.mjs +++ b/module/actor/actor.mjs @@ -1974,7 +1974,9 @@ export class HeroSystem6eActor extends Actor { } } - await this.update({ "flags.-=uploading": null }); + if (this.id) { + await this.update({ "flags.-=uploading": null }); + } uploadPerformance.retainedDamage = new Date() - uploadPerformance._d; uploadPerformance._d = new Date(); diff --git a/module/config.mjs b/module/config.mjs index d8780416..bf18741b 100644 --- a/module/config.mjs +++ b/module/config.mjs @@ -5682,7 +5682,7 @@ function addPower(powerDescription6e, powerOverrideFor5e) { let value = 0; switch (options.attackDefenseVs) { case "POWERDEFENSE": - value = parseInt(actorItemDefense.system.LEVELS) || 0; + value = actorItemDefense.adjustedLevels; //parseInt(actorItemDefense.system.LEVELS) || 0; break; } if (value > 0) { diff --git a/module/item/item-attack.mjs b/module/item/item-attack.mjs index 5bc6250c..0a273e17 100644 --- a/module/item/item-attack.mjs +++ b/module/item/item-attack.mjs @@ -1678,7 +1678,7 @@ export async function _onRollDamage(event) { attackTags: getAttackTags(item), targetTokens: targetTokens, user: game.user, - action, + actionData: JSON.stringify(action), }; // render card @@ -1996,9 +1996,10 @@ export async function _onApplyDamageToSpecificToken(event, tokenId) { const originalRoll = heroRoller.clone(); const automation = game.settings.get(HEROSYS.module, "automation"); + const action = damageData.actionData ? JSON.parse(damageData.actionData) : null; if (item.system.XMLID === "ENTANGLE") { - return _onApplyEntangleToSpecificToken(item, token, originalRoll); + return _onApplyEntangleToSpecificToken(item, token, originalRoll, action); } // Target ENTANGLE @@ -2031,7 +2032,7 @@ export async function _onApplyDamageToSpecificToken(event, tokenId) { } if (targetEntangle && entangleAE) { - return _onApplyDamageToEntangle(item, token, originalRoll, entangleAE); + return _onApplyDamageToEntangle(item, token, originalRoll, entangleAE, action); } } @@ -2203,7 +2204,7 @@ export async function _onApplyDamageToSpecificToken(event, tokenId) { item: item, })?.XMLID === "TRANSFORM"; if (transformation) { - return _onApplyTransformationToSpecificToken(item, token, damageDetail, defense, defenseTags); + return _onApplyTransformationToSpecificToken(item, token, damageDetail, defense, defenseTags, action); } // AID, DRAIN or any adjustment powers @@ -2211,7 +2212,7 @@ export async function _onApplyDamageToSpecificToken(event, tokenId) { item: item, })?.type?.includes("adjustment"); if (adjustment) { - return _onApplyAdjustmentToSpecificToken(item, token, damageDetail, defense, defenseTags); + return _onApplyAdjustmentToSpecificToken(item, token, damageDetail, defense, defenseTags, action); } const senseAffecting = getPowerInfo({ @@ -2343,6 +2344,7 @@ export async function _onApplyDamageToSpecificToken(event, tokenId) { tags: defenseTags.filter((o) => !o.options?.knockback), attackTags: getAttackTags(item), targetToken: token, + actionData: JSON.stringify(action), }; // render card @@ -2672,7 +2674,7 @@ async function _onApplyTransformationToSpecificToken(transformationItem, token, ui.notifications.warn("TRANSFORM damage & defenses are not yet implemented."); } -async function _onApplyAdjustmentToSpecificToken(adjustmentItem, token, damageDetail, defense, defenseTags) { +async function _onApplyAdjustmentToSpecificToken(adjustmentItem, token, damageDetail, defense, defenseTags, action) { if ( adjustmentItem.actor.id === token.actor.id && ["DISPEL", "DRAIN", "SUPPRESS", "TRANSFER"].includes(adjustmentItem.system.XMLID) @@ -2765,6 +2767,7 @@ async function _onApplyAdjustmentToSpecificToken(adjustmentItem, token, damageDe damageDetail.effects, false, reductionTargetActor, + action, ), ); } diff --git a/module/item/item.mjs b/module/item/item.mjs index 64c82af2..8d16e262 100644 --- a/module/item/item.mjs +++ b/module/item/item.mjs @@ -688,7 +688,7 @@ export class HeroSystem6eItem extends Item { // If we have control of this token, re-acquire to update movement types const myToken = this.actor?.getActiveTokens()?.[0]; - if (canvas.tokens.controlled.find((t) => t.id == myToken.id)) { + if (canvas.tokens.controlled?.find((t) => t.id == myToken.id)) { myToken.release(); myToken.control(); } @@ -5290,6 +5290,19 @@ export class HeroSystem6eItem extends Item { return RoundFavorPlayerDown(cost) + costSuffix; } + + /// Get Levels with AID/DRAIN Active Effects + get adjustedLevels() { + let _adjustedLevels = parseInt(this.system.LEVELS || 0); + + for (const ae of this.actor.temporaryEffects.filter( + (effect) => effect.flags.XMLID === "DRAIN" && effect.flags.key === "POWERDEFENSE", + )) { + console.log(ae); + _adjustedLevels += parseInt(ae.changes?.[0].value || 0); + } + return Math.max(0, _adjustedLevels); + } } export function getItem(id) { diff --git a/module/utility/adjustment.mjs b/module/utility/adjustment.mjs index dd99aa6b..6d275078 100644 --- a/module/utility/adjustment.mjs +++ b/module/utility/adjustment.mjs @@ -216,7 +216,7 @@ function _findExistingMatchingEffect(item, potentialCharacteristic, powerTargetN return targetSystem.effects.find( (effect) => effect.origin === item.uuid && - effect.flags.target[0] === (potentialCharacteristic || powerTargetName?.uuid), + effect.flags.target[0] === (powerTargetName?.uuid || potentialCharacteristic), ); } @@ -263,13 +263,23 @@ function _createNewAdjustmentEffect( rawActivePointsDamage, targetActor, targetSystem, + action, ) { // Create new ActiveEffect // TODO: Add a document field + + // Educated guess for token + const itemTokenName = + canvas.tokens.get(action?.current?.attackerTokenId)?.name || + item.actor?.getActiveTokens().find((t) => canvas.tokens.controlled.find((c) => c.id === t.id))?.name || + item.actor?.getActiveTokens()?.[0].name || + targetActor.name || + "undefined"; + const activeEffect = { name: `${item.system.XMLID || "undefined"} 0 ${ (targetPower?.name || potentialCharacteristic)?.toUpperCase() // TODO: This will need to change for multiple effects - } (0 AP) [by ${item.actor.name || "undefined"}]`, + } (0 AP) [by ${itemTokenName}]`, id: `${item.system.XMLID}.${item.id}.${ targetPower?.name || potentialCharacteristic // TODO: This will need to change for multiple effects }`, @@ -285,8 +295,10 @@ function _createNewAdjustmentEffect( affectedPoints: 0, XMLID: item.system.XMLID, source: targetActor.name, - target: [potentialCharacteristic || targetPower?.uuid], - key: potentialCharacteristic, + target: [targetPower?.uuid || potentialCharacteristic], + key: targetPower?.system?.XMLID || potentialCharacteristic, + itemTokenName, + attackerTokenId: action?.current?.attackerTokenId, }, origin: item.uuid, //description: item.system.description, // Issues with core FoundryVTT where description doesn't show, nor is editable. @@ -324,6 +336,7 @@ export async function performAdjustment( effectsDescription, isFade, targetActor, + action, ) { const isHealing = item.system.XMLID === "HEALING"; const isOnlyToStartingValues = item.findModsByXmlid("ONLYTOSTARTING") || isHealing; @@ -381,7 +394,9 @@ export async function performAdjustment( // Do we have a target? if (!targetCharacteristic && !targetPower) { - await ui.notifications.error(`${nameOfCharOrPower} is an invalid target for the adjustment power ${item.name}`); + await ui.notifications.warn( + `${nameOfCharOrPower} is an invalid target for the adjustment power ${item.name}. Perhaps ${targetActor.name} does not have that characteristic or power.`, + ); return; } @@ -408,6 +423,7 @@ export async function performAdjustment( thisAttackRawActivePointsDamage, targetActor, targetSystem, + action, ); // Healing doesn't fade @@ -548,10 +564,18 @@ export async function performAdjustment( parseInt(activeEffect.changes[2].value) + (newCalculatedValue - oldCalculatedValue); } + // Educated guess for token + const itemTokenName = + canvas.tokens.get(action?.current?.attackerTokenId)?.name || + item.actor?.getActiveTokens().find((t) => canvas.tokens.controlled.find((c) => c.id === t.id))?.name || + item.actor?.getActiveTokens()?.[0].name || + targetActor.name || + "undefined"; + // Update the effect max value(s) activeEffect.name = `${item.system.XMLID || "undefined"} ${Math.abs(totalActivePointsThatShouldBeAffected)} ${( targetPower?.name || potentialCharacteristic - )?.toUpperCase()} (${Math.abs(totalAdjustmentNewActivePoints)} AP) [by ${item.actor.name || "undefined"}]`; + )?.toUpperCase()} (${Math.abs(totalAdjustmentNewActivePoints)} AP) [by ${itemTokenName}]`; activeEffect.flags.affectedPoints = totalActivePointsThatShouldBeAffected; activeEffect.flags.adjustmentActivePoints = totalAdjustmentNewActivePoints; diff --git a/module/utility/vision.mjs b/module/utility/vision.mjs index 4d54ce86..472d4828 100644 --- a/module/utility/vision.mjs +++ b/module/utility/vision.mjs @@ -2,6 +2,9 @@ import { calculateDistanceBetween } from "./range.mjs"; export class HeroPointVisionSource extends foundry.canvas.sources.PointVisionSource { get isBlinded() { + // if (this.token?.name === "Onyx") { + // debugger; + // } const defaultBlind = (this.data.radius === 0 && (this.data.lightRadius === 0 || !this.visionMode?.perceivesLight)) || Object.values(this.blinded).includes(true); @@ -13,13 +16,15 @@ export class HeroPointVisionSource extends foundry.canvas.sources.PointVisionSou // Some visions have SENSE/RANGE (built in) // SightGroup/ToughGroup/HearingGroup/RadioGroup/SmellGroup have SENSE builtIn // Assuming only SIGHT/TOUCH/SMELL or TARGETING can actually SEE (you can see, touch, smell a wall) - const blindVisionItem = this.token?.actor?.items.find( + let blindVisionItem = this.token?.actor?.items.find( (i) => i.isActive && i.isSense && i.isRangedSense && - (i.isTargeting || ["TOUCHGROUP", "SMELLGROUP"].includes(i.system.GROUP)), + (i.isTargeting || ["TOUCHGROUP", "SMELLGROUP"].includes(i.system.GROUP)) && + (!this.token?.actor?.statuses.has("blind") || i.system.GROUP !== "SIGHTGROUP"), ); + if (blindVisionItem) { //console.log("blindVisionItem", blindVisionItem); return false; @@ -87,7 +92,7 @@ export function setPerceptionModes() { return (filter2.thickness = 1), filter2; } _canDetect(visionSource, target) { - if (super._canDetect(visionSource, target)) return false; // handled by standard vision + if (super._canDetect(visionSource, target)) return true; // handled by standard vision if (!target.document.hidden && !target.document.hasStatusEffect("invisible")) { return true; } diff --git a/templates/chat/item-damage-card.hbs b/templates/chat/item-damage-card.hbs index d200c0c9..1d014c26 100644 --- a/templates/chat/item-damage-card.hbs +++ b/templates/chat/item-damage-card.hbs @@ -40,7 +40,7 @@