Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
123 changes: 123 additions & 0 deletions Fade/1.0.0/Fade.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
var API_Meta = API_Meta||{};
API_Meta.Fade={offset:Number.MAX_SAFE_INTEGER,lineCount:-1};
{try{throw new Error('');}catch(e){API_Meta.Fade.offset=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-3);}}

// Fade — Smooth opacity fading for Roll20 graphics
// !fade --in[|time] [--all]
// !fade --out[|time] [--all]

on('ready', () => {

const version = '1.0.0'; //version number set here
log('-=> Fade v' + version + ' is loaded. Command !fade --|<in/out>|<number of seconds> <--all>.');


on('chat:message', (msg) => {
if (msg.type !== 'api' || !msg.content.startsWith('!fade')) return;

const player = getObj('player', msg.playerid);
const args = msg.content.split(/\s+--/).slice(1).map(a => a.trim()); //better


const FADE_STEPS = 20;
let activeIntervals = [];

// Parse arguments
let fadeIn = false;
let fadeOut = false;
let fadeTime = 1;
let affectAll = false;

args.forEach(arg => {
const [keyRaw, valueRaw] = arg.split('|');
const key = (keyRaw || '').trim().toLowerCase();
const value = valueRaw ? valueRaw.trim() : undefined;

if (key === 'in') {
fadeIn = true;
fadeTime = value ? parseFloat(value) : 1;
} else if (key === 'out') {
fadeOut = true;
fadeTime = value ? parseFloat(value) : 1;
} else if (key === 'all') {
affectAll = true;
}
});
// Defensive checks
if (!fadeIn && !fadeOut) {
sendChat('Fade', `/w "${player.get('displayname')}" You must specify --in or --out.`);
return;
}

const targetOpacity = fadeIn ? 1.0 : 0.0;
let pageId =
(player && player.get('lastpage')) ||
Campaign().get('playerpageid');

if (!affectAll && (!msg.selected || msg.selected.length === 0)) {
sendChat('Fade', `/w "${player.get('displayname')}" No graphics selected. Use --all to affect the entire page.`);
return;
}

if (affectAll && !pageId) {
sendChat('Fade', `/w "${player.get('displayname')}" Could not determine current page.`);
return;
}


// Collect target graphics
let targets = affectAll
? findObjs({ _pageid: pageId, _type: 'graphic' }) || []
: msg.selected
.map(sel => getObj(sel._type, sel._id))
.filter(obj => obj && obj.get('type') === 'graphic');

if (targets.length === 0) {
sendChat('Fade', `/w "${player.get('displayname')}" No valid graphics found to fade.`);
return;
}

const stepInterval = (fadeTime * 1000) / FADE_STEPS;

// Stop any active fades
activeIntervals.forEach(interval => clearInterval(interval));
activeIntervals = [];

// Precompute fixed per-graphic fade steps
const fadeData = targets.map(g => {
const start = parseFloat(g.get('baseOpacity')) || 0;
const diff = targetOpacity - start;
return {
g,
start,
step: diff / FADE_STEPS,
currentStep: 0
};
}).filter(fd => Math.abs(fd.step) > 0.0001); // skip already at target

if (fadeData.length === 0) return;

const intervalId = setInterval(() => {
let done = true;

fadeData.forEach(fd => {
if (fd.currentStep < FADE_STEPS) {
const newVal = fd.start + fd.step * (fd.currentStep + 1);
fd.g.set('baseOpacity', Math.max(0, Math.min(1, newVal)));
fd.currentStep++;
done = false;
} else {
fd.g.set('baseOpacity', targetOpacity);
}
});

if (done) {
clearInterval(intervalId);
activeIntervals = activeIntervals.filter(id => id !== intervalId);
}
}, stepInterval);

activeIntervals.push(intervalId);
});
});
{ try { throw new Error(''); } catch (e) { API_Meta.Fade.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.Fade.offset); } }
123 changes: 123 additions & 0 deletions Fade/Fade.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
var API_Meta = API_Meta||{};
API_Meta.Fade={offset:Number.MAX_SAFE_INTEGER,lineCount:-1};
{try{throw new Error('');}catch(e){API_Meta.Fade.offset=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-3);}}

// Fade — Smooth opacity fading for Roll20 graphics
// !fade --in[|time] [--all]
// !fade --out[|time] [--all]

on('ready', () => {

const version = '1.0.0'; //version number set here
log('-=> Fade v' + version + ' is loaded. Command !fade --|<in/out>|<number of seconds> <--all>.');


on('chat:message', (msg) => {
if (msg.type !== 'api' || !msg.content.startsWith('!fade')) return;

const player = getObj('player', msg.playerid);
const args = msg.content.split(/\s+--/).slice(1).map(a => a.trim()); //better


const FADE_STEPS = 20;
let activeIntervals = [];

// Parse arguments
let fadeIn = false;
let fadeOut = false;
let fadeTime = 1;
let affectAll = false;

args.forEach(arg => {
const [keyRaw, valueRaw] = arg.split('|');
const key = (keyRaw || '').trim().toLowerCase();
const value = valueRaw ? valueRaw.trim() : undefined;

if (key === 'in') {
fadeIn = true;
fadeTime = value ? parseFloat(value) : 1;
} else if (key === 'out') {
fadeOut = true;
fadeTime = value ? parseFloat(value) : 1;
} else if (key === 'all') {
affectAll = true;
}
});
// Defensive checks
if (!fadeIn && !fadeOut) {
sendChat('Fade', `/w "${player.get('displayname')}" You must specify --in or --out.`);
return;
}

const targetOpacity = fadeIn ? 1.0 : 0.0;
let pageId =
(player && player.get('lastpage')) ||
Campaign().get('playerpageid');

if (!affectAll && (!msg.selected || msg.selected.length === 0)) {
sendChat('Fade', `/w "${player.get('displayname')}" No graphics selected. Use --all to affect the entire page.`);
return;
}

if (affectAll && !pageId) {
sendChat('Fade', `/w "${player.get('displayname')}" Could not determine current page.`);
return;
}


// Collect target graphics
let targets = affectAll
? findObjs({ _pageid: pageId, _type: 'graphic' }) || []
: msg.selected
.map(sel => getObj(sel._type, sel._id))
.filter(obj => obj && obj.get('type') === 'graphic');

if (targets.length === 0) {
sendChat('Fade', `/w "${player.get('displayname')}" No valid graphics found to fade.`);
return;
}

const stepInterval = (fadeTime * 1000) / FADE_STEPS;

// Stop any active fades
activeIntervals.forEach(interval => clearInterval(interval));
activeIntervals = [];

// Precompute fixed per-graphic fade steps
const fadeData = targets.map(g => {
const start = parseFloat(g.get('baseOpacity')) || 0;
const diff = targetOpacity - start;
return {
g,
start,
step: diff / FADE_STEPS,
currentStep: 0
};
}).filter(fd => Math.abs(fd.step) > 0.0001); // skip already at target

if (fadeData.length === 0) return;

const intervalId = setInterval(() => {
let done = true;

fadeData.forEach(fd => {
if (fd.currentStep < FADE_STEPS) {
const newVal = fd.start + fd.step * (fd.currentStep + 1);
fd.g.set('baseOpacity', Math.max(0, Math.min(1, newVal)));
fd.currentStep++;
done = false;
} else {
fd.g.set('baseOpacity', targetOpacity);
}
});

if (done) {
clearInterval(intervalId);
activeIntervals = activeIntervals.filter(id => id !== intervalId);
}
}, stepInterval);

activeIntervals.push(intervalId);
});
});
{ try { throw new Error(''); } catch (e) { API_Meta.Fade.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.Fade.offset); } }
34 changes: 34 additions & 0 deletions Fade/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Fade

Fade smoothly transitions graphics between 0% and 100% opacity over a specified time.

---

## Commands

!fade --in|<seconds>
!fade --out|<seconds>
!fade --in --all
!fade --out --all

**<seconds>** is optional (default: 1).
**--all** affects all graphics on the current page.

### Examples
!fade --out|5 → Fade selected graphics to 0% over 5 seconds
!fade --in|3 → Fade selected graphics to 100% over 3 seconds
!fade --in --all → Fade in all graphics on the page over 1 second

---

## Features
- All graphics fade simultaneously
- Works on all layers
- Ignores graphics already at target opacity
- Silent operation (no chat spam)

---

## Usage Notes
- If no graphics are selected, `--all` is required.
- Page detection uses the player's last viewed page or the GM's active page.
14 changes: 14 additions & 0 deletions Fade/script.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "Fade",
"script": "Fade.js",
"version": "1.0.0",
"description": "# Fade\n\nFade smoothly transitions graphics between 0% and 100% opacity over a specified time.\n\n---\n\n## Commands\n\n```\n!fade --in|<seconds>\n!fade --out|<seconds>\n!fade --in --all\n!fade --out --all\n```\n\n**<seconds>** is optional (default: 1).\n**--all** affects all graphics on the current page.\n\n### Examples\n```\n!fade --out|5 → Fade selected graphics to 0% over 5 seconds\n!fade --in|3 → Fade selected graphics to 100% over 3 seconds\n!fade --in --all → Fade in all graphics on the page over 1 second\n```\n\n---\n\n## Features\n- All graphics fade simultaneously\n- Works on all layers\n- Ignores graphics already at target opacity\n- Silent operation (no chat spam)\n\n---\n\n## Usage Notes\n- If no graphics are selected, `--all` is required.\n- Page detection uses the player's last viewed page or the GM's active page.",
"authors": "Keith Curtis",
"roll20userid": "162065",
"dependencies": [],
"modifies": {
"graphic": "write"
},
"conflicts": [],
"previousversions": []
}
Loading