function Avatar(meta, x, y, onload) { //console.log(meta); this.meta = meta; this.x = x || 0; this.y = y || 0; this.spritesheet = new Image(); this.loaded = false; this.frameid = 0; this.animStack = []; this.messageQueue = []; this.alpha = 1; this.scale = 1; this.rotation = 0; this.palette = Uint8Array.from(atob(meta.palette).split('').map(function (c) { return c.charCodeAt(0); })); this.fillStyle = '#000000' var self = this; this.spritesheet.onload = function() { self.loaded = true; if(onload) onload.apply(self); }; this.spritesheet.src = 'data:image/png;base64,' + meta.data; } Avatar.CURRENT_GENERATION = null; Avatar.GENERATIONS = [51754]; Avatar.ADDONS = {"ca2a": {"6ad5d06a818d6195c01fe48bacda5970ac5dd0f6": ["Earrings", {"176b1e616cae21c91bf96336ca632e8a92e8599e": "Left", "c11caf1752dbe73ced1180632b2566ed2698cae6": "Both"}, {}], "ba22ef34f8bf4f0d4fde40c9b22c2921ae964f9c": ["Glasses", {"c62338a4e14416d30476fae0056777c72d968cb4": "Fatale", "605ae021b3fa3c412fa07861a8092936ae0721d6": "Thief"}, {}], "bb4a91749fe02cfaa8478886c95a69d9e2017356": ["Neck", {"e2efff6f6b54d13181e215cf76cb06c48867e0c3": "Necklace", "9dc7328f3507054c6274bcc31703d91d8570496b": "Scarf"}, {"7505d64a54e061b7acd54ccd58b49dc43500b635": "Gold", "f872caad177d67bbe18c119d0505f2d3caa02af3": "Diamond", "f8248e12727710c946f73d8f6e02eb93530dd9de": "Silver"}]}}; Object.defineProperty(Avatar.prototype, 'curAnim', { get: function curAnim() { return (this.animStack.length == 0 ? null : this.animStack[0].anim); } }); Object.defineProperty(Avatar.prototype, 'curMessage', { get: function curMessage() { return (this.messageQueue.length == 0 ? null : this.messageQueue[0].message); } }); Avatar.prototype.render = function(ctx) { if(!this.loaded) return; var origWidth = this.spritesheet.height, origHeight = this.spritesheet.height; var w = Math.round(origWidth * this.scale); var h = Math.round(origHeight * this.scale); ctx.globalAlpha = this.alpha; ctx.translate(this.x, this.y); ctx.rotate(this.rotation); ctx.drawImage(this.spritesheet, this.frameid * origHeight, 0, origHeight, origHeight, Math.floor(-.5 * w), Math.floor(-.5 * h), w, h); if(this.messageQueue.length > 0) { var lines = this.messageQueue[0].lines; for(var i = 0; i < lines.length; i++) { var line = lines[i]; ctx.fillStyle = this.fillStyle; ctx.font = Math.round(this.scale * 3) + 'px "8-bit"'; ctx.textAlign = "center"; ctx.fillText(line, 0, Math.round(.5 * h + (i + 1) * 4 * this.scale)); } } ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.globalAlpha = 1; }; Avatar.prototype.blink = function() { this.play('blink', 1 + Math.floor(Math.random() * 2)); }; Avatar.prototype.turnEyes = function() { this.play('turn'); }; Avatar.prototype.update = function(dt) { if(Math.random() < 0.0001 * dt) this.blink(); if(Math.random() < 0.0001 * dt) this.turnEyes(); if(this.animStack.length > 0) { var animParams = this.animStack[0]; animParams.timeout -= dt; while(animParams.timeout <= 0) { if(animParams.frameid < animParams.anim.frames) { this.frameid = animParams.frameOffset + animParams.frameid++; } else { this.frameid = animParams.frameid = 0; if(--animParams.repeat <= 0) { this.animStack.shift(); if(this.animStack.length == 0) this.frameid = 0; } } animParams.timeout += 1000 / animParams.anim.fps; } } if(this.messageQueue.length > 0) { var m = this.messageQueue[0]; m.timeout -= dt; if(m.timeout <= 0) this.messageQueue.shift(); } }; Avatar.prototype.say = function(message) { var words = message.trim().split(' '); var i = 0; var maxChars = Math.floor(this.scale * this.spritesheet.height / 14); var lines = []; while(i < words.length) { var len = -1; for(var j = i; j < words.length && len <= maxChars; j++) len += words[j].length + 1; if(i + 1 != j && len > maxChars) j--; lines.push(words.slice(i, j).join(' ')); i = j; } if(lines.length > 0) { this.play('talk', Math.round(.75 * message.length), false); this.messageQueue.push({ message: message, lines: lines, timeout: 500 * Math.round(.75 * message.length) }); } }; Avatar.prototype.play = function(animid, repeat, immediate) { repeat = repeat || 1; immediate = immediate || true; var anim = null; var frameOffset = 1; var frameId = 1; for(var i = 0; i < this.meta.animations.length; i++) { if(this.meta.animations[i].id == animid) { anim = this.meta.animations[i]; break; } frameOffset += this.meta.animations[i].frames; } if(!anim) return; var animParams = { anim: anim, repeat: repeat, frameid: 0, frameOffset: frameOffset, timeout: 0 }; if(immediate) this.animStack.unshift(animParams); else this.animStack.push(animParams); return animParams; }; Avatar.load = function(avatarId, addons, onload) { addons = addons || []; if(typeof(avatarId) == 'string') { if(avatarId[0] == '#') avatarId = avatarId.slice(1); if(avatarId.slice(0, 2).toLowerCase() == '0x') avatarId = avatarId.slice(2); } var genId = avatarId.slice(0, 4); var genData = Avatar.ADDONS[genId]; var req = new XMLHttpRequest(); req.onreadystatechange = function() { if(this.readyState == 4) { if(this.status != 200) { console.log('Error getting avatar data.'); return; } var avatarData = JSON.parse(this.responseText); if(avatarData.error) { console.log('Error: server replied "' + avatarData.error + '".'); return; } new Avatar(avatarData, 0, 0, onload); } }; req.open('POST', '/api/' + avatarId + '.json', true); req.setRequestHeader('Content-Type', 'application/json'); var actualAddons = {}; for(var k in addons) { var addon = addons[k]; if(addon[0]) actualAddons[k] = [ addon[0], addon[1] || '' ]; } req.send(JSON.stringify({ addons: actualAddons })); };