Skip to content

Commit

Permalink
feat: Adding channel with a V A P O R W A V E scenery
Browse files Browse the repository at this point in the history
  • Loading branch information
SushiElemental committed Jan 6, 2024
1 parent b94f25d commit 1553ed6
Show file tree
Hide file tree
Showing 6 changed files with 1,017 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/channels/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ import './dragon-warrior';
import './ff-shop';
import './template';
import './papers-please';
import './laser-sunset';

export * from './channels';
84 changes: 84 additions & 0 deletions src/channels/laser-sunset/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* @file Configuration values - mainly for changing colors, fps, animations.
* Many of the settings are now balanced to look good enough and will break if put to extremes.
* Handle with care.
*/

const CONFIG = {
Donations: {
despawnMs: 5000,
fontSizeMin: 20,
fontSizeMax: 50,
lockTimeMs: 100,
colors: [
'cyan',
'magenta',
'orchid',
'blueviolet',
'mediumpurple',
'mediumvioletred',
'mediumspringgreen',
'deepskyblue',
],
},
Subscriptions: {
despawnMs: 6000,
spawnPercent: -10,
despawnPercent: 110,
slowdownPercent: 48,
speedupPercent: 42,
topMinPercent: 5,
topMaxPercent: 80,
lockTimeMs: 500,
minSize: 0.6,
maxSize: 1,
maxMonths: 12,
colors: ['ghostwhite', 'mintcream', 'pink', 'lavender', 'hotpink', 'salmon', 'mistyrose'],
},
Stars: {
countX: 10,
countY: 7,
brightnessMin: 120,
brightnessMax: 240,
opacityMin: 0.3,
opacityMax: 1.0,
twinkleMs: 400,
},
Lasers: {
bgXstart: 67,
bgXmin: -53,
scaleXdefault: 2,
},
Cloud: {
despawnMs: 10000,
xMargin: 5,
sizeMinPercent: 3,
sizeMaxPercent: 20,
sizeDonationMin: 5,
sizeDonationMax: 1000,
perspectiveGrowth: 2.4,
colors: ['magenta', 'orchid', 'blueviolet', 'mediumpurple'],
},
Thumping: {
scale: 1.05,
intervalMs: 800,
durationMs: 90,
},
Timers: {
fpsInterval: 1000 / 60,
},
Titles: {
// See https://en.wikipedia.org/wiki/Combining_Diacritical_Marks
diacriticalMarks: [
'', // none
'\u0307', // ̇ Dot
'\u0320', // ̠ Minus sign below
'\u032A', // ̪ Bridge below
'\u0332', // ̲ Low line
'\u0333', // ̳ Double low line
'\u0359', // ͙ Asterisk below
],
},
};

export default CONFIG;
228 changes: 228 additions & 0 deletions src/channels/laser-sunset/functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import type { FormattedDonation, TwitchSubscription } from '@gdq/types/tracker';
import { StarVisual, Overcast, DonationPopup, SubscriptionVisual } from './types';
import CONFIG from './config';

export const formatCurrency = (val: number) => {
return val.toLocaleString('en-US', {
style: 'currency',
currency: 'USD',
maximumFractionDigits: 2,
});
};

export const vaporifyText = (text: string) => {
const marks = CONFIG.Titles.diacriticalMarks;
return text
.split('')
.map((character) => character.replace(/([^\s])/i, (c) => c + marks[c.charCodeAt(0) % marks.length]))
.join('');
};

export const easeIn = (number: number, total: number, scaleMin = 1.0, scaleMax = 1.0) => {
return scaleMin + (number / total) * (scaleMax - scaleMin);
};

export const easeOut = (number: number, total: number, scaleMin = 1.0, scaleMax = 1.0) => {
return scaleMin + (scaleMax - scaleMin) * easeIn(number, total, 0.0, 1.0);
};

export const randomRange = (min: number, max: number) => {
return min + Math.random() * (max - min);
};

export const subscriptionFlyby = (number: number, total: number, x0: number, x1: number, x2: number, x3: number) => {
const progress = number / total;
return (
Math.pow(1 - progress, 3) * x0 +
3 * Math.pow(1 - progress, 2) * progress * x1 +
3 * (1 - progress) * Math.pow(progress, 2) * x2 +
Math.pow(progress, 3) * x3
);
};

export const randomStarOpacity = () => {
return randomRange(CONFIG.Stars.opacityMin, CONFIG.Stars.opacityMax);
};

export const spawnStar = (number: number, xMin: number, xMax: number, yMin: number, yMax: number): StarVisual => {
const clr = randomRange(CONFIG.Stars.brightnessMin, CONFIG.Stars.brightnessMax);
const opacity = randomStarOpacity();

return {
left: randomRange(xMin, xMax),
top: randomRange(yMin, yMax),
text: number % 2 == 0 ? '+' : '*',
color: 'RGB(' + clr + ',' + clr + ',' + clr + ')',
opacity: opacity,
};
};

export const starStyle = (star: StarVisual) => {
return {
left: star.left + '%',
top: star.top + '%',
color: star.color,
opacity: star.opacity,
};
};

export const spawnDonation = (baseProps: FormattedDonation, count: number): DonationPopup => {
const angleDegs = count % 2 ? (count % 36) * 10 : ((count + 18) % 36) * 10;
const angleRads = (angleDegs / 360) * Math.PI * 2.0;

return {
...baseProps,
renderedAmount: formatCurrency(baseProps.rawAmount),
angle: angleRads,
radius: 2 + Math.random() * 5,
color: CONFIG.Donations.colors[count % CONFIG.Donations.colors.length],
received: new Date(),
};
};

export const spawnCloud = (donationAmount: number, count: number): Overcast => {
const clamped = Math.min(Math.max(donationAmount, CONFIG.Cloud.sizeDonationMin), CONFIG.Cloud.sizeDonationMax);
const amountRatio = (clamped - CONFIG.Cloud.sizeDonationMin) / CONFIG.Cloud.sizeDonationMax;
const sizeMin = CONFIG.Cloud.sizeMinPercent;
const sizeMax = CONFIG.Cloud.sizeMaxPercent;
const sideLength = sizeMin + amountRatio * (sizeMax - sizeMin);

return {
left: CONFIG.Cloud.xMargin + Math.random() * (100 - CONFIG.Cloud.xMargin * 2),
width: sideLength,
height: sideLength * 1.5,
backgroundColor: CONFIG.Cloud.colors[count % CONFIG.Cloud.colors.length],
received: new Date(),
};
};

export const cloudScreenspaceProps = (cloud: Overcast) => {
const now = new Date();
const timeVisible = Math.min(now.getTime() - cloud.received.getTime(), CONFIG.Cloud.despawnMs);
const ageRatio = timeVisible / CONFIG.Cloud.despawnMs;
const visibilityWindow = 100 + CONFIG.Cloud.sizeMaxPercent;
const age = ageRatio * visibilityWindow;

return {
left: cloud.left,
bottom: age,
width: cloud.width + cloud.width * ageRatio * CONFIG.Cloud.perspectiveGrowth,
height: cloud.height,
backgroundColor: cloud.backgroundColor,
now: now,
age: age,
rotate: age * 28,
};
};

export const cloudStyle = (cloud: Overcast) => {
const csp = cloudScreenspaceProps(cloud);

return {
left: csp.left + '%',
bottom: csp.bottom + '%',
width: csp.width + '%',
height: csp.height + '%',
backgroundColor: csp.backgroundColor,
rotate: 'x -' + csp.rotate + 'deg',
};
};

export const cloudReflectionStyle = (cloud: Overcast) => {
const csp = cloudScreenspaceProps(cloud);

return {
left: csp.left + '%',
top: csp.bottom + '%',
width: csp.width + '%',
height: csp.height + '%',
backgroundColor: csp.backgroundColor,
rotate: 'x ' + csp.rotate + 'deg',
};
};

export const donationScreenspaceProps = (donation: DonationPopup) => {
const now = new Date();
const age = now.getTime() - donation.received.getTime();
const radius = donation.radius + age / 50.0;

return {
x: 50 + Math.cos(donation.angle) * radius,
y: 50 + Math.sin(donation.angle) * radius,
now: now,
age: age,
radius: radius,
color: donation.color,
fontSize: easeIn(age, CONFIG.Donations.despawnMs, CONFIG.Donations.fontSizeMin, CONFIG.Donations.fontSizeMax),
};
};

export const donationStyle = (donation: DonationPopup) => {
const dsp = donationScreenspaceProps(donation);
return { left: dsp.x + '%', top: dsp.y + '%', fontSize: dsp.fontSize + 'px', color: dsp.color };
};

export const donationReflectionStyle = (donation: DonationPopup) => {
const dsp = donationScreenspaceProps(donation);
const age = dsp.age * 2;
const pushDown = 10 + (age / CONFIG.Donations.despawnMs) * 120;
return {
...donation,
left: dsp.x + '%',
top: dsp.radius + pushDown + '%',
};
};

export const spawnSubscription = (sub: TwitchSubscription, count: number): SubscriptionVisual => {
// The y position is based on the months subscribed. When I tested subscriptions they always had "months" set to 1.
// To test layout use months value below based on number of subscriptions:
// const months = Math.floor(count) % CONFIG.Subscriptions.maxMonths;
const months = Math.min(sub.months, CONFIG.Subscriptions.maxMonths);
const ageRatio = months / CONFIG.Subscriptions.maxMonths;
const top = CONFIG.Subscriptions.topMinPercent + ageRatio * CONFIG.Subscriptions.topMaxPercent;

return {
left: CONFIG.Subscriptions.spawnPercent,
top: top,
zoom:
CONFIG.Subscriptions.minSize + (top / 100) * (CONFIG.Subscriptions.maxSize - CONFIG.Subscriptions.minSize),
user_name: sub.user_name,
display_name: sub.display_name,
months: sub.months,
context: sub.context,
color: CONFIG.Subscriptions.colors[count % CONFIG.Subscriptions.colors.length],
received: new Date(),
};
};

export const subscriptionScreenspaceProps = (sub: SubscriptionVisual) => {
const now = new Date();
const age = now.getTime() - sub.received.getTime();

return {
now: now,
age: age,
left: subscriptionFlyby(
age,
CONFIG.Subscriptions.despawnMs,
CONFIG.Subscriptions.spawnPercent,
CONFIG.Subscriptions.slowdownPercent,
CONFIG.Subscriptions.speedupPercent,
CONFIG.Subscriptions.despawnPercent,
),
top: sub.top,
zoom: sub.zoom,
color: sub.color,
};
};

export const subscriptionStyle = (sub: SubscriptionVisual) => {
const ssp = subscriptionScreenspaceProps(sub);
return {
left: ssp.left + '%',
top: ssp.top + '%',
color: ssp.color,
zIndex: Math.floor(ssp.top),
zoom: ssp.zoom,
};
};
Loading

0 comments on commit 1553ed6

Please sign in to comment.