UNPKG

@knopkem/little-game-engine-ts

Version:

LittleGameEngineTS - Tiny and Fast HTML5 Game Engine typescript port of LittleJS

323 lines (274 loc) 12.1 kB
import { ASSERT } from "./index"; import { debugMedals } from "./index"; import { overlayCanvas, overlayContext } from "./index"; import { defaultFont, medalDisplayHeight, medalDisplayWidth, medalDisplayIconSize, medalDisplayTime, medalDisplaySlideTime } from "./index"; import { timeReal } from "./index"; // import { enc, lib, AES } from 'crypto-js'; /** * LittleJS Medal System * <br> - Tracks and displays medals * <br> - Saves medals to local storage * <br> - Newgrounds and OS13k integration * @namespace Medals */ /** List of all medals * @memberof Medals */ export const medals: any = []; /** Set to stop medals from being unlockable (like if cheats are enabled) * @memberof Medals */ export let medalsPreventUnlock: boolean; /** This can used to enable Newgrounds functionality * @type {Newgrounds} * @memberof Medals */ export let newgrounds: any; // Engine internal variables not exposed to documentation let medalsDisplayQueue: any = [], medalsSaveName: string, medalsDisplayTimer: number; /////////////////////////////////////////////////////////////////////////////// /** Initialize medals with a save name used for storage * <br> - Checks if medals are unlocked * <br> - Call this after creating all medals * @param {String} saveName * @memberof Medals */ export function medalsInit(saveName: any) { // check if medals are unlocked medalsSaveName = saveName; debugMedals || medals.forEach((medal: any) => medal.unlocked = localStorage[medal.storageKey()]); } /** Medal Object - Tracks an unlockable medal */ export class Medal { description: any; icon: any; id: any; image: any; name: any; unlocked: any; /** * Create an medal object and adds it to the list of medals * @param {Number} id - The unique identifier of the medal * @param {String} name - Name of the medal * @param {String} [description] - Description of the medal * @param {String} [icon='🏆'] - Icon for the medal * @param {String} [src] - Image location for the medal */ constructor(id: any, name: any, description='', icon='🏆', src: any) { ASSERT(id >= 0 && !medals[id]); // save attributes and add to list of medals medals[this.id = id] = this; this.name = name; this.description = description; this.icon = icon; if (src) { // load image this.image = new Image(); this.image.src = src; } } /** Unlocks a medal if not already unlocked */ unlock() { if (medalsPreventUnlock || this.unlocked) return; // save the medal ASSERT(medalsSaveName); // game name must be set localStorage[this.storageKey()] = this.unlocked = 1; medalsDisplayQueue.push(this); // save for newgrounds and OS13K newgrounds && newgrounds.unlockMedal(this.id); localStorage['OS13kTrophy,' + this.icon + ',' + medalsSaveName + ',' + this.name] = this.description; } /** Render a medal * @param {Number} [hidePercent=0] - How much to slide the medal off screen */ render(hidePercent=0) { const context = overlayContext; const x = overlayCanvas.width - medalDisplayWidth; const y = -medalDisplayHeight*hidePercent; // draw containing rect and clip to that region context.save(); context.beginPath(); context.fillStyle = '#ddd'; context.rect(x, y, medalDisplayWidth, medalDisplayHeight); context.fill(); context.strokeStyle = context.fillStyle = '#000'; context.lineWidth = 2; context.stroke(); context.clip(); this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2); // draw the text context.textAlign = 'left'; context.font = '3em '+ defaultFont; context.fillText(this.name, x+medalDisplayIconSize+25, y+35); context.font = '1.5em '+ defaultFont; context.fillText(this.description, x+medalDisplayIconSize+25, y+70); context.restore(); } /** Render the icon for a medal * @param {Number} x - Screen space X position * @param {Number} y - Screen space Y position * @param {Number} [size=medalDisplayIconSize] - Screen space size */ renderIcon(x: any, y: any, size=medalDisplayIconSize) { // draw the image or icon const context = overlayContext; context.textAlign = 'center'; context.textBaseline = 'middle'; context.font = size*.6 + 'px '+ defaultFont; context.fillStyle = '#000'; if (this.image) context.drawImage(this.image, x-size/2, y-size/2, size, size); else context.fillText(this.icon, x, y); // show icon if there is no image } // Get local storage key used by the medal storageKey() { return medalsSaveName + '_medal_' + this.id; } } // engine automatically renders medals export function medalsRender() { if (!medalsDisplayQueue.length) return; // update first medal in queue const medal = medalsDisplayQueue[0]; const time = timeReal - medalsDisplayTimer; if (!medalsDisplayTimer) medalsDisplayTimer = timeReal; else if (time > medalDisplayTime) medalsDisplayQueue.shift(medalsDisplayTimer = 0); else { // slide on/off medals const slideOffTime = medalDisplayTime - medalDisplaySlideTime; const hidePercent = time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime : time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0; medal.render(hidePercent); } } /////////////////////////////////////////////////////////////////////////////// /** Newgrounds API wrapper object */ export class Newgrounds { app_id: any; cipher: any; host: any; medals: any; scoreboards: any; session_id: any; /** * Create a newgrounds object * @param {Number} app_id - The newgrounds App ID * @param {String} [cipher] - The encryption Key (AES-128/Base64) */ constructor(app_id: any, cipher: any) { ASSERT(!newgrounds && app_id); this.app_id = app_id; this.cipher = cipher; this.host = location ? location.hostname : ''; // create an instance of CryptoJS for encrypted calls // cipher && (this.cryptoJS = CryptoJS); // get session id from url search params const url = new URL(window.location.href); this.session_id = url.searchParams.get('ngio_session_id') || 0; if (this.session_id == 0) return; // only use newgrounds when logged in // get medals const medalsResult = this.call('Medal.getList'); this.medals = medalsResult ? medalsResult.result.data['medals'] : []; debugMedals && console.log(this.medals); for (const newgroundsMedal of this.medals) { const medal = medals[newgroundsMedal['id']]; if (medal) { // copy newgrounds medal data medal.image = new Image(); medal.image.src = newgroundsMedal['icon']; medal.name = newgroundsMedal['name']; medal.description = newgroundsMedal['description']; medal.unlocked = newgroundsMedal['unlocked']; medal.difficulty = newgroundsMedal['difficulty']; medal.value = newgroundsMedal['value']; if (medal.value) medal.description = medal.description + ' (' + medal.value + ')'; } } // get scoreboards const scoreboardResult = this.call('ScoreBoard.getBoards'); this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : []; debugMedals && console.log(this.scoreboards); } /** Send message to unlock a medal by id * @param {Number} id - The medal id */ // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ id: any; }' is not assignable ... Remove this comment to see the full error message unlockMedal(id: any) { return this.call('Medal.unlock', {'id':id}, 1); } /** Send message to post score * @param {Number} id - The scoreboard id * @param {Number} value - The score value */ // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ id: any; value: any; }' is not... Remove this comment to see the full error message postScore(id: any, value: any) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); } /** Send message to log a view */ // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ host: any; }' is not assignabl... Remove this comment to see the full error message logView() { return this.call('App.logView', {'host':this.host}, 1); } /** Get scores from a scoreboard * @param {Number} id - The scoreboard id * @param {String} [user=0] - A user's id or name * @param {Number} [social=0] - If true, only social scores will be loaded * @param {Number} [skip=0] - Number of scores to skip before start * @param {Number} [limit=10] - Number of scores to include in the list * @return {Object} - The response JSON object */ getScores(id: any, user=0, social=0, skip=0, limit=10) // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ id: any; user: number; social:... Remove this comment to see the full error message { return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); } /** Send a message to call a component of the Newgrounds API * @param {String} component - Name of the component * @param {Object} [parameters=0] - Parameters to use for call * @param {Boolean} [async=0] - If true, wait for response before continuing (will cause stall) * @return {Object} - The response JSON object */ call(component: any, parameters=0, async=0) { const call = {'component':component, 'parameters':parameters}; if (this.cipher) { // encrypt using AES-128 Base64 with cryptoJS /* const aesKey = enc.Base64.parse(this.cipher); const iv = lib.WordArray.random(16); const encrypted = AES.encrypt(JSON.stringify(call), aesKey, {'iv':iv}); // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message call['secure'] = enc.Base64.stringify(iv.concat(encrypted['ciphertext'])); call['parameters'] = 0; */ } // build the input object const input = { 'app_id': this.app_id, 'session_id': this.session_id, 'call': call }; // build post data const formData = new FormData(); formData.append('input', JSON.stringify(input)); // send post data const xmlHttp = new XMLHttpRequest(); const url = 'https://newgrounds.io/gateway_v3.php'; // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'number' is not assignable to par... Remove this comment to see the full error message xmlHttp.open('POST', url, !debugMedals && async); xmlHttp.send(formData); debugMedals && console.log(xmlHttp.responseText); return xmlHttp.responseText && JSON.parse(xmlHttp.responseText); } } ///////////////////////////////////////////////////////////////////////////////