const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); const STV_GLOBAL_URL = () => "https://api.7tv.app/v2/emotes/global"; const STV_CHANNEL_URL = (user) => "https://api.7tv.app/v2/users/" + user + "/emotes"; const FFZ_GLOBAL_URL = () => "https://api.frankerfacez.com/v1/set/global"; const FFZ_CHANNEL_URL = (user) => "https://api.frankerfacez.com/v1/room/" + user; const BTT_GLOBAL_URL = () => "https://api.betterttv.net/3/cached/emotes/global"; const BTT_CHANNEL_URL = (id) => "https://api.betterttv.net/3/cached/users/twitch/" + id; function getData(ajaxurl) { return $.ajax({ url: ajaxurl, type: 'GET', }); }; const get_g7TV = async() => { const req = await getData(STV_GLOBAL_URL()) return req.map((v,i)=> ({str:v.name, url: (v.urls['2']||v.urls['2']||v.urls['1'])[1]})); } const get_p7TV = async(user) => { try{ const req = await getData(STV_CHANNEL_URL(user)) return req.map((v,i)=> ({str:v.name, url: (v.urls['2']||v.urls['2']||v.urls['1'])[1]})); }catch(e){return []} } const get_gFFZ = async() => { const req = await getData(FFZ_GLOBAL_URL()) return req.sets['3'].emoticons.map((v,i)=> ({str:v.name, url: 'https:'+(v.urls['2']||v.urls['2']||v.urls['1'])})) } const get_pFFZ = async(user) => { try{ const req = await getData(FFZ_CHANNEL_URL(user)) return req.sets[req.room.set].emoticons.map((v,i)=> ({str:v.name, url: 'https:'+(v.urls['2']||v.urls['2']||v.urls['1'])})) }catch(e){return []} } const get_gBTT = async() => { const req = await getData(BTT_GLOBAL_URL()) return req.map((v,i) => ({str:v.code, url: 'https://cdn.betterttv.net/emote/'+v.id+'/2x'})) } const get_pBTT = async(id) => { try{ id = '486183840' const req = await getData(BTT_CHANNEL_URL(id)) return req.sharedEmotes.map((v,i) => ({str:v.code, url: 'https://cdn.betterttv.net/emote/'+v.id+'/2x'})) }catch(e){return []} } const get_emotes = async(username, userid) => { return [].concat(await get_g7TV(), await get_p7TV(username), await get_gFFZ(), await get_pFFZ(username), await get_gBTT(), await get_pBTT(userid)) } const canvasSizing = (cnv) => { const resizeCanvas = () => { cnv.width = window.innerWidth; cnv.height = window.innerHeight; } window.addEventListener('resize', resizeCanvas, false); resizeCanvas(); } $(async function () { const webSocket = window.socket; var img_map = {}, static_emotes = {}; var lastframe = 0; var preloaded = false, initialized = false; var particles = []; const canvas = document.getElementById('wall'); const ctx = canvas.getContext('2d'); canvasSizing(canvas); const loadImages = (emotes) => { let loadcount = 0, loadtotal = emotes.length; preloaded = false; var loadedimages = {}; for (let i=0; i { if(!Array.isArray(set)) set = Object.keys(img_map) || [] return set[parseInt(set.length* Math.random())] } const username = "soraefir" const userid = "486204587" static_emotes = await get_emotes(username,userid) img_map = loadImages(static_emotes) const part_speed = 50; const part_bounce_el = 0.9 const fdt = 1; const drt = 8; const tWave = (t) => (t<0 || t>drt )? 0 : (t(drt-fdt) ? (drt-t)/fdt : 1)) const sWave = (t, s) => (t<0 || t>drt) ? 1 : (t(drt-fdt) ? (drt-t)*s/fdt : s)) const randRange = (min,max) => (Math.random()* (max-min) + min) class Particle { constructor(emote,data){ this.size = 64; this.data = data; this.setPhysics(randRange(0,canvas.width), randRange(0,canvas.height), 0,0,0,0); this.sete = emote ? emote:null this.emote = emote || randEmote(this.sete) this.time = 0; } setPhysics(x,y,vx,vy,ax,ay){ this.x = x || this.x || 0; this.y = y || this.y || 0; this.vx = vx || this.vx || 0; this.vy = vy || this.vy || 0; this.ax = ax || this.ax || 0; this.ay = ay || this.ay || 0; } draw(){ ctx.globalAlpha = tWave(this.time); if(img_map[this.emote]) ctx.drawImage(img_map[this.emote], this.x, this.y, sWave(this.time,this.size), sWave(this.time,this.size)); } update(tick){ this.time += tick; this.vx += tick * this.ax; this.vy += tick * this.ay; this.x += tick * this.vx * part_speed; this.y += tick * this.vy * part_speed; } } class RainP extends Particle { constructor(emote,data) { super(emote,data) this.setPhysics(randRange(0, canvas.width), randRange(0, -this.size), 0,randRange(10,15), 0,0) this.size = randRange(this.size/2,this.size) this.time = 5.5+this.y/canvas.height*2; } draw(){ ctx.globalAlpha = tWave(this.time); if(img_map[this.emote]) ctx.drawImage(img_map[this.emote], this.x, this.y, this.size, this.size); } } class BounceP extends Particle { constructor(emote,data) { super(emote,data) let speed = randRange(5,10); let ang = randRange(0, 2*Math.PI); this.setPhysics(randRange(0, canvas.width), randRange(0, canvas.height), Math.cos(ang)*speed, Math.sin(ang)*speed, 0,0) } update(tick){ super.update(tick); if (this.x < 0) { this.vx = this.vx < 0 ? -this.vx * part_bounce_el : this.vx; this.x = 0; } else if (this.x + this.size > canvas.width) { this.vx = this.vx > 0 ? -this.vx * part_bounce_el : this.vx; this.x = canvas.width - this.size; } if (this.y <0) { this.vy = this.vy < 0 ? -this.vy * part_bounce_el : this.vy; this.y = 0; } else if (this.y + this.size > canvas.height) { this.vy = this.vy > 0 ? -this.vy * part_bounce_el : this.vy; this.y = canvas.height - this.size; } } } class BounceGP extends BounceP { constructor(emote,data) { super(emote,data) let speed = randRange(5,10); let ang = randRange(0,2*Math.PI); this.setPhysics(randRange(0,canvas.width), randRange(0,canvas.height), Math.cos(ang)*speed, Math.sin(ang)*speed, 0,10); } } class ExplosionP extends RainP { constructor(emote,data,args){ super(emote,data) let speed = randRange(5,15); let fang = 2*Math.PI* (args.angle/360) let ang = randRange(-fang/2, +fang/2)+Math.PI/2; this.setPhysics(args.x,args.y, Math.cos(ang)*speed, -Math.sin(ang)*speed, 0,10); this.time = drt - fdt*2; this.size = randRange(this.size/2,this.size) } } class FireworkP extends Particle { constructor(emote,data) { super(emote,data) this.setPhysics(canvas.width/2,canvas.height, 0,-15, 0,0); this.time=fdt; } update(tick){ super.update(tick); if(this.y <= canvas.height/4 && this.time < drt){ this.time = drt; genExplosion(this.x,this.y,360, this.sete) } } } class BombP extends Particle { constructor(emote,data) { super(emote,data) this.setPhysics(randRange(0, canvas.width-this.size), -this.size, 0,15, 0,0) this.time=fdt; } update(tick){ super.update(tick); if(this.y >= canvas.height - this.size && this.time < drt){ this.time = drt; genExplosion(this.x,this.y,60, this.sete) } } } const createParticule = (partP, emote, data, args) => { let a = new partP(emote,data,args) particles.push(a); setTimeout(()=>{particles.pop()}, drt*1000+2000); } const genExplosion = (x,y,ang,em) =>{ for(let i=0; i<100; ++i) createParticule(ExplosionP, em? em:randEmote(), {},{x:x,y:y,angle:ang}); } const genRain = (em) => { let xpos = randRange(0, canvas.width); let time = 0; const interval = setInterval(()=>{ for(let i=0; i<10; ++i) createParticule(RainP, em? em:randEmote(), {},{}); ++time; if(time > 50) clearInterval(interval); }, 100); } const genVolcano = (em) => { let xpos = randRange(0, canvas.width); let time = 0; const interval = setInterval(()=>{ for(let i=0; i<10; ++i) createParticule(ExplosionP, em? em:randEmote(), {},{x:xpos,y:canvas.height,angle:30}); ++time; if(time > 50) clearInterval(interval); }, 100); } async function init() { main(0); sleep(1000).then(()=>{ createParticule(BounceGP, randEmote(), {}, {}) createParticule(BounceP, randEmote(), {}, {}) createParticule(BounceP, randEmote(), {}, {}) createParticule(BombP, randEmote(), {}, {}) createParticule(FireworkP, randEmote(), {}, {}) genRain(); genVolcano(); }) } function main(tframe) { window.requestAnimationFrame(main); var dt = (tframe - lastframe) / 1000; lastframe = tframe; ctx.clearRect(0, 0, canvas.width, canvas.height); if (!initialized) { if (preloaded) setTimeout(function(){initialized = true;}, 1000); } else { particles.forEach(p => p.update(dt)); particles.forEach(p => p.draw()); } } init() const handleSocketMessage = (e)=>{ try { let rawMessage = e.data, message = JSON.parse(rawMessage); if(!message.hasOwnProperty('eventFamily') || message.eventFamily != 'emote' || !message.hasOwnProperty('eventType') || !message.hasOwnProperty('data')) return; console.log(message.eventType, message.data) if(message.eventType == 'bounce') { createParticule(BounceP, message.data, {}, {}) }else if(message.eventType == 'rain') { genRain(message.data) }else if(message.eventType == 'firework') { genFirework(message.data) }else if(message.eventType == 'volcano') { genVolcano(message.data) }else if(message.eventType == 'bomb') { createParticule(BombP, message.data, {}, {}) }else if(message.eventType == 'explosion') { //genExplosion(message.data) }else { console.log("unhandled:", message.eventType) } } catch (ex) { console.log(ex) } }; jQuery(async ()=>{ try{ socket.addFamilyHandler("emote", handleSocketMessage); if(socket){ while(socket.getReadyState() === 0){ await new Promise(r => setTimeout(r, 500)); } } }catch(e) {console.log(e)} }) });