Skip to content

Commit c6bc694

Browse files
authored
Merge pull request #2170 from keithcurtis1/master
Fade 1.0.0
2 parents 0472aca + 4c252e1 commit c6bc694

File tree

4 files changed

+294
-0
lines changed

4 files changed

+294
-0
lines changed

Fade/1.0.0/Fade.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
var API_Meta = API_Meta||{};
2+
API_Meta.Fade={offset:Number.MAX_SAFE_INTEGER,lineCount:-1};
3+
{try{throw new Error('');}catch(e){API_Meta.Fade.offset=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-3);}}
4+
5+
// Fade — Smooth opacity fading for Roll20 graphics
6+
// !fade --in[|time] [--all]
7+
// !fade --out[|time] [--all]
8+
9+
on('ready', () => {
10+
11+
const version = '1.0.0'; //version number set here
12+
log('-=> Fade v' + version + ' is loaded. Command !fade --|<in/out>|<number of seconds> <--all>.');
13+
14+
15+
on('chat:message', (msg) => {
16+
if (msg.type !== 'api' || !msg.content.startsWith('!fade')) return;
17+
18+
const player = getObj('player', msg.playerid);
19+
const args = msg.content.split(/\s+--/).slice(1).map(a => a.trim()); //better
20+
21+
22+
const FADE_STEPS = 20;
23+
let activeIntervals = [];
24+
25+
// Parse arguments
26+
let fadeIn = false;
27+
let fadeOut = false;
28+
let fadeTime = 1;
29+
let affectAll = false;
30+
31+
args.forEach(arg => {
32+
const [keyRaw, valueRaw] = arg.split('|');
33+
const key = (keyRaw || '').trim().toLowerCase();
34+
const value = valueRaw ? valueRaw.trim() : undefined;
35+
36+
if (key === 'in') {
37+
fadeIn = true;
38+
fadeTime = value ? parseFloat(value) : 1;
39+
} else if (key === 'out') {
40+
fadeOut = true;
41+
fadeTime = value ? parseFloat(value) : 1;
42+
} else if (key === 'all') {
43+
affectAll = true;
44+
}
45+
});
46+
// Defensive checks
47+
if (!fadeIn && !fadeOut) {
48+
sendChat('Fade', `/w "${player.get('displayname')}" You must specify --in or --out.`);
49+
return;
50+
}
51+
52+
const targetOpacity = fadeIn ? 1.0 : 0.0;
53+
let pageId =
54+
(player && player.get('lastpage')) ||
55+
Campaign().get('playerpageid');
56+
57+
if (!affectAll && (!msg.selected || msg.selected.length === 0)) {
58+
sendChat('Fade', `/w "${player.get('displayname')}" No graphics selected. Use --all to affect the entire page.`);
59+
return;
60+
}
61+
62+
if (affectAll && !pageId) {
63+
sendChat('Fade', `/w "${player.get('displayname')}" Could not determine current page.`);
64+
return;
65+
}
66+
67+
68+
// Collect target graphics
69+
let targets = affectAll
70+
? findObjs({ _pageid: pageId, _type: 'graphic' }) || []
71+
: msg.selected
72+
.map(sel => getObj(sel._type, sel._id))
73+
.filter(obj => obj && obj.get('type') === 'graphic');
74+
75+
if (targets.length === 0) {
76+
sendChat('Fade', `/w "${player.get('displayname')}" No valid graphics found to fade.`);
77+
return;
78+
}
79+
80+
const stepInterval = (fadeTime * 1000) / FADE_STEPS;
81+
82+
// Stop any active fades
83+
activeIntervals.forEach(interval => clearInterval(interval));
84+
activeIntervals = [];
85+
86+
// Precompute fixed per-graphic fade steps
87+
const fadeData = targets.map(g => {
88+
const start = parseFloat(g.get('baseOpacity')) || 0;
89+
const diff = targetOpacity - start;
90+
return {
91+
g,
92+
start,
93+
step: diff / FADE_STEPS,
94+
currentStep: 0
95+
};
96+
}).filter(fd => Math.abs(fd.step) > 0.0001); // skip already at target
97+
98+
if (fadeData.length === 0) return;
99+
100+
const intervalId = setInterval(() => {
101+
let done = true;
102+
103+
fadeData.forEach(fd => {
104+
if (fd.currentStep < FADE_STEPS) {
105+
const newVal = fd.start + fd.step * (fd.currentStep + 1);
106+
fd.g.set('baseOpacity', Math.max(0, Math.min(1, newVal)));
107+
fd.currentStep++;
108+
done = false;
109+
} else {
110+
fd.g.set('baseOpacity', targetOpacity);
111+
}
112+
});
113+
114+
if (done) {
115+
clearInterval(intervalId);
116+
activeIntervals = activeIntervals.filter(id => id !== intervalId);
117+
}
118+
}, stepInterval);
119+
120+
activeIntervals.push(intervalId);
121+
});
122+
});
123+
{ try { throw new Error(''); } catch (e) { API_Meta.Fade.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.Fade.offset); } }

Fade/Fade.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
var API_Meta = API_Meta||{};
2+
API_Meta.Fade={offset:Number.MAX_SAFE_INTEGER,lineCount:-1};
3+
{try{throw new Error('');}catch(e){API_Meta.Fade.offset=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-3);}}
4+
5+
// Fade — Smooth opacity fading for Roll20 graphics
6+
// !fade --in[|time] [--all]
7+
// !fade --out[|time] [--all]
8+
9+
on('ready', () => {
10+
11+
const version = '1.0.0'; //version number set here
12+
log('-=> Fade v' + version + ' is loaded. Command !fade --|<in/out>|<number of seconds> <--all>.');
13+
14+
15+
on('chat:message', (msg) => {
16+
if (msg.type !== 'api' || !msg.content.startsWith('!fade')) return;
17+
18+
const player = getObj('player', msg.playerid);
19+
const args = msg.content.split(/\s+--/).slice(1).map(a => a.trim()); //better
20+
21+
22+
const FADE_STEPS = 20;
23+
let activeIntervals = [];
24+
25+
// Parse arguments
26+
let fadeIn = false;
27+
let fadeOut = false;
28+
let fadeTime = 1;
29+
let affectAll = false;
30+
31+
args.forEach(arg => {
32+
const [keyRaw, valueRaw] = arg.split('|');
33+
const key = (keyRaw || '').trim().toLowerCase();
34+
const value = valueRaw ? valueRaw.trim() : undefined;
35+
36+
if (key === 'in') {
37+
fadeIn = true;
38+
fadeTime = value ? parseFloat(value) : 1;
39+
} else if (key === 'out') {
40+
fadeOut = true;
41+
fadeTime = value ? parseFloat(value) : 1;
42+
} else if (key === 'all') {
43+
affectAll = true;
44+
}
45+
});
46+
// Defensive checks
47+
if (!fadeIn && !fadeOut) {
48+
sendChat('Fade', `/w "${player.get('displayname')}" You must specify --in or --out.`);
49+
return;
50+
}
51+
52+
const targetOpacity = fadeIn ? 1.0 : 0.0;
53+
let pageId =
54+
(player && player.get('lastpage')) ||
55+
Campaign().get('playerpageid');
56+
57+
if (!affectAll && (!msg.selected || msg.selected.length === 0)) {
58+
sendChat('Fade', `/w "${player.get('displayname')}" No graphics selected. Use --all to affect the entire page.`);
59+
return;
60+
}
61+
62+
if (affectAll && !pageId) {
63+
sendChat('Fade', `/w "${player.get('displayname')}" Could not determine current page.`);
64+
return;
65+
}
66+
67+
68+
// Collect target graphics
69+
let targets = affectAll
70+
? findObjs({ _pageid: pageId, _type: 'graphic' }) || []
71+
: msg.selected
72+
.map(sel => getObj(sel._type, sel._id))
73+
.filter(obj => obj && obj.get('type') === 'graphic');
74+
75+
if (targets.length === 0) {
76+
sendChat('Fade', `/w "${player.get('displayname')}" No valid graphics found to fade.`);
77+
return;
78+
}
79+
80+
const stepInterval = (fadeTime * 1000) / FADE_STEPS;
81+
82+
// Stop any active fades
83+
activeIntervals.forEach(interval => clearInterval(interval));
84+
activeIntervals = [];
85+
86+
// Precompute fixed per-graphic fade steps
87+
const fadeData = targets.map(g => {
88+
const start = parseFloat(g.get('baseOpacity')) || 0;
89+
const diff = targetOpacity - start;
90+
return {
91+
g,
92+
start,
93+
step: diff / FADE_STEPS,
94+
currentStep: 0
95+
};
96+
}).filter(fd => Math.abs(fd.step) > 0.0001); // skip already at target
97+
98+
if (fadeData.length === 0) return;
99+
100+
const intervalId = setInterval(() => {
101+
let done = true;
102+
103+
fadeData.forEach(fd => {
104+
if (fd.currentStep < FADE_STEPS) {
105+
const newVal = fd.start + fd.step * (fd.currentStep + 1);
106+
fd.g.set('baseOpacity', Math.max(0, Math.min(1, newVal)));
107+
fd.currentStep++;
108+
done = false;
109+
} else {
110+
fd.g.set('baseOpacity', targetOpacity);
111+
}
112+
});
113+
114+
if (done) {
115+
clearInterval(intervalId);
116+
activeIntervals = activeIntervals.filter(id => id !== intervalId);
117+
}
118+
}, stepInterval);
119+
120+
activeIntervals.push(intervalId);
121+
});
122+
});
123+
{ try { throw new Error(''); } catch (e) { API_Meta.Fade.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.Fade.offset); } }

Fade/readme.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Fade
2+
3+
Fade smoothly transitions graphics between 0% and 100% opacity over a specified time.
4+
5+
---
6+
7+
## Commands
8+
9+
!fade --in|<seconds>
10+
!fade --out|<seconds>
11+
!fade --in --all
12+
!fade --out --all
13+
14+
**<seconds>** is optional (default: 1).
15+
**--all** affects all graphics on the current page.
16+
17+
### Examples
18+
!fade --out|5 → Fade selected graphics to 0% over 5 seconds
19+
!fade --in|3 → Fade selected graphics to 100% over 3 seconds
20+
!fade --in --all → Fade in all graphics on the page over 1 second
21+
22+
---
23+
24+
## Features
25+
- All graphics fade simultaneously
26+
- Works on all layers
27+
- Ignores graphics already at target opacity
28+
- Silent operation (no chat spam)
29+
30+
---
31+
32+
## Usage Notes
33+
- If no graphics are selected, `--all` is required.
34+
- Page detection uses the player's last viewed page or the GM's active page.

Fade/script.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "Fade",
3+
"script": "Fade.js",
4+
"version": "1.0.0",
5+
"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.",
6+
"authors": "Keith Curtis",
7+
"roll20userid": "162065",
8+
"dependencies": [],
9+
"modifies": {
10+
"graphic": "write"
11+
},
12+
"conflicts": [],
13+
"previousversions": []
14+
}

0 commit comments

Comments
 (0)