Skip to content

Commit

Permalink
feat: add LaserSunset channel (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
SushiElemental authored Mar 3, 2024
1 parent 48852dd commit ea887bb
Show file tree
Hide file tree
Showing 6 changed files with 859 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 @@ -17,6 +17,7 @@ import './ff-shop';
import './papers-please';
import './minesweeper';
import './qwop';
import './laser-sunset';
import './here-comes-niko';

export * from './channels';
55 changes: 55 additions & 0 deletions src/channels/laser-sunset/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @file Configuration values. 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: 4000,
countMax: 6,
xMarginPercent: 15,
yMinPercent: 12,
yMaxPercent: 25,
reflectionOpacity: 0.3,
fadeAt: 0.2,
hoverTimeMs: 1500,
hoverOffset: 1,
colors: ['mediumspringgreen', 'deepskyblue', 'cyan'],
},
Subscriptions: {
despawnMs: 8000,
fontSizeMin: 25,
fontSizeMax: 60,
lockTimeMs: 600,
colors: ['Lavender', 'LightSkyBlue', 'Aquamarine', 'AliceBlue', 'HoneyDew'],
},
Stars: {
countX: 10,
countY: 7,
brightnessMin: 120,
brightnessMax: 240,
opacityMin: 0.3,
opacityMax: 1.0,
twinkleMs: 600,
},
Lasers: {
bgXstart: 67,
bgXmin: -53,
scrollSpeed: 0.5,
},
Cloud: {
despawnMs: 5000,
xMargin: 5,
sizeMinPercent: 2,
sizeMaxPercent: 12,
sizeDonationMin: 5,
sizeDonationMax: 1000,
perspectiveGrowth: 2.4,
colors: ['magenta', 'orchid', 'blueviolet', 'mediumpurple'],
},
Timers: {
fpsInterval: 1000 / 60,
},
};

export default CONFIG;
222 changes: 222 additions & 0 deletions src/channels/laser-sunset/functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
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: 0,
});
};

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 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 range = 100 - CONFIG.Donations.xMarginPercent;
const left =
CONFIG.Donations.xMarginPercent + ((count % CONFIG.Donations.countMax) / CONFIG.Donations.countMax) * range;

return {
...baseProps,
renderedAmount: formatCurrency(baseProps.rawAmount),
left: left,
top: randomRange(CONFIG.Donations.yMinPercent, CONFIG.Donations.yMaxPercent),
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 * 8,
};
};

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 donationHovering = (age: number) => {
const maxTime = CONFIG.Donations.hoverTimeMs;
const relAge = age % maxTime;
const offset = CONFIG.Donations.hoverOffset;
return Math.cos((relAge / maxTime) * Math.PI * 2) * offset;
};

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 progress = Math.min(1.0, age / CONFIG.Donations.despawnMs);
const fadeAt = CONFIG.Donations.fadeAt;
const fadeOutAt = 1.0 - fadeAt;

let opacity = 1.0;
if (progress < fadeAt) {
opacity = progress / fadeAt;
} else if (progress > fadeOutAt) {
opacity = 1 - (progress - fadeOutAt) / fadeAt;
}

return {
x: donation.left,
y: donation.top + donationHovering(age),
now: now,
age: age,
color: donation.color,
opacity: opacity,
};
};

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

export const donationReflectionStyle = (donation: DonationPopup) => {
const dsp = donationScreenspaceProps(donation);
const pushDown = 10;

return {
...donation,
left: dsp.x + '%',
top: 50 - dsp.y + pushDown + '%',
opacity: dsp.opacity * CONFIG.Donations.reflectionOpacity,
};
};

export const spawnSubscription = (sub: TwitchSubscription, count: number): SubscriptionVisual => {
const angleDegs = count % 2 ? (count % 36) * 10 : ((count + 18) % 36) * 10;
const angleRads = (angleDegs / 360) * Math.PI * 2.0;

return {
angle: angleRads,
radius: 2 + Math.random() * 5,
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();
const radius = sub.radius + age / 50.0;

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

export const subscriptionStyle = (sub: SubscriptionVisual) => {
const ssp = subscriptionScreenspaceProps(sub);
return { left: ssp.x + '%', top: ssp.y + '%', fontSize: ssp.fontSize + 'px', color: ssp.color };
};

export const subscriptionReflectionStyle = (sub: SubscriptionVisual) => {
const dsp = subscriptionScreenspaceProps(sub);
const age = dsp.age * 2;
const pushDown = 10 + (age / CONFIG.Donations.despawnMs) * 120;
return {
...sub,
left: dsp.x + '%',
top: dsp.radius + pushDown + '%',
};
};
Loading

0 comments on commit ea887bb

Please sign in to comment.