Skip to content

Instantly share code, notes, and snippets.

@Bolloxim
Last active October 7, 2016 20:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Bolloxim/9835c966e7e0b6ccfa7f to your computer and use it in GitHub Desktop.
Save Bolloxim/9835c966e7e0b6ccfa7f to your computer and use it in GitHub Desktop.
A Pen by Andi Smithers.
<body>
<div class = "outer">
<!-- setup the canvas -->
<canvas class='letter' id='star-raiders'></canvas>
</div>
<!-- link to google fonts -->
<link href='http://fonts.googleapis.com/css?family=Orbitron' rel='stylesheet' type='text/css'>
<!-- parse -->
<script src="http://www.parsecdn.com/js/parse-1.2.19.min.js"></script>
<!-- include some basic math helper class -->
<script src="http://bolloxim.github.io/Star-Raiders/javascripts/simplemath.js"></script>
<!-- include ui slider and button controls -->
<script src="http://bolloxim.github.io/Star-Raiders/javascripts/uicontrols.js"></script>
<!-- include game board pieces and logic -->
<script src="http://bolloxim.github.io/Star-Raiders/javascripts/MainGameBoard.js"></script>
<!-- include core asteriod components -->
<script src="http://bolloxim.github.io/Star-Raiders/javascripts/asteriods.js"></script>
<!-- include core starfield components -->
<script src="http://bolloxim.github.io/Star-Raiders/javascripts/warpfield.js"></script>
<!-- include particles components -->
<script src="http://bolloxim.github.io/Star-Raiders/javascripts/particles.js"></script>
<!-- include zylon fighter -->
<script src="http://bolloxim.github.io/Star-Raiders/javascripts/zylonfighters.js"></script>
<!-- include basestar rendering -->
<script src="http://bolloxim.github.io/Star-Raiders/javascripts/basestar.js"></script>
<!-- include cruiser rendering -->
<script src="http://bolloxim.github.io/Star-Raiders/javascripts/cruiser.js"></script>
<!-- include nice message plotting method -->
<script src="http://bolloxim.github.io/Star-Raiders/javascripts/messageplotter.js"></script>
<!-- include starbase rendering -->
<script src="http://bolloxim.github.io/Star-Raiders/javascripts/starbase.js"></script>
<!-- include collision checks -->
<script src="http://bolloxim.github.io/Star-Raiders/javascripts/weaponcollisions.js"></script>
<!-- include procedural audio -->
<script src="http://bolloxim.github.io/Star-Raiders/javascripts/audioeffects.js"></script>
</body>
/*****************************************************************************
The MIT License (MIT)
Copyright (c) 2014 Andi Smithers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*****************************************************************************/
// conceptualized and written by andi smithers
// constant options
const focalDepth = 80;
const focalPoint = 256;
const eNone = 0;
const eGalacticScanner = 1;
const eLongRange = 2;
const eTargetComputer = 3;
const shieldRange = 5;
// end game scenerios
const aborted = 0;
const destroyed = 1;
const energyLost = 2;
const basesGone = 3;
const allDead = 4;
const playing = 5;
var backgroundColor;
// variables
var centreX;
var centreY;
var mouseX=0;
var mouseY=0;
var frameCount=0;
var gameStart;
var context;
var canvas;
var fontsize = 20;
var overlayMode = eNone;
// overlay transitions
var lrsTargetX;
var lrsTargetY;
var lrsCentreX;
var lrsCentreY;
var lrsOffScreen = true;
var mapTargetX;
var mapTargetY;
var mapCentreX;
var mapCentreY;
var mapOffScreen = true;
// test multiple groups
var boardPieces = [];
var mapScale = {x:0, y:0};
var border = {x:0, y:0};
var galaxyMapSize = {x:16, y:8};
var shipLocation = {x:0, y:0};
var shipPosition = {x:0, y:0};
var localPosition = {x:0, y:0, z:0};
var shipPing = {x:0, y:0};
var pingRadius = 100;
var lastCycle = 0;
var cycleScalar = 3; // 3 = 30 seconds.
var targetBase = 0;
var localSpaceCubed = 1024;
var lrScale = {x:0, y:0};
var warpLocation = {x:0, y:0};
var warpAnim = 0;
var warpLocked = false;
var warpEnergy = 0;
var energy = 9999;
var kills = 0;
var badDriving = 0;
var redAlertColor = 0;
// tracking computer
var trackingTarget = 0;
// ship damage
var shipDamage = {photons:false, engines:false, shields:false, computer:false, longrangescanner:false, subspaceradio:false};
// difficulty settings
var maxAsteriods = 32;
var maxNMEs = 8;
var noContact = true;
var noDeaths= true;
// needs moving
var targetComputer = false;
var trackingComputer = false;
var redTime = 0;
var redAlert = 0;
var shipOrientation = new matrix3x3();
var orientation = new matrix3x3();
var scannerView = new matrix3x3();
var angleX = 0;
var angleY = 0;
var nmes = [];
var gameDifficultyHitBox = 1.5;
var statistics = {rank:0, kills:0, difficulty:0, played:0, roidsFragmented:0, roidsHit:0, refuel:0, shieldsHit:0, bases:0, shipsHit:0, killTypes:[0,0,0,0], damaged:0, shots:0, deflects:0, jumped:0, jumpedEnergy:0, travelled:0, jumpCancelled:0, timePlayed:0, accuracy:0, energy:0, distance:0, endgames:[0,0,0,0,0]};
var totals = {rank:0, kills:0, difficulty:0, played:0, roidsFragmented:0, roidsHit:0, refuel:0, shieldsHit:0, bases:0, shipsHit:0, killTypes:[0,0,0,0], damaged:0, shots:0, deflects:0, jumped:0, jumpedEnergy:0, travelled:0, jumpCancelled:0, timePlayed:0, accuracy:0, energy:0, distance:0, endgames:[0,0,0,0,0]};
var currentBoardItem = null;
var pauseGame = false;
var titleScreen = true;
var attractMode = 0;
var bestRanks = [{rank:-1000, date:new Date(), percentile:100},{rank:-1000, date:new Date(), percentile:100},{rank:-1000, date:new Date(), percentile:100},{rank:-1000, date:new Date(), percentile:100},{rank:-1000, date:new Date(), percentile:100},{rank:-1000, date:new Date(), percentile:100},{rank:-1000, date:new Date(), percentile:100},{rank:-1000, date:new Date(), percentile:100},{rank:-1000, date:new Date(), percentile:100},{rank:-1000, date:new Date(), percentile:100}];
var lastScore = {rank:-1000, date:new Date(), percentile:100};
var difficultyNames = ['Novice', 'Pilot', 'Warrior', 'Commander'];
// endgame event
var endGameTime;
var endGameEvent;
var endGameLastMessage;
var endGameRank;
// global ranking
var gameTotalPlays=0;
var gameHitsPerRanks = [];
function UpdateAllTotals()
{
// update end game data
statistics.energy+=9999-energy;
statistics.kills=kills;
var gameTime = currentTime - gameStart;
var decimalTime = gameTime / 60000;
statistics.timePlayed = decimalTime;
if (statistics.shots == 0)
statistics.accuracy = 0;
else
statistics.accuracy = ((statistics.deflects+statistics.roidsFragmented+statistics.roidsHit+statistics.shipsHit) / statistics.shots) * 100;
totals.played++;
totals.diffculty+=statistics.difficulty; // make array?
totals.roidsFragmented += statistics.roidsFragmented;
totals.roidsHit+=statistics.roidsHit;
totals.refuel+=statistics.refuel;
totals.shieldsHit+=statistics.shieldsHit;
totals.shipsHit+=statistics.shipsHit;
totals.killTypes[0]+=statistics.killTypes[0];
totals.killTypes[1]+=statistics.killTypes[1];
totals.killTypes[2]+=statistics.killTypes[2];
totals.killTypes[3]+=statistics.killTypes[3];
totals.damaged+=statistics.damaged;
totals.shots+=statistics.shots;
totals.deflects+=statistics.deflects;
totals.jumped+=statistics.jumped;
totals.jumpedEnergy+=statistics.jumpedEnergy;
totals.travelled+=statistics.travelled;
totals.jumpCancelled+=statistics.jumpCancelled;
totals.energy+=statistics.energy;
totals.kills+=statistics.kills;
totals.timePlayed+=statistics.timePlayed;
totals.distance+=statistics.distance;
totals.endgames[0]+=statistics.endgames[0];
totals.endgames[1]+=statistics.endgames[1];
totals.endgames[2]+=statistics.endgames[2];
totals.endgames[3]+=statistics.endgames[3];
totals.endgames[4]+=statistics.endgames[4];
if (totals.shots == 0)
totals.accuracy = 0;
else
totals.accuracy = ((totals.deflects+totals.roidsFragmented+totals.roidsHit+totals.shipsHit) / totals.shots) * 100;
}
// difficulty setup, novice , pilot, warrior, commander
function ClearGame()
{
// clear stats
statistics = {kills:0, difficulty:0, played:0, roidsFragmented:0, roidsHit:0, refuel:0, shieldsHit:0, bases:0, shipsHit:0, killTypes:[0,0,0,0], damaged:0, shots:0, deflects:0, jumped:0, jumpedEnergy:0, travelled:0, jumpCancelled:0, timePlayed:0, accuracy:0, energy:0, distance:0, endgames:[0,0,0,0,0]};
shipDamage = {photons:false, engines:false, shields:false, computer:false, longrangescanner:false, subspaceradio:false};
// clear ship details
shipLocation = {x:0, y:0};
shipPosition = {x:0, y:0};
localPosition = {x:0, y:0, z:0};
shipOrientation = new matrix3x3();
orientation = new matrix3x3();
scannerView = new matrix3x3();
targetComputer = false;
trackingComputer = false;
redTime = 0;
redAlert = 0;
warpLocation = {x:0, y:0};
warpAnim = 0;
warpLocked = false;
warpEnergy = 0;
energy = 9999;
kills = 0;
badDriving = 0;
redAlertColor = 0;
shieldUp = false;
}
function SetupNMEs(boardItem)
{
// clear list
nmes = [];
currentBoardItem = boardItem;
if (boardItem == null) return;
noContact = true;
noDeaths = true;
for (var i=0; i<boardItem.numTargets; i++)
{
var nme = new NME();
nme.randomize(boardItem.targets[i]);
nmes.push(nme);
}
console.log(boardItem.numTargets);
if (boardItem.type != base) SetRedAlert();
}
function UpdateNMEs()
{
for (var i=0; i<nmes.length; i++)
{
if (nmes[i].hitpoints)
{
var x = nmes[i].pos.x + nmes[i].vel.x * nmes[i].speed * freqHz;
var y = nmes[i].pos.y + nmes[i].vel.y * nmes[i].speed * freqHz;
var z = nmes[i].pos.z + nmes[i].vel.z * nmes[i].speed * freqHz;
nmes[i].pos.x = x;
nmes[i].pos.y = y;
nmes[i].pos.z = z;
switch(nmes[i].type)
{
case fighter:
ZylonFighterAI(nmes[i]);
break;
case base:
{
// compute distance
var delta = nmes[i].delta();
var t = orientation.transform(delta.x, delta.y, delta.z);
// compute angles
var phi = Math.atan2(t.x, t.z);
var theta = Math.atan2(t.y, Math.sqrt(t.x*t.x+t.z*t.z));
var a = Math.abs(gradon(phi));
var b = Math.abs(gradon(theta));
var dock = (a <= 1 && b <= 1 && t.z <= 5) ? true:false;
EnableDocking(dock);
break;
}
case cruiser:
ZylonCruiserAI(nmes[i]);
break;
case basestar:
ZylonBaseStarAI(nmes[i]);
break;
default:
ZylonFighterAI(nmes[i]);
break;
}
}
}
}
function nmeShoot(pos)
{
var scale =512/canvas.height;
var x = modulo2(localPosition.x - pos.x, localSpaceCubed)-localSpaceCubed*0.5;
var y = modulo2(localPosition.y - pos.y, localSpaceCubed)-localSpaceCubed*0.5;
var z = modulo2(localPosition.z - pos.z, localSpaceCubed)-localSpaceCubed*0.5;
var t = orientation.transform(x, y, z);
if (Math.abs(t.z)>128) return;
var depth = focalPoint*5 / ((t.z + 5*scale) +1);
if (depth<0) depth *= -1;
var depthInv = focalPoint / ((t.z + focalDepth) +1);
spawnX = (t.x*depth)/depthInv+centreX;
spawnY = (t.y*depth)/depthInv+centreY;
spawnZ = t.z;
plasmaEmitter.create();
PlayDisruptor();
// escalate
noContact = false;
}
function ZylonFighterAI(nme)
{
var targetLocations = [{x:0, y:0, z:-70},{x:10, y:-10, z:-100},{x:-20, y:30, z:-50},{x:0, y:0, z:70},{x:20, y:10, z:50},{x:-30, y:10, z:30}];
var targ = shipOrientation.invtransform(targetLocations[nme.target].x,targetLocations[nme.target].y,targetLocations[nme.target].z);
var dir = nme.targetPoint(targ);
// always swarm player
// var dir = nme.deltaAhead(-100);
// we want to go that way
var dirn = new vector3(dir.x, dir.y, dir.z);
dirn.normalize();
var dx = nme.vel.x - dirn.x;
var dy = nme.vel.y - dirn.y;
var dz = nme.vel.z - dirn.z;
nme.vel.x+=dirn.x*0.1;
nme.vel.y+=dirn.y*0.1;
nme.vel.z+=dirn.z*0.1;
var tmp = new vector3( nme.vel.x, nme.vel.y, nme.vel.z);
tmp.normalize();
nme.vel.x = tmp.x;
nme.vel.y = tmp.y;
nme.vel.z = tmp.z;
if (dir.lengthSquared()<25)
{
nme.pass = (nme.pass+1) &63;
if (nme.pass == 0)
nme.target = (nme.target+1) % targetLocations.length;
}
nme.calcypr();
// randomize fire
if (endGameEvent==playing && (Math.random() * 100) >99)
{
nmeShoot(nme.pos);
}
}
function ZylonCruiserAI(nme)
{
var targetLocations = [{x:0, y:0, z:-70},{x:10, y:-10, z:100},{x:-20, y:30, z:-50},{x:0, y:0, z:70},{x:20, y:10, z:-50},{x:-30, y:10, z:40}];
var targ = shipOrientation.invtransform(targetLocations[nme.target].x,targetLocations[nme.target].y,targetLocations[nme.target].z);
var dir = nme.targetPoint(targ);
if (dir.lengthSquared() > 400*400 && noContact==true) dir = dir.mul(-1);
// always swarm player
// var dir = nme.deltaAhead(-100);
// we want to go that way
var dirn = new vector3(dir.x, dir.y, dir.z);
dirn.normalize();
var dx = nme.vel.x - dirn.x;
var dy = nme.vel.y - dirn.y;
var dz = nme.vel.z - dirn.z;
nme.vel.x+=dirn.x*0.1;
nme.vel.y+=dirn.y*0.1;
nme.vel.z+=dirn.z*0.1;
var tmp = new vector3( nme.vel.x, nme.vel.y, nme.vel.z);
tmp.normalize();
nme.vel.x = tmp.x;
nme.vel.y = tmp.y;
nme.vel.z = tmp.z;
if (dir.lengthSquared()<25)
{
nme.pass = (nme.pass+1) &63;
if (nme.pass == 0)
nme.target = (nme.target+1) % targetLocations.length;
}
nme.calcypr();
// randomize fire
if (endGameEvent==playing && (Math.random() * 100) >97)
{
nmeShoot(nme.pos);
}
}
function ZylonBaseStarAI(nme)
{
var targetLocations = [{x:30, y:0, z:-70},{x:0, y:-30, z:-80},{x:-30, y:00, z:-50},{x:0, y:30, z:-70},{x:40, y:40, z:70},{x:-40, y:40, z:60},{x:-40, y:-40, z:90},{x:40, y:-40, z:40}];
var targ = shipOrientation.invtransform(targetLocations[nme.target].x,targetLocations[nme.target].y,targetLocations[nme.target].z);
var dir = nme.targetPoint(targ);
if (dir.lengthSquared() > 300*300 && noDeaths==true) dir=dir.mul(-1);
// always swarm player
// var dir = nme.deltaAhead(-100);
// we want to go that way
var dirn = new vector3(dir.x, dir.y, dir.z);
dirn.normalize();
var dx = nme.vel.x - dirn.x;
var dy = nme.vel.y - dirn.y;
var dz = nme.vel.z - dirn.z;
nme.vel.x+=dirn.x*0.1;
nme.vel.y+=dirn.y*0.1;
nme.vel.z+=dirn.z*0.1;
var tmp = new vector3( nme.vel.x, nme.vel.y, nme.vel.z);
tmp.normalize();
nme.vel.x = tmp.x;
nme.vel.y = tmp.y;
nme.vel.z = tmp.z;
if (dir.lengthSquared()<25)
{
nme.pass = (nme.pass+1) &63;
if (nme.pass == 0)
nme.target = (nme.target+1) % targetLocations.length;
}
nme.calcypr();
// randomize fire
if (endGameEvent==playing && (Math.random() * 100) >95)
{
nmeShoot(nme.pos);
}
}
function DestroyStarbase()
{
// verify base
if (nmes[0].type == base)
{
// detroy base
SpawnAsteriodsAt(nmes[0].pos);
spawnX = nmes[0].pos.x;
spawnY = nmes[0].pos.y;
spawnZ = nmes[0].pos.z;
explodeEmitter.create();
dustEmitter.create();
PlayExplosion();
}
}
function RenderNMEs()
{
for (var i=0; i<nmes.length; i++)
{
if (nmes[i].hitpoints>0)
nmes[i].render();
}
}
function KillNmeType(shipType)
{
if (shipType == base) DestroyStarbase();
kills++;
statistics.killTypes[shipType]++;
// update board item
currentBoardItem.killTarget(shipType);
// escalate
noContact = false;
noDeaths = false;
}
// nme objects
function NME()
{
this.pos = {x:0, y:0, z:0};
this.rot = {y:0, p:0, r:0};
this.vel = {x:0, y:0, z:0};
this.speed = 0;
this.damage = 0;
this.type = 0; // 0 = fighter, 1 = cruiser, 2 = basestar
this.hitpoints = 0;
this.target = 0;
this.pass = 0;
this.theta = 0;
this.phi = 0;
}
NME.prototype.randomize = function(type)
{
var hitpointTable = [4, 1, 1, 2];
this.pos.x = Math.random()*localSpaceCubed;
this.pos.y = Math.random()*localSpaceCubed;
this.pos.z = Math.random()*localSpaceCubed;
if (type!=base) this.vel = RandomNormal();
this.type = type;
this.speed = 25;
this.hitpoints = hitpointTable[type];
}
NME.prototype.delta = function()
{
var x = modulo2(localPosition.x - this.pos.x, localSpaceCubed)-localSpaceCubed*0.5;
var y = modulo2(localPosition.y - this.pos.y, localSpaceCubed)-localSpaceCubed*0.5;
var z = modulo2(localPosition.z - this.pos.z, localSpaceCubed)-localSpaceCubed*0.5;
return {x:x, y:y, z:z};
}
NME.prototype.targetPoint = function(target)
{
var x = modulo2((localPosition.x+target.x) - this.pos.x, localSpaceCubed)-localSpaceCubed*0.5;
var y = modulo2((localPosition.y+target.y) - this.pos.y, localSpaceCubed)-localSpaceCubed*0.5;
var z = modulo2((localPosition.z+target.z) - this.pos.z, localSpaceCubed)-localSpaceCubed*0.5;
return new vector3(x, y, z);
}
NME.prototype.calcypr = function()
{
this.theta = Math.atan2(this.vel.x, this.vel.z) ;//+ Math.PI*0.5;
this.phi = 0;//Math.asin(this.vel.y);
}
NME.prototype.render = function()
{
var x = modulo2(localPosition.x - this.pos.x, localSpaceCubed)-localSpaceCubed*0.5;
var y = modulo2(localPosition.y - this.pos.y, localSpaceCubed)-localSpaceCubed*0.5;
var z = modulo2(localPosition.z - this.pos.z, localSpaceCubed)-localSpaceCubed*0.5;
var camspace = orientation.transform(x, y, z);
switch(this.type)
{
case base:
var scale = 512/canvas.height;
var depth = focalPoint*5 / camspace.z + 5*scale;
var sx = camspace.x * depth + centreX;
var sy =camspace.y * depth + centreY;
var sz = 1 * depth;
if (camspace.z>0) RenderStarbase(sx, sy, sz, Math.atan2(camspace.z, camspace.y)+Math.PI);
break;
case fighter:
renderZylon(camspace.x, camspace.y, camspace.z, this.theta, this.phi);
break;
case cruiser:
var scale = 512/canvas.height;
var depth = focalPoint*5 / camspace.z + 5*scale;
var sx = camspace.x * depth + centreX;
var sy = camspace.y * depth + centreY;
var sz = 0.5 * depth;
if (sz<=0) return;
var fade = 1.0;
if (camspace.z>128) fade = 1.0 - (camspace.z-128)/128;
if (fade<0) fade = 0;
context.globalAlpha = fade;
if (camspace.z>0) RenderCruiser(sx, sy, sz, Math.atan2(camspace.y, camspace.z)+Math.PI*1.5);
break;
case basestar:
var scale = 512/canvas.height;
var depth = focalPoint*5 / camspace.z + 5*scale;
var sx = camspace.x * depth + centreX;
var sy = camspace.y * depth + centreY;
var sz = 1 * depth;
if (sz<=0) return;
var fade = 1.0;
if (camspace.z>128) fade = 1.0 - (camspace.z-128)/128;
if (fade<0) fade = 0;
context.globalAlpha = fade;
if (camspace.z>0) RenderBasestar(sx, sy, sz, Math.atan2(camspace.z, camspace.y*2)+Math.PI);
break;
default:
renderZylon(camspace.x, camspace.y, camspace.z, 0, 0);
break;
}
}
var docking;
var dockTimer;
function EnableDocking(dock)
{
if (dock && !docking)
{
docking = true;
startText("docked with starbase: transfering...", border.x, 150);
dockTimer = (new Date()).getTime()+5000;
}
if (!dock && docking)
{
docking = false;
if (energy<9900)
startText("docking aborted!", border.x, 150);
else
startText("undocked starbase", border.x, 150);
}
if (docking && dockTimer<(new Date()).getTime())
{
// refuel
statistics.refuel = 9999-energy;
energy = 9999;
dockTimer+=100000;
startText("transfer completed", border.x, 150);
}
}
function SetRedAlert()
{
redTime = (new Date()).getTime();
PlayRedAlert(0.5);
}
function DrawRedAlert()
{
var delta = (new Date()).getTime() - redTime;
redAlert = delta<=4000;
if (redAlert == false)
{
document.getElementById("star-raiders").style.background = 'black';
backgroundColor = 'rgb(0,0,0)';
starsColorAlias = backgroundColor;
return;
}
redAlertColor = (Math.floor(delta/500) & 1)*64;
context.globalAlpha = 1.0;
context.font = '40pt Orbitron';
context.fillStyle = 'rgb('+(redAlertColor*3^192)+',0,'+redAlertColor*3+')';
context.textAlign = "center";
context.fillText('RED ALERT', canvas.width/2, 50);
backgroundColor = 'rgb('+((redAlertColor))+',0,'+((redAlertColor^64))+')';
starsColorAlias = backgroundColor;
}
// long range scan
var shipPhi = 0.0;
var shipTheta = 0;
function renderLongRangeScanner()
{
var radius = lrScale.x<lrScale.y ? lrScale.x : lrScale.y;
var strobe = frameCount;
// logic for enabling/disabling the scanner
if (lrsOffScreen)
{
lrsCentreY = centreY*3;
lrsCentreX = centreX;
}
lrsTargetX = centreX;
lrsTargetY = overlayMode == eLongRange ? centreY : centreY*3;
if (Math.abs(lrsTargetY-lrsCentreY)>4)
{
// animate
var dy = lrsTargetY - lrsCentreY;
lrsCentreY+= dy/8;
lrsOffScreen = false;
}
else
{
lrsOffScreen = overlayMode!=eLongRange;
}
// check if scanner is suppose to be on
if (lrsOffScreen) return;
// ok lets draw
context.beginPath();
context.arc(lrsCentreX, lrsCentreY, radius/2, 0, Math.PI*2.0, 0);
context.fillStyle = 'rgba(0,0,64,0.5)';
context.fill();
context.beginPath();
context.arc(lrsCentreX, lrsCentreY, radius/2, 0, Math.PI*2.0, 0);
context.strokeStyle = 'rgba(0,0,255,0.5)';
context.lineWidth = 1;
context.stroke();
context.beginPath();
context.arc(lrsCentreX, lrsCentreY, radius/8, 0, Math.PI*2.0, 0);
context.stroke();
context.beginPath();
context.arc(lrsCentreX, lrsCentreY, radius/2, Math.PI*8.5/6, Math.PI*9.5/6, false);
context.lineTo(lrsCentreX, lrsCentreY);
context.closePath();
context.stroke();
context.strokeStyle = 'rgba(0,0,128,0.5)';
for (var a=1; a<8; a++)
{
context.beginPath();
context.arc(lrsCentreX, lrsCentreY, (a/8) * radius*0.5, Math.PI*8.5/6, Math.PI*9.5/6, 0);
context.stroke();
}
// render asteriods
var s = (radius/canvas.width) * 1.3;
// I think a matrix is now going to be faster..
for (var i=0; i<asteriods.length; i++)
{
var x = modulo(localPosition.x - asteriods[i].x)-512;
var y = modulo(localPosition.y - asteriods[i].y)-512;
var z = modulo(localPosition.z - asteriods[i].z)-512;
var t = scannerView.transform(x,y,z);
var depth = focalPoint*5 / ((t.z + 1400) +1);
var sz = 5 * depth;
// draw a blob
var grey = Math.floor(depth*400-200);
context.beginPath();
context.rect(t.x*depth*s+lrsCentreX-sz*0.5, t.y*depth*s+lrsCentreY-sz*0.5, sz, sz);
context.fillStyle = 'rgba('+grey+','+grey+','+grey+',1)';
context.fill();
}
strobe+=1;
for (var i=0; i<nmes.length; i++)
{
if (nmes[i].hitpoints<=0) continue;
var x = modulo(localPosition.x - nmes[i].pos.x)-512;
var y = modulo(localPosition.y - nmes[i].pos.y)-512;
var z = modulo(localPosition.z - nmes[i].pos.z)-512;
var t = scannerView.transform(x,y,z);
var depth = focalPoint*5 / ((t.z + 1400) +1);
var sz = 8 * depth;
// draw a blob
var red = Math.floor(depth*400-200);
context.beginPath();
// context.rect(t.x*depth*s+centreX-sz*0.5, t.y*depth*s+centreY-sz*0.5, sz, sz);
context.arc(t.x*depth*s+lrsCentreX, t.y*depth*s+lrsCentreY, sz, 0, Math.PI*2.0);
context.fillStyle = 'rgba('+red+','+red*(strobe&4)+','+red*(strobe&8)+',1)';
context.fill();
}
}
// galactic scan
function renderGalacticScanner()
{
// logic for enabling/disabling the scanner
if (mapOffScreen)
{
mapCentreY = centreY*2;
mapCentreX = 0;
}
mapTargetX = 0;
mapTargetY = overlayMode == eGalacticScanner ? 0 : centreY*2;
var offFade = overlayMode == eGalacticScanner ? 1.0-Math.abs(mapTargetY-mapCentreY) / (centreY*2) : Math.abs(mapTargetY-mapCentreY) / (centreY*2);
if (Math.abs(mapTargetY-mapCentreY)>4)
{
// animate
var dy = mapTargetY - mapCentreY;
mapCentreY += dy/8;
mapOffScreen = false;
}
else
{
mapOffScreen = overlayMode!=eGalacticScanner;
}
// check if scanner is suppose to be on
if (mapOffScreen) return;
var offX = mapCentreX;
var offY = mapCentreY;
var scaleX = mapScale.x;
var scaleY = mapScale.y;
// clear a shaded area
context.beginPath();
context.rect(border.x+offX, border.y+offY, mapScale.x*16, mapScale.y*8);
context.fillStyle = 'rgba(0,0,128,0.5)';
context.fill();
context.beginPath();
for (var i=0; i<=galaxyMapSize.x; i++)
{
context.moveTo(scaleX*i+border.x+offX, border.y+offY);
context.lineTo(scaleX*i+border.x+offX, scaleY*(galaxyMapSize.y)+offY+border.y);
}
context.strokeStyle = '#c0c0c0';
context.lineWidth = 4;
context.stroke();
context.beginPath();
for (var j=0; j<=galaxyMapSize.y; j++)
{
context.moveTo(border.x+offX, scaleY*(j)+offY+border.y);
context.lineTo(scaleX*(galaxyMapSize.x)+offX+border.x, scaleY*(j)+offY+border.y);
}
context.strokeStyle = '#c0c0c0';
context.lineWidth = 4;
context.stroke();
// ping every 5 seconds
var d = new Date();
var currentTime = d.getTime();
var distance = ((currentTime - gameStart)%10000)/10000;
pingRadius = distance * canvas.width/2;
context.globalCompositeOperation='source-over';
var shipX = shipPing.x * scaleX + border.x+offX;
var shipY = shipPing.y * scaleY + border.y+offY;
// update map with locations of ships
for (var b=0; b<boardPieces.length; b++)
{
// fade in and out board pieces as the ping passes over them
boardPieces[b].render(shipX, shipY, pingRadius);
}
context.globalCompositeOperation='lighter';
context.globalAlpha = 1;
context.beginPath();
var gradient = context.createRadialGradient(shipX, shipY, pingRadius*0.5, shipX, shipY, pingRadius*2);
gradient.addColorStop(0, 'rgba(128, 255, 128,0)');
gradient.addColorStop(1, 'rgba(128, 255, 128,'+(1-distance)*(offFade)+')');
context.fillStyle = gradient;
context.arc(shipX, shipY, pingRadius*2, Math.PI*2, false);
context.fill();
// render hyperspace location
clampX = warpLocation.x*mapScale.x+border.x;
clampY = warpLocation.y*mapScale.y+border.y;
context.beginPath();
context.moveTo(clampX+offX, border.y+offY)
context.lineTo(clampX+offX, scaleY*(galaxyMapSize.y)+border.y+offY);
context.moveTo(border.x+offX, clampY+offY);
context.lineTo(scaleX*(galaxyMapSize.x)+border.x+offX, clampY+offY);
if (warpLocked)
{
warpAnim=(warpAnim+1)%(Math.sqrt(scaleX*scaleX+scaleY*scaleY)*0.5);
context.arc(clampX+offX, clampY+offY, warpAnim, 0, Math.PI*2, false);
}
context.strokeStyle = '#c0ffc0';
context.lineWidth = 1;
context.stroke();
}
// initialization
function init()
{
// setup canvas and context
canvas = document.getElementById('star-raiders');
context = canvas.getContext('2d');
// set canvas to be window dimensions
resize();
// create event listeners
canvas.addEventListener('mousemove', mouseMove);
canvas.addEventListener('click', mouseClick);
canvas.addEventListener('mousedown', mouseDown);
canvas.addEventListener('mouseup', mouseUp);
canvas.addEventListener("mousewheel", mouseWheel, false);
document.addEventListener('keydown', processKeydown, false);
window.addEventListener('resize', resize);
// initialze variables
window.AudioContext = window.AudioContext||window.webkitAudioContext;
audioContext = new AudioContext();
// init parse
Parse.initialize("QiC9M2aTG3f4OpldOxdbav1VAwDuwDIX65GyBmYe", "RMg2Xv02FXTXu3d1UfiMeTsYbotrH4oSB7Pcbvyi");
// track views
var details = { Width:''+canvas.width, Height:''+canvas.height, Browser:window.navigator.userAgent};
Parse.Analytics.track('Viewed', details);
// audio
initAudio();
// load cookies
// emergency delete
// DeleteCookie("lastScore");
// DeleteCookie("bestRanks");
LoadRanks();
// load stats from local store
LoadStats();
// start game
// StartGame(novice);
// init title
TitleScreen();
}
function TitleScreen()
{
clearTitleClick = true;
// init engine sounds
StopEngine();
// enable title
titleScreen = true;
// credit time
titleStartTime = new Date().getTime();
// populate local space
SetupAsteriods(localSpaceCubed*0.25);
SetupTitleButtons();
// override
warpspread = 2;
// have starfield not track mouse
trackMouse = false;
// centre tracking
tX = cX;
tY = cY;
// set scroller
initVelocity = -1.0;
termVelocity = -10.0;
// fetch ranks
CacheGlobalRankings();
}
function StartGame(difficulty)
{
// remove titlescreen
titleScreen = false;
clearTitleClick = false;
endGameEvent = playing;
// log
Parse.Analytics.track('Started', {difficulty:difficultyNames[difficulty]});
// initialze variables
ClearGame();
//
SetShipLocation(galaxyMapSize.x*0.5, galaxyMapSize.y*0.5);
// initial ping
shipPing.x = shipPosition.x;
shipPing.y = shipPosition.y;
// buttons
SetupButtons();
// populate map
BoardSetup(difficulty);
// populate local space
SetupAsteriods(localSpaceCubed);
// populate shiplocation
SetupNMEs(GetPieceAtShipLocation());
var d = new Date();
gameStart = d.getTime();
// override
warpspread = 2;
// track mouse
trackMouse = true;
// init engine sounds
InitEngine();
}
// input functions
function mouseMove(event)
{
var rect = canvas.getBoundingClientRect();
mouseX = event.clientX - rect.left;
mouseY = event.clientY - rect.top;
SetWarpPoint(mouseX, mouseY, false);
if (event.which&1 && dragging)
{
DragEvent(event);
}
}
function mouseDown(event)
{
console.log("mousedown" + event.which);
if (clearTitleClick==false) return;
if (CheckButtons(mouseX, mouseY, false) == true) return;
if (event.which&1)
{
dragging = true;
DragEventStart(event);
}
}
function mouseUp(event)
{
clearTitleClick=true;
if (event.which&1 && dragging)
{
DragEventDone(event);
}
}
function mouseClick()
{
buttonpressed = CheckButtons(mouseX, mouseY, true);
if (buttonpressed || endGameEvent!=playing)
{
return;
}
if (titleScreen==true) return;
if (overlayMode == eNone || overlayMode == eTargetComputer)
{
FirePhotons();
}
// lock in warp point
if (overlayMode == eGalacticScanner)
{
if (!warpLocked)
SetWarpPoint(mouseX, mouseY, true);
else
ClearWarpPoint();
}
}
function resize()
{
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// compute centre of screen
centreX = canvas.width/2;
centreY = canvas.height/2;
fontsize = 21;
// resize font based on canvas size
mapScale.x = canvas.width/(galaxyMapSize.x+6);
mapScale.y = canvas.height/(galaxyMapSize.y+3);
border.x = mapScale.x*3;
border.y = mapScale.y;
lrScale.x = canvas.width*16/18;
lrScale.y = canvas.height*16/20;
do
{
fontsize-=0.1;
context.font = fontsize + 'pt Orbitron';
var fits = context.measureText('group');
}while((fits.width+20>mapScale.x || mapScale.y<fontsize*4) && fontsize >5);
// reset buttons
if (titleScreen == true)
SetupTitleButtons();
else if (endGameEvent ==playing)
SetupButtons();
else
SetupMainMenuButton();
}
function renderInformation()
{
switch (overlayMode)
{
case eGalacticScanner:
renderGalaxyInformation();
break;
case eLongRange:
renderLongRangeInformation();
break;
}
renderStarDate();
renderVelocity();
renderEnergy();
renderKills();
}
function renderGalaxyInformation()
{
var scaleX = mapScale.x;
var scaleY = mapScale.y;
var i = GetBoardPieceScreen(mouseX, mouseY);
var targets = 'empty';
if (i>0 && boardPieces[i].status!=0)
{
targets = boardPieces[i].numTargets;
if (boardPieces[i].type==base) targets = 'starbase';
}
// render timer
var piece = boardPieces[targetBase];
if (piece.nextMove!=0)
{
var x = piece.location.x*scaleX+border.x+mapCentreX;
var y = piece.location.y*scaleY+border.y+mapCentreY;
var currentTime = (new Date()).getTime() ;
var gameTime = Math.floor((currentTime - gameStart));
var remainingTime= Math.floor((piece.nextMove * 10000 - gameTime) / 600);
var segment = (Math.PI*2 * remainingTime / 100);
context.beginPath();
context.moveTo(x+scaleX*0.5, y+scaleY*0.5);
context.arc(x+scaleX*0.5, y+scaleY*0.5, scaleX<scaleY?scaleY:scaleY, Math.PI*1.5-segment, Math.PI*1.5);
context.fillStyle = 'rgba(255,0,0,0.7)';
context.fill();
context.strokeStyle = 'rgba(255,128,0,0.7)';
context.stroke();
context.globalAlpha = 1.0;
context.font = '12pt Orbitron';
context.fillStyle = 'rgb(255,128,0)';
context.textAlign = "right";
context.fillText('.' + Math.floor(remainingTime), x+scaleX*0.9, y+20);
}
context.globalAlpha = 1.0;
context.font = '20pt Orbitron';
context.fillStyle = 'rgb(255,255,0)';
context.textAlign = "left";
context.fillText('Targets: ' + targets, border.x+mapCentreX, border.y+mapScale.y*8+24+mapCentreY);
var fuel = ShipCalculateWarpEnergy(mouseX, mouseY);
context.font = '20pt Orbitron';
context.fillStyle = 'rgb(255,255,0)';
context.textAlign = "center";
context.fillText('Warp Energy: ' + fuel, canvas.width/2+mapCentreX, border.y+mapScale.y*8+24+mapCentreY);
if (warpLocked)
{
// does this in mouse coords for rollover.. so have to add the border back in
var fuel = ShipCalculateWarpEnergy(warpLocation.x*mapScale.x+border.x, warpLocation.y*mapScale.y+border.y);
warpEnergy = fuel;
context.textAlign = "centre";
context.font = '22pt Orbitron';
context.fillStyle = 'rgb(0,0,255)';
context.fillText('Energy: ' + fuel, warpLocation.x*mapScale.x+border.x+mapCentreX, warpLocation.y*mapScale.y+mapCentreY+border.y-12);
}
var x = shipPosition.x*scaleX+border.x;
var y = shipPosition.y*scaleY+border.y;
renderIconShip(x, y, fontsize*1.5);
context.globalAlpha = 1.0;
context.font = '20pt Orbitron';
context.fillStyle = 'rgb(255,255,255)';
context.textAlign = "center";
context.fillText('Galactic Scanner', canvas.width/2, 30);
}
function renderLongRangeInformation()
{
context.globalAlpha = 1.0;
// renderIconShip(centreX, centreY, 10);
context.globalAlpha = 1.0;
context.font = '20pt Orbitron';
context.fillStyle = 'rgb(255,255,255)';
context.textAlign = "center";
context.fillText('Long Range Scanner', canvas.width/2, 30);
}
function renderIconShip(posx, posy, scale)
{
// render our ship
var x = posx+mapCentreX;
var y = posy+mapCentreY;
context.beginPath();
context.arc(x, y, scale*0.5, 0, Math.PI*2);
context.strokeStyle = 'rgb(255,255,255)';
context.lineWidth =1;
context.stroke();
context.moveTo(x, y-scale*1.5);
context.lineTo(x-scale, y+scale*1.5);
context.lineTo(x, y);
context.lineTo(x+scale, y+scale*1.5);
context.closePath();
context.stroke();
}
function gradon(radian)
{
return Math.floor(radian*100 / (Math.PI*2));
}
function leadPadding(value, num, sign)
{
var padded = value>=0 ? (sign?'+':'') :'-';
var absvalue = Math.abs(value);
for (var i=1;i<num;i++)
{
padded += (absvalue<Math.pow(10,i)) ? "0":"";
}
return padded+absvalue;
}
function renderTargetingComputer()
{
if (!targetComputer) return;
var x = centreX;
var y = centreY;
var w = canvas.width/16;
var h = canvas.height/16;
context.beginPath();
context.moveTo(x+w/4, y);
context.lineTo(x+w, y);
context.moveTo(x-w/4, y);
context.lineTo(x-w, y);
if (viewingFront())
{
context.moveTo(x, y-h/4);
context.lineTo(x, y-h);
context.moveTo(x, y+h/4);
context.lineTo(x, y+h);
}
context.lineWidth = 7;
context.strokeStyle = 'rgba(255,0,0,0.5)';
context.stroke();
context.lineWidth = 3;
context.strokeStyle = 'rgba(255,0,0,1)';
context.stroke();
if (viewingFront())
{
var x1 = mapScale.x*16;
var y1 = mapScale.y*8;
var xw =border.x;
var yh =mapScale.y*2;
context.beginPath();
context.rect(x1, y1, xw, yh);
context.fillStyle = 'rgba(0,0,128,0.5)';
context.fill();
context.rect(x1+xw*0.25, y1+yh*0.25, xw*0.5, yh*0.5);
context.moveTo(x1, y1+yh*0.5);
context.lineTo(x1+xw*0.25, y1+yh*0.5);
context.moveTo(x1+xw*0.75, y1+yh*0.5);
context.lineTo(x1+xw, y1+yh*0.5);
context.moveTo(x1+xw*0.5, y1);
context.lineTo(x1+xw*0.5, y1+yh*0.25);
context.moveTo(x1+xw*0.5, y1+yh*0.75);
context.lineTo(x1+xw*0.5, y1+yh);
context.lineWidth = 4;
context.strokeStyle = 'rgba(255,255,255,0.5)';
context.stroke();
// compute tracking position
var distance = 0;
var gradonTheta = 0;
var gradonPhi = 0;
if (trackingTarget>=0 && trackingTarget < nmes.length && nmes[trackingTarget].hitpoints)
{
var x = modulo2(localPosition.x - nmes[trackingTarget].pos.x, localSpaceCubed)-localSpaceCubed*0.5;
var y = modulo2(localPosition.y - nmes[trackingTarget].pos.y, localSpaceCubed)-localSpaceCubed*0.5;
var z = modulo2(localPosition.z - nmes[trackingTarget].pos.z, localSpaceCubed)-localSpaceCubed*0.5;
var t = orientation.transform(x,y,z);
distance = Math.round(Math.sqrt(t.x*t.x+t.y*t.y+t.z*t.z));
if (t.z<0) distance*=-1;
// compute angles
var phi = Math.atan2(t.x, t.z);
var rx1 = (modulo2(phi+Math.PI, Math.PI*2) / (Math.PI*2)) * (xw-30) + x1+15;
var theta = Math.atan2(t.y, Math.sqrt(t.x*t.x+t.z*t.z));
var ry1 = (modulo2(theta+Math.PI, Math.PI*2) / (Math.PI*2)) *2* (yh-15) + y1+15-yh*0.5;
context.beginPath();
context.rect(rx1-8, ry1-8, 16,16);
context.rect(rx1-15, ry1-15, 5, 30);
context.rect(rx1+10, ry1-15, 5, 30);
context.fillStyle= 'yellow';
context.fill();
// convert to gradons
gradonTheta = gradon(theta);
gradonPhi = gradon(phi);
}
context.font = '20pt Orbitron';
context.fillStyle = 'rgb(255,255,0)';
context.textAlign = "left";
context.fillText('T:'+trackingTarget, x1, canvas.height-15);
context.fillText('R:'+leadPadding(distance,3, true), x1+xw*0.5, canvas.height-15);
context.fillText('Φ:'+leadPadding(gradonPhi,2, true), x1, y1-15);
context.fillText('θ:'+leadPadding(gradonTheta,2, true), x1+xw*0.5, y1-15);
}
}
function renderStarDate()
{
var d = new Date();
var currentTime = d.getTime();
var gameTime = currentTime - gameStart;
var decimalTime = gameTime / 60000;
context.font = '20pt Orbitron';
context.fillStyle = 'rgb(255,255,0)';
context.textAlign = "left";
leadingzero = decimalTime<10 ? '0':'';
context.fillText('StarDate: ' + leadingzero + decimalTime.toFixed(2), canvas.width-border.x+15, canvas.height-15);
}
function renderKills()
{
context.font = '20pt Orbitron';
context.fillStyle = 'rgb(255,255,0)';
context.textAlign = "left";
leadingzero = kills<10 ? '0':'';
context.fillText('Kills: ' + leadingzero + kills, 15, canvas.height-15);
}
function renderVelocity()
{
context.font = '20pt Orbitron';
context.fillStyle = 'rgb(255,255,0)';
context.textAlign = "right";
leadingzero = shipVelocity<10 ? '0':'';
context.textAlign = "right";
context.fillText('Velocity: ', canvas.width-mapScale.x-40, 30);
context.textAlign = "right";
var intVel = Math.floor(shipVelocity);
context.fillText(leadingzero + intVel, canvas.width-mapScale.x, 30);
context.textAlign = "left";
var fracVel = Math.floor((shipVelocity-intVel)*100);
postZero = fracVel<10? '0':'';
context.fillText('.' + fracVel + postZero, canvas.width-mapScale.x, 30);
}
function renderEnergy()
{
context.font = '20pt Orbitron';
context.fillStyle = 'rgb(255,255,0)';
context.textAlign = "center";
leadingzero = energy<10 ? '000':'';
leadingzero = energy<100 ? '00':'';
leadingzero = energy<1000 ? '0':'';
context.fillText('Energy: ' + leadingzero + energy.toFixed(0), canvas.width/2, canvas.height-15);
}
function renderGameScreen()
{
// RenderStarDome();
renderStarfield();
RenderAsteriods();
RenderNMEs();
RenderParticles();
renderShield();
}
function RenderRanks()
{
context.textAlign = "center";
context.fillStyle = 'rgb(0,0,255)';
context.font = '20pt Orbitron';
context.fillText('Best Ranks', canvas.width/2, 150);
for (var i=0; i<bestRanks.length; i++)
{
// update the last score percentile
bestRanks[i].percentile = UpdatePercentile(bestRanks[i].rank);
if ((i*50+180) < (canvas.height-90))
{
context.font = '20pt Orbitron';
context.fillText('Rank: '+ rank(bestRanks[i].rank)+' - Ranked in top '+bestRanks[i].percentile.toFixed(1)+'%', canvas.width/2, 180+i*50);
context.font = '12pt Orbitron';
context.fillText('Date: '+ bestRanks[i].date, canvas.width/2, 200+i*50);
}
}
}
function RenderStatistics(stat, lastgame, fade)
{
var x = canvas.width/2 - 500;
var y = 150;
var yinc = 20;
context.lineWidth = 1;
context.font = '20pt Orbitron';
context.strokeStyle = 'rgba(0,255,0,'+fade+')';
context.textAlign = 'left';
context.strokeText(lastgame?'Last Game Stats':'All time stats', x, y);
y+=20;
context.fillStyle = 'rgba(255,0,0,'+fade+')';
context.font = '14pt Orbitron';
context.fillText('You Won : '+ stat.endgames[allDead], x, y+=yinc);
context.fillText('You were destroyed: '+ stat.endgames[destroyed], x, y+=yinc);
context.fillText('You run out of energy: '+ stat.endgames[energyLost], x, y+=yinc);
context.fillText('All your bases gone: '+ stat.endgames[basesGone], x, y+=yinc);
context.fillText('Manually Aborted: '+ stat.endgames[aborted], x, y+=yinc);
context.fillText('Difficulty: '+ stat.difficulty, x, y+=yinc);
context.fillText('Played: '+ stat.played, x, y+=yinc);
context.fillText('Meteors Fragmented: '+ stat.roidsFragmented, x, y+=yinc);
context.fillText('Meteors Destroyed: '+ stat.roidsHit, x, y+=yinc);
context.fillText('Refueled: '+ stat.refuel.toFixed(2), x, y+=yinc);
context.fillText('Shots Fired: '+ stat.shots, x, y+=yinc);
context.fillText('Shields Hit: '+ stat.shieldsHit, x, y+=yinc);
context.fillText('Ships Hit: '+ stat.shipsHit, x, y+=yinc);
context.fillText('Shots Hit: '+ stat.deflects, x, y+=yinc);
context.fillText('Accuracy: '+ stat.accuracy.toFixed(2), x, y+=yinc);
context.fillText('Starbases lost: '+ stat.bases, x, y+=yinc);
context.fillText('Starbases killed: '+ stat.killTypes[0], x, y+=yinc);
context.fillText('Fighters killed: '+ stat.killTypes[1], x, y+=yinc);
context.fillText('Cruisers killed: '+ stat.killTypes[2], x, y+=yinc);
context.fillText('Basestars killed: '+ stat.killTypes[3], x, y+=yinc);
context.fillText('Times jumped: '+ stat.jumped, x, y+=yinc);
context.fillText('Energy jumped: '+ stat.jumpedEnergy.toFixed(2), x, y+=yinc);
context.fillText('Sectors jumped: '+ stat.travelled, x, y+=yinc);
context.fillText('Jumps cancelled: '+ stat.jumpCancelled, x, y+=yinc);
context.fillText('Damaged Systems: '+ stat.damaged, x, y+=yinc);
context.fillText('Metrons Travelled: '+ stat.distance.toFixed(2), x, y+=yinc);
context.fillText('Energy Used: '+ stat.energy.toFixed(2), x, y+=yinc);
}
function RenderInstructions()
{
var time = new Date().getTime() - titleStartTime;
var dt = modulo2(time, 40000);
if (dt<1000)
fade = dt/1000;
else if (dt<10000)
fade = 1;
else if (dt>10000)
fade = (11000-dt)/1000;
fade = Math.min(1, Math.max(0, fade));
context.fillStyle = 'rgba(255,64,0,'+fade+')';
context.font = '20pt Orbitron';
context.fillText('original game written by', canvas.width/2, 150);
context.fillText('Respectfully rejuvenated by', canvas.width/2, 250);
context.font = '30pt Orbitron';
context.fillStyle = 'rgba(255,128,0,'+fade+')';
context.fillText('Doug Neubauer, Atari, 1979', canvas.width/2, 200);
context.fillText('Andi Smithers, 2014', canvas.width/2,300);
context.font = '14pt Orbitron';
context.fillStyle = 'rgba(0,128,0,'+fade+')';
context.fillText('Version 0.9 beta', canvas.width/2, 360);
context.fillText('todo: Adv Difficulty warp', canvas.width/2, 380);
context.fillText('todo: Damage/Destroyed systems', canvas.width/2, 400);
fade = 0;
if (dt>20000)
fade = (21000-dt)/1000;
else if (dt>11000)
fade = 1;
else if (dt>10000)
fade = (dt-10000)/1000;
fade = Math.min(1, Math.max(0, fade));
var x = (canvas.width/2) + 500;
context.fillStyle = 'rgba(255,255,255,'+fade+')';
context.font = '20pt Orbitron';
context.textAlign = "right";
context.fillText('Instructions', x, 150);
context.fillStyle = 'rgba(0,255,0,'+fade+')';
context.fillText('Protect starbases from being destroyed', x, 180);
context.fillText('Use Hyperspace chart to warp to a sector',x, 270);
context.fillText('Clear all enemies in a sector to remove threat', x, 330);
context.fillText('Use shields to defend against meteors and weapons', x,390);
context.fillStyle = 'rgba(0,192,0,'+fade+')';
context.fillText('Starbases are vunerable when surrounded', x, 210);
context.fillText('Select sector with cross hair before warping',x, 300);
context.fillText('Dock at starbases to replenish energy', x,360);
context.fillText('Keep Warp Cursor Centred for optimal warp', x,420);
fade = 0;
if (dt>30000)
fade = (31000-dt)/1000;
else if (dt>21000)
fade = 1;
else if (dt>20000)
fade = (dt-20000)/1000;
fade = Math.min(1, Math.max(0, fade));
var x = (canvas.width/2) - 500;
context.fillStyle = 'rgba(255,255,255,'+fade+')';
context.font = '20pt Orbitron';
context.textAlign = "left";
context.fillText('Keyboard short cuts', x, 150);
context.fillStyle = 'rgba(0, 255,255,'+fade+')';
context.fillText('S : Shields', x, 180);
context.fillText('H : Hyperspace', x, 210);
context.fillText('L : Longrange scanner',x, 240);
context.fillText('G : Galactic chart', x, 270);
context.fillText('C : Computer', x,300);
context.fillText('T : Tracking', x,330);
context.fillText('M : Select Target', x,360);
context.fillText('A : Aft/Front view', x,390);
context.fillText('0-9 : Throttle (Mousewheel as well)', x,420);
context.fillText('Mouse to target, Left Mouse fires weapons', x,450);
fade = 0;
if (dt<1000 && time>40000)
fade = (1000-dt)/1000;
else if (dt>31000)
fade = 1;
else if (dt>30000)
fade = (dt-30000)/1000;
fade = Math.min(1, Math.max(0, fade));
var x = (canvas.width/2) + 500;
context.fillStyle = 'rgba(255,255,255,'+fade+')';
context.font = '20pt Orbitron';
context.textAlign = "right";
context.fillText('Strategies', x, 150);
context.fillStyle = 'rgba(128,255,0,'+fade+')';
context.fillText('Destroy Fast moving patrols first', x, 180);
context.fillText('Starbases when destroyed will spawn a new enemy patrol', x,240);
context.fillText('Destroying Asteriods is for fun only',x, 300);
context.fillText('Cancel Hyperwarps before 99 and you get a free boost(well almost free)',x, 360);
context.fillText('Watch your Energy and dont forget to refuel', x,420);
context.fillStyle = 'rgba(0,192,0,'+fade+')';
context.fillText('Patrols move every 30 seconds or so', x, 210);
context.fillText('If you cant save the base, destroying it denies the enemy', x,270);
context.fillText('Never come out of warp without shields up', x,330);
context.fillText('12.00 is a good cruise speed', x,390);
}
function RenderCredits()
{
var time = new Date().getTime() - titleStartTime;
var dt = modulo2(time, 40000);
if (dt<1000)
fade = dt/1000;
else if (dt<10000)
fade = 1;
else if (dt>10000)
fade = (11000-dt)/1000;
fade = Math.min(1, Math.max(0, fade));
context.fillStyle = 'rgba(255,64,0,'+fade+')';
context.font = '20pt Orbitron';
context.fillText('original game written by', canvas.width/2, 150);
context.fillText('Respectfully rejuvenated by', canvas.width/2, 250);
context.font = '30pt Orbitron';
context.fillStyle = 'rgba(255,128,0,'+fade+')';
context.fillText('Doug Neubauer, Atari, 1979', canvas.width/2, 200);
context.fillText('Andi Smithers, 2014', canvas.width/2,300);
context.font = '14pt Orbitron';
context.fillStyle = 'rgba(0,128,0,'+fade+')';
context.fillText('Version 0.9 beta', canvas.width/2, 360);
context.fillText('todo: Adv Difficulty warp', canvas.width/2, 380);
context.fillText('todo: Damage/Destroyed systems', canvas.width/2, 400);
fade = 0;
if (dt>20000)
fade = (21000-dt)/1000;
else if (dt>11000)
fade = 1;
else if (dt>10000)
fade = (dt-10000)/1000;
fade = Math.min(1, Math.max(0, fade));
var x = (canvas.width/2) + 500;
context.fillStyle = 'rgba(255,255,255,'+fade+')';
context.font = '20pt Orbitron';
context.textAlign = "right";
context.fillText('Thanks', x, 150);
context.fillStyle = 'rgba(0,255,0,'+fade+')';
context.fillText('Brian Dumlao for QA pass in his off hours', x, 180);
context.fillText('Codepen for making it easy to prototype',x, 240);
context.fillText('All the Disney folks I work with daily',x, 300);
context.fillStyle = 'rgba(0,192,0,'+fade+')';
context.fillText('Chris Chapman for pointing me at some cool webresources', x, 210);
context.fillText('Parse for a real easy to use datastore',x, 270);
context.fillText('Sheri Smithers for putting up with my late nights',x, 330);
fade = 0;
if (dt>30000)
fade = (31000-dt)/1000;
else if (dt>21000)
fade = 1;
else if (dt>20000)
fade = (dt-20000)/1000;
fade = Math.min(1, Math.max(0, fade));
var x = (canvas.width/2) - 500;
context.fillStyle = 'rgba(255,255,255,'+fade+')';
context.font = '20pt Orbitron';
context.textAlign = "left";
context.fillText('Objective of this project', x, 150);
context.fillStyle = 'rgba(128,255,0,'+fade+')';
context.fillText('This game is completely procedural.', x, 180);
context.fillText('This project started out as an exercise', x, 240);
context.fillText('the need to go into webGL or other extensions', x, 300);
context.fillText('Taking about 2 weeks for all modules', x,360);
context.fillText('It was written in about 3 hours per evening over 5 weeks', x,420);
context.fillStyle = 'rgba(0,192,0,'+fade+')';
context.fillText('Art, Sound and data are all generated',x, 210);
context.fillText('into creating HTML5 canvas games without', x,270);
context.fillText('The project was built with about 8 or so modules',x, 330);
context.fillText('However it took 3 more weeks to build them into the game', x,390);
context.fillText('whilst my wife, sheri and son, edward slept - andi.', x,450);
fade = 0;
if (dt<1000 && time>40000)
fade = (1000-dt)/1000;
else if (dt>31000)
fade = 1;
else if (dt>30000)
fade = (dt-30000)/1000;
fade = Math.min(1, Math.max(0, fade));
var x = (canvas.width/2) - 500;
context.fillStyle = 'rgba(255,255,255,'+fade+')';
context.font = '20pt Orbitron';
context.textAlign = "left";
context.fillText('A note from the author- sept 2014', x, 150);
context.fillStyle = 'rgba(128,255,0,'+fade+')';
context.fillText('This game is completely procedural.', x, 180);
context.fillText('This project started out as an exercise', x, 240);
context.fillText('the need to go into webGL or other extensions', x, 300);
context.fillText('Taking about 2 weeks for all modules', x,360);
context.fillText('It was written in about 3 hours per evening over 5 weeks', x,420);
context.fillStyle = 'rgba(0,192,0,'+fade+')';
context.fillText('Art, Sound and data are all generated',x, 210);
context.fillText('into creating HTML5 canvas games without', x,270);
context.fillText('The project was built with about 8 or so modules',x, 330);
context.fillText('However it took 3 more weeks to build them into the game', x,390);
context.fillText('whilst my wife, sheri and son, edward slept - andi.', x,450);
}
function RenderStats()
{
var time = new Date().getTime() - titleStartTime;
var dt = modulo2(time, 30000);
var fade = 0;
if (dt<1000)
fade = dt/1000;
else
fade = (16000-dt)/1000;
fade = Math.min(1, Math.max(0, fade));
RenderStatistics(statistics, true, fade);
fade = 0;
if (dt<1000 && time>30000)
fade = (1000-dt)/1000;
else if (dt<15000)
fade = 0;
else
fade = (dt-15000)/1000;
fade = Math.min(1, Math.max(0, fade));
RenderStatistics(totals, false, fade);
}
function RenderTitleScreen()
{
renderStarfield();
RenderAsteriods();
RenderButtons();
switch (attractMode)
{
case 0:
RenderInstructions();
break;
case 1:
RenderStats();
break;
case 2:
RenderRanks();
break;
case 3:
RenderCredits();
break;
}
context.font = '61pt Orbitron';
context.lineWidth = 8;
context.textAlign = "center";
context.strokeStyle = 'rgba(0,128,0,0.5)';
context.strokeText('STAR RAIDERS - 2014', canvas.width/2,100);
context.lineWidth = 1;
context.font = '60pt Orbitron';
context.fillStyle = 'rgb(255,255,0)';
context.fillText('STAR RAIDERS - 2014', canvas.width/2,100);
var titlepixel = context.measureText('STAR RAIDERS - 2014');
// update the last score percentile
lastScore.percentile = UpdatePercentile(lastScore.rank);
context.textAlign = "center";
context.font = '20pt Orbitron';
context.fillStyle = 'rgb(0,0,255)';
context.textAlign = "center";
context.fillText('Last Score: '+ rank(lastScore.rank) +' - Ranked in top '+lastScore.percentile.toFixed(1)+'%', canvas.width/2, canvas.height- 40);
context.font = '12pt Orbitron';
context.fillText('Date: '+ lastScore.date, canvas.width/2, canvas.height- 20);
}
function UpdatePercentile(rank)
{
var percentile = 100;
// update percentiles
if (gameHitsPerRanks[rank+247]) percentile = 100 - (gameHitsPerRanks[rank+247] / gameTotalPlays * 100);
return percentile;
}
function ranksort(a, b)
{
return (b.rank-a.rank);
}
function AddNewRank(ranking, date)
{
var percentile = UpdatePercentile(ranking);
lastScore.date = date;
lastScore.rank = ranking;
lastScore.percentile = percentile; // compute using database
// adds a rank making it 11
bestRanks.push({rank:ranking, date:date, percentile:percentile});
// sorts rank in high to low
bestRanks.sort(ranksort);
// pops the 11th off
bestRanks.pop();
// update local db
SaveRanks();
}
function LoadStats()
{
var result = LoadCookie("statistics");
if (result) statistics = result;
var result = LoadCookie("totals");
if (result) totals = result;
}
function SaveStats()
{
SaveCookie("statistics", statistics);
SaveCookie("totals", totals);
}
function LoadRanks()
{
var result = LoadCookie("lastScore");
if (result)
{
lastScore = result;
lastScore.date = new Date(lastScore.date);
}
var result = LoadCookie("bestRanks");
if (result)
{
bestRanks = result;
for (var i=0; i<bestRanks.length;i++) bestRanks[i].date = new Date(bestRanks[i].date);
}
}
function SaveRanks()
{
SaveCookie("lastScore", lastScore);
SaveCookie("bestRanks", bestRanks);
}
function SaveCookie(name, value)
{
var cookie = [name, '=', JSON.stringify(value), '; domain=.', window.location.host.toString(), '; path=/;'].join('');
document.cookie = cookie;
}
function LoadCookie(name)
{
var result = document.cookie.match(new RegExp(name + '=([^;]+)'));
result && (result = JSON.parse(result[1]));
return result;
}
function DeleteCookie(name)
{
document.cookie = [name, '=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/; domain=.', window.location.host.toString()].join('');
}
// setup buttons
function SetupTitleButtons()
{
// erase old buttons
buttons = [];
var w = canvas.width;
var b = border;
var ms = mapScale;
var bx = border.x*0.2;
var bw = border.x*0.6;
new Button(w-bx-bw, b.y*3.2, bw, ms.y*0.7, "Novice", StartNovice, '0');
new Button(w-bx-bw, b.y*4.2, bw, ms.y*0.7, "Pilot", StartPilot, '1');
new Button(w-bx-bw, b.y*5.2, bw, ms.y*0.7, "Warrior", StartWarrior, '2');
new Button(w-bx-bw, b.y*6.2, bw, ms.y*0.7, "Commander", StartCommander, '3');
new Button(bx, b.y*3.2, bw, ms.y*0.7, "Statistics", ViewStats, '4');
new Button(bx, b.y*4.2, bw, ms.y*0.7, "Ranks", ViewRanks, '5');
new Button(bx, b.y*5.2, bw, ms.y*0.7, "Instructions", ViewInstructions, '6');
new Button(bx, b.y*6.2, bw, ms.y*0.7, "Credits", ViewCredits, '7');
}
function StartNovice()
{
PlayConfirm();
StartGame(novice);
}
function StartPilot()
{
PlayConfirm();
StartGame(pilot);
}
function StartWarrior()
{
PlayConfirm();
StartGame(warrior);
}
function StartCommander()
{
PlayConfirm();
StartGame(commander);
}
function ViewInstructions()
{
PlayConfirm();
attractMode = 0;
titleStartTime = new Date().getTime()-11000;
}
function ViewStats()
{
PlayConfirm();
attractMode = 1;
titleStartTime = new Date().getTime();
}
function ViewRanks()
{
PlayConfirm();
attractMode = 2;
}
function ViewCredits()
{
PlayConfirm();
attractMode = 3;
titleStartTime = new Date().getTime();
}
function UpdateTitleScreen()
{
localPosition.z= modulo2(localPosition.z-0.1, localSpaceCubed*0.25);
// cancel mouse tracking
tX = cX;
tY = cY;
moveStarfield();
UpdateAsteriods();
}
function renderOverlays()
{
switch (overlayMode)
{
case eGalacticScanner:
renderGalacticScanner();
if (!lrsOffScreen) renderLongRangeScanner();
break;
case eLongRange:
renderLongRangeScanner();
if (!mapOffScreen) renderGalacticScanner();
break;
case eTargetComputer:
renderTargetingComputer();
if (!lrsOffScreen) renderLongRangeScanner();
if (!mapOffScreen) renderGalacticScanner();
break;
default:
if (!lrsOffScreen) renderLongRangeScanner();
if (!mapOffScreen) renderGalacticScanner();
break;
}
}
// rendering functions
function render()
{
document.getElementById("star-raiders").style.background = backgroundColor;
context.clearRect(0, 0, canvas.width, canvas.height);
if (titleScreen == true) return RenderTitleScreen();
renderGameScreen();
if (endGameEvent == playing)
{
renderOverlays();
renderInformation();
}
RenderButtons();
if (endGameEvent == playing)
WeaponCollisions();
DrawRedAlert();
displayText();
}
// per frame tick functions
var previousTime = 0;
var currentTime = 0;
var freqHz = 0.016666;
function updateclocks()
{
// compute frequency
frameCount++;
// compute time between frames
currentTime = new Date().getTime();
if (previousTime) freqHz = (currentTime - previousTime)/1000.0;
previousTime = new Date().getTime();
}
// movement functions
function update()
{
if (pauseGame == true) return;
if (titleScreen == true) return UpdateTitleScreen();
if (endGameEvent!=playing)
{
tX = cX;
tY = cY;
}
moveStarfield();
UpdateAsteriods();
if (endGameEvent == playing)
{
UpdateBoard();
UpdateShipControls();
}
UpdateNMEs();
UpdateParticles();
if (endGameEvent == playing)
energyManagement();
else
EndGameEvent();
}
function animate()
{
// compute frequency
updateclocks();
// movement update
update();
// render update
render();
// trigger next frame
requestAnimationFrame(animate);
}
// keyboard controls
function processKeydown(event)
{
var key = String.fromCharCode(event.keyCode);
console.log("key = " + key);
if (CheckShortcuts(key)==false)
{
// throttle
if (key>='0' && key<='9')
{
var slider = GetControl("throttle");
slider.value = key - '0';
SetThrottle(slider);
}
if (key=='M') trackingTarget++;
if (trackingTarget>=nmes.length) trackingTarget = 0;
}
}
// setup buttons
function SetupButtons()
{
// erase old buttons
buttons = [];
var b = border;
var ms = mapScale;
var bx = border.x*0.2;
var bw = border.x*0.6;
new Button(bx, b.y*2.2, bw, ms.y*0.6, "Long Range", SwitchToLongRange, 'L');
new Button(bx, b.y*3.2, bw, ms.y*0.6, "Galaxy Chart", SwitchToGalaxyChart, 'G');
new Button(bx, b.y*4.2, bw, ms.y*0.6, "Hyperspace", ToggleWarp, 'H');
new Button(bx, b.y*5.2, bw, ms.y*0.6, "Shields", ToggleShields, 'S');
new Button(bx, b.y*6.2, bw, ms.y*0.6, "Target Comp", ToggleTargetComp, 'C');
new Button(bx, b.y*7.2, bw, ms.y*0.6, "Tracking", ToggleTrackingComp, 'T');
new Button(bx, b.y*8.2, bw, ms.y*0.6, "Swap View", swapView, 'A');
new Button(bx, b.y*1.2, bw, ms.y*0.6, "Pause Game", TogglePauseGame, 'P');
new Button(bx, b.y*0.2, bw, ms.y*0.6, "Abort Mission", AbortMission, 'Q');
new Slider(ms.x*20, b.y*2, border.x*0.25, ms.y*6, 10, true, 10, "throttle", SetThrottle);
}
function SwitchToLongRange(button)
{
console.log("switching to long range");
if (overlayMode == eLongRange)
{
overlayMode = targetComputer?eTargetComputer:eNone;
GetControl("Target Comp").state = targetComputer;
}
else
{
overlayMode = eLongRange;
}
button.state = overlayMode == eLongRange;
ClearButtonState("Galaxy Chart");
}
function SwitchToGalaxyChart(button)
{
console.log("switching to galatic range");
if (overlayMode == eGalacticScanner)
{
overlayMode = targetComputer?eTargetComputer:eNone;
GetControl("Target Comp").state = targetComputer;
}
else
{
overlayMode = eGalacticScanner;
}
button.state = overlayMode == eGalacticScanner;
ClearButtonState("Long Range");
}
function ToggleWarp(button)
{
// cant cancel now
if (triggerWarp == inHyperspace) return;
button.state^=1;
if (button.state == false && triggerWarp!=normalSpace) triggerWarp=cancelHyperspace;
else
{
triggerWarp = enterHyperspace;
PlayBeginHyperspace();
}
if (warpLocked && triggerWarp==enterHyperspace)
{
startText("jumping to hyperspace", border.x, 150);
}
else if (!warpLocked && triggerWarp==enterHyperspace)
{
startText("No hyperspace location set.\nYou could end up anywhere", border.x, 150);
}
else
{
startText("Aborting jump to hyperspace", border.x, 150);
}
}
function ToggleShields(button)
{
button.state^=1;
shieldUp=button.state;
splutterCount = 30;
splutterNoise = 60;
startText(shieldUp?"Shields Activated": "Shields Deactivated", border.x, 150);
PlayConfirm();
}
function ToggleTargetComp(button)
{
PlayConfirm();
if (overlayMode != eTargetComputer)
{
overlayMode = eTargetComputer;
targetComputer = 1;
button.state=1;
ClearButtonState("Long Range");
ClearButtonState("Galaxy Chart");
}
else
{
overlayMode = eNone;
button.state = 0;
targetComputer=0;
}
}
function ToggleTrackingComp(button)
{
button.state^=1;
trackingComputer= button.state;
startText(trackingComputer?"ship tracking enabled": "ship tracking disabled", border.x, 150);
PlayConfirm();
}
function TogglePauseGame(button)
{
PlayConfirm();
button.state^=1;
pauseGame = button.state;
startText(pauseGame ? "Paused Game":"Resume Game", border.x, 150);
}
function AbortMission(button)
{
if (GetControl("Confirm")==null)
{
PlayConfirm();
startText("Abort Mission (Y/N) ?", border.x, 150);
var b = border;
var ms = mapScale;
var bx = b.x*0.2;
var bw = b.x*0.6;
new Button(bx+bw*1.06, b.y*0.2, bw, ms.y*0.6, "Confirm", AbortMissionConfirm, 'Y');
new Button(bx+bw*2.12, b.y*0.2, bw, ms.y*0.6, "Cancel", AbortMissionCancel, 'N');
}
}
function AbortMissionConfirm(button)
{
PlayConfirm();
startText(" ", border.x, 150);
startText(" ", border.x, 150);
startText(" ", border.x, 150);
EndGame(aborted);
}
function AbortMissionCancel(button)
{
PlayConfirm();
SetupButtons();
startText("Cancelled Abort Mission ", border.x, 150);
}
function EndGame(endType)
{
// not sure how we got back here if we're not playing
if (endGameEvent != playing) return;
// andi: needs the tickers sequence
statistics.endgames[endType]++;
if (endType == destroyed)
{
DestroyShip();
}
if (endType == energyLost)
{
PowerDownShip();
}
// clear all buttons
buttons = [];
// clear redalert
redTime = 0;
// update statistics
UpdateAllTotals();
// calculate the player rank
var ranking = CalculateScore();
AddNewRank(ranking, new Date());
// save stats and rank to server
SaveStatistics();
// save stats to local storage
SaveStats();
// save new rank
UpdateRankings(ranking);
// need to go back to title.
// TitleScreen();
endGameTime = new Date().getTime();
endGameEvent = endType;
endGameLastMessage=-1;
endGameRank = rank(ranking);
SetupMainMenuButton();
}
function SetupMainMenuButton()
{
// main menu
buttons = [];
new Button(border.x, canvas.height/2, border.x, mapScale.y*0.6, "Return to Main Menu", TitleScreen, ' ');
}
function EndGameEvent()
{
if (endGameEvent == playing) return;
var currentTime = new Date().getTime();
var dx = currentTime - endGameTime;
var messageNum = Math.floor(dx/2500)%4;
console.log(messageNum);
if (endGameLastMessage != messageNum)
{
var endMessages = [
"Star fleet to all units",
"Star Cruiser 7 aborted mission", // aborted
"Star Cruiser 7 destroyed by zlyon fire", // destroyed
"Star Cruiser 7 depleted energy, mission aborted", // energylost
"Star Cruiser 7 all starbases lost, mission aborted", // basesGone
"Star Cruiser 7 all enemy units destroyed", // allDead
"Posthumous rank: ",
"Rank: "];
endGameLastMessage = messageNum;
if (messageNum == 0)
{
startText("",border.x, 150);
startText(endMessages[0], border.x, 150);
}
else if (messageNum == 1)
startText(endMessages[endGameEvent+1], border.x, 150);
else if (messageNum == 2)
startText(endMessages[endGameEvent==destroyed?6:7] + endGameRank,border.x, 150);
}
}
function DestroyShip()
{
// explode (asteriods)
trackingComputer=false;
targetComputer = false;
shieldUp = false;
// detroy base
SpawnAsteriodsAt(localPosition);
spawnX = localPosition.x;
spawnY = localPosition.y;
spawnZ = localPosition.z;
explodeEmitter.create();
dustEmitter.create();
PlayExplosion();
}
function PowerDownShip()
{
// explode (asteriods)
trackingComputer=false;
targetComputer = false;
shieldUp = false;
setShipVelocity = 0;
}
function CalculateScore(endType)
{
var Mtable =
[ 80, 60, 40,
76, 60, 50,
60, 50, 40,
111, 100, 90];
// compute M
var mindex = gameDifficulty*3;
if (endType == destroyed) mindex++;
if (endType != allDead) mindex++;
var M = Mtable[mindex];
M += 6*kills;
M -= Math.floor(statistics.energy/100);
M -= statistics.killTypes[base] * 3;
M -= statistics.bases * 18;
M -= Math.floor(statistics.timePlayed); // time is decimalized
statistics.rank = M;
totals.rank+=statistics.rank;
return M;
}
function SaveStatistics()
{
// save
var Statistics = Parse.Object.extend("Statistics");
var rankings = new Statistics();
// var statsjson = JSON.stringify(statistics);
// console.log("jsonstring = "+statsjson);
rankings.set('rank', statistics.rank);
rankings.set('kills', statistics.kills);
rankings.set('timePlayed', statistics.timePlayed);
rankings.set('difficulty', statistics.difficulty);
rankings.set('played',statistics.played);
rankings.set('roidsFragmented',statistics.roidsFragmented);
rankings.set('roidsHit',statistics.roidsHit);
rankings.set('refuel',statistics.refuel);
rankings.set('shieldsHit',statistics.shieldsHit);
rankings.set('bases',statistics.bases);
rankings.set('shipsHit',statistics.shipsHit);
rankings.set('killTypes',statistics.killTypes);
rankings.set('damaged',statistics.damaged);
rankings.set('shots',statistics.shots);
rankings.set('deflects',statistics.deflects);
rankings.set('jumped',statistics.jumped);
rankings.set('jumpedEnergy',statistics.jumpedEnergy);
rankings.set('jumpCancelled',statistics.jumpCancelled);
rankings.set('accuracy',statistics.accuracy);
rankings.set('energy',statistics.energy);
rankings.set('distance',statistics.distance);
rankings.set('endgames',statistics.endgames);
rankings.save().then(function(object)
{
console.log("uploaded statistics");
});
}
function UpdateRankings(ranking)
{
// range to the min/max
if (ranking < -248) ranking = -248;
else if (ranking >320) ranking = 320;
var Ranks = Parse.Object.extend("Ranks");
var query = new Parse.Query(Ranks);
query.equalTo("rank", ranking);
query.first(
{
success: function(object)
{
if (object)
{
object.increment("hits");
object.save();
}
else
{
var rankings = new Ranks();
rankings.set("rank", ranking);
rankings.set("hits", 0);
rankings.save();
}
},
error: function(error)
{
console.log("Error: " + error.code + " " + error.message);
}
});
}
function CacheGlobalRankings()
{
var Ranks = Parse.Object.extend("Ranks");
var query = new Parse.Query(Ranks);
query.greaterThan("rank", -250);
query.ascending("rank");
query.limit(570);
query.find(
{
success: function(results)
{
gameTotalPlays = 0;
for (var i=0; i<results.length; i++)
{
var index = results[i].get("rank");
var count = results[i].get("hits");
gameTotalPlays+=count;
gameHitsPerRanks[index+248] = gameTotalPlays;
}
console.log("results = "+ results.length);
console.log("numtotalplays = " + gameTotalPlays);
},
error: function(error)
{
console.log("error fetching data : "+error.message);
}
});
}
// flight controls
var dragStart = {x:0,y:0};
var dragging = false;
var dragCurr = {x:0, y:0};
var dragmatrix = new matrix3x3();
function DragEvent(event)
{
dragCurr.x = event.clientX;
dragCurr.y = event.clientY;
}
function DragEventStart(event)
{
dragStart.x = event.clientX;
dragStart.y = event.clientY;
dragCurr.x = event.clientX;
dragCurr.y = event.clientY;
dragmatrix.clone(orientation);
shipPhi = 0;
shipTheta = 0;
dragging = true;
}
function DragEventDone(event)
{
dragging =false;
}
var rotateVelocity = 0;
var pitchVelocity = 0;
var shipVelocity = 0;
var shipThrottle = 0;
var shipVelocityEnergy = 0;
var setShipVelocity = 0;
var triggerWarp = 0;
const normalSpace = 0;
const enterHyperspace = 1;
const inHyperspace = 2;
const cancelHyperspace = 3;
function UpdateShipControls()
{
TestMoveUnderMouse(mouseX, mouseY,dragging);
if (triggerWarp!=normalSpace) EnteringWarp();
return;
var rotationForce = -(dragCurr.x - dragStart.x) / canvas.width;
var pitchForce = (dragCurr.y - dragStart.y) / canvas.height;
if (dragging==true)
{
rotateVelocity+=rotationForce;
pitchVelocity+=pitchForce;
}
rotateVelocity*=0.9;
pitchVelocity*=0.9;
shipTheta+=(pitchVelocity*Math.PI*0.25) / 60;
shipPhi+=(rotateVelocity*Math.PI*0.25) / 60;
// build rotation matrix
var rx = new matrix3x3();
var rz = new matrix3x3();
var ry = new matrix3x3();
rx.rotateX(shipTheta);
ry.rotateY(shipPhi);
orientation.clone( ry.multiply(rx.multiply(dragmatrix)) );
// orientate the view polar for the scanner
var rotate90 = new matrix3x3();
rotate90.rotateX(Math.PI*0.5);
scannerView.clone(rotate90.multiply(orientation));
// move along direction of rotation
var dv = setShipVelocity-shipVelocity;
if (dv<-1) dv = -0.2;
if (dv>+1) dv = +0.2;
shipVelocity += dv;
var speed = -shipVelocity * freqHz;
localPosition.x += orientation.m[6]*speed;
localPosition.y += orientation.m[7]*speed;
localPosition.z += orientation.m[8]*speed;
// UpdateEngineSound(shipVelocity);
}
var starTheta = 0;
var starPhi = 0;
// continual movement..
function TestMoveUnderMouse(mouseX, mouseY)
{
// calculate the fov angle from the centre to the mouse
var dx = centreX - mouseX;
dx = Math.max(Math.min(dx, 512), -512); // clamp rotation spee
var sx = dx * focalPoint*5 / 1000;
var dy = centreY - mouseY;
dy = Math.max(Math.min(dy, 512), -512);
var sy = -dy * focalPoint*5 / 1000;
//convert into angle
angleX = Math.tan(sx/1000);
//convert into angle
angleY = Math.tan(sy/1000);
var rx = new matrix3x3();
var rz = new matrix3x3();
var ry = new matrix3x3();
rx.rotateX(angleY*freqHz);
ry.rotateY(angleX*freqHz);
shipOrientation.clone( ry.multiply(rx.multiply(shipOrientation)) );
if (viewingFront())
{
orientation.clone(shipOrientation);
}
else
{
var rotate180 = new matrix3x3();
rotate180.rotateY(Math.PI);
orientation.clone(rotate180.multiply(shipOrientation));
}
// orientate the view polar for the scanner
var rotate90 = new matrix3x3();
rotate90.rotateX(Math.PI*0.5);
scannerView.clone(rotate90.multiply(orientation));
// move along direction of rotation
var dv = setShipVelocity-shipVelocity;
if (dv<-1) dv = -0.2;
if (dv>+1) dv = +0.2;
shipVelocity += dv;
var speed = -shipVelocity * freqHz;
localPosition.x += shipOrientation.m[6]*speed;
localPosition.y += shipOrientation.m[7]*speed;
localPosition.z += shipOrientation.m[8]*speed;
// update stats
statistics.distance+=Math.abs(speed);
// changing
initVelocity = -setShipVelocity*0.05;
if (viewingAft()) initVelocity*=-1;
panStarfield(angleX, angleY);
if (triggerWarp == normalSpace) PlayEngine(shipVelocity);
}
/*
var angleX=0;
var angleY=0;
function TestMoveUnderMouse(mouseX, mouseY, click)
{
if (click)
{
// calculate the fov angle from the centre to the mouse
var dx = centreX - mouseX;
var sx = dx * focalPoint*5 / 1400;
var dy = centreY - mouseY;
var sy = -dy * focalPoint*5 / 1400;
//convert into angle
angleX = Math.tan(sx/1400);
//convert into angle
angleY = Math.tan(sy/1400);
}
else
{
angleX-=angleX*freqHz;
angleY-=angleY*freqHz;
}
var rx = new matrix3x3();
var rz = new matrix3x3();
var ry = new matrix3x3();
rx.rotateX(angleY*freqHz);
ry.rotateY(angleX*freqHz);
orientation.clone( ry.multiply(rx.multiply(orientation)) );
// orientate the view polar for the scanner
var rotate90 = new matrix3x3();
rotate90.rotateX(Math.PI*0.5);
scannerView.clone(rotate90.multiply(orientation));
// move along direction of rotation
var dv = setShipVelocity-shipVelocity;
if (dv<-1) dv = -0.2;
if (dv>+1) dv = +0.2;
shipVelocity += dv;
var speed = -shipVelocity * freqHz;
localPosition.x += orientation.m[6]*speed;
localPosition.y += orientation.m[7]*speed;
localPosition.z += orientation.m[8]*speed;
// changing
initVelocity = -setShipVelocity*0.125;
}
*/
// throttle + energy
var throttleTable = [0, 0.3, 0.75, 1.5, 3, 6, 12, 25, 37, 43 ];
var throttleEnergy = [0, 1, 1.5, 2, 2.5, 3, 3.5, 7.5, 11.25, 15];
function mouseWheel(event)
{
var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
var slider = GetControl("throttle");
if (slider)
{
slider.value+=delta*freqHz*6;
SetThrottle(slider);
}
}
function SetThrottle(slider)
{
if (slider.value >9) slider.value = 9;
if (slider.value <0) slider.value = 0;
if (triggerWarp==normalSpace)
{
throttle = Math.round(slider.value);
setShipVelocity = throttleTable[throttle];
shipVelocityEnergy = throttleEnergy[throttle];
}
}
function energyManagement()
{
// twin ions
energy -= (shipVelocityEnergy*freqHz);
// shields
if (shieldUp) energy -= 2*freqHz;
// lifesupport
energy -= 0.25 * freqHz;
// tracking computer
if (trackingComputer) energy -= 0.5 * freqHz;
// check damage
CheckShields();
// end game
if (energy < 0 ) EndGame(energyLost);
}
function EnteringWarp()
{
if (triggerWarp==enterHyperspace)
{
UpdateHyperspaceSound(shipVelocity);
setShipVelocity = 99.99;
if (!enterWarp && shipVelocity >90)
{
triggerWarp = inHyperspace;
enterWarp = true;
warpStartDepth = cameraDepth;
warpTime = 0;
// clear data
asteriods = [];
nmes = [];
}
}
if (triggerWarp == inHyperspace)
{
// waiting destination
if (enterWarp == false)
{
PlayConfirm();
PauseHyperspaceSound(2);
PlayExit(2);
CancelHyperSound(2);
triggerWarp = normalSpace;
var slider = GetControl("throttle");
SetThrottle(slider);
GetControl("Hyperspace").state = 0;
// setwarpLocation
var badDriver = 1;
var x = warpLocation.x + Math.random()*badDriving;
var y = warpLocation.y + Math.random()*badDriving;
if (warpLocked== false)
{
x = Math.random()*badDriving*2 - 1;
y = Math.random()*badDriving*2 - 1;
}
// update stats - sectors covered, jumped and energy
statistics.travelled += Math.round(Math.abs(x-shipLocation.x) + Math.abs(y-shipLocation.y));
statistics.jumpedEnergy+=warpEnergy;
statistics.jumped++;
SetShipLocation(x, y);
// Setup NME's
SetupAsteriods(localSpaceCubed);
SetupNMEs(GetPieceAtShipLocation());
warpLocked = false;
energy-=warpEnergy;
}
else
{
UpdateHyperspaceSound(shipVelocity+warpTime*0.5);
}
}
if (triggerWarp == cancelHyperspace)
{
triggerWarp = normalSpace;
var slider = GetControl("throttle");
SetThrottle(slider);
energy-=100;
PlayExit();
CancelHyperSound();
// statistics
statistics.jumpedEnergy-=100;
statistics.jumpCancelled++;
}
}
const maxpoints = 1024;
var pointcloud =[];
function InitStarDome()
{
for (var i=0; i<maxpoints;i++)
{
pointcloud.push(RandomNormal());
}
}
var theta = 0;
var phi = 0;
function RenderStarDome()
{
// theta+=0.01;
// phi+=0.01;
var scale = 1000* canvas.height/500;
context.beginPath();
for (var i=0; i<pointcloud.length; i++)
{
var point = pointcloud[i];
var t = orientation.transform(point.x, point.y, point.z);
var x1=t.x*scale;
var y1=t.y*scale;
var z1=t.z*scale+focalDepth;
if (z1+focalDepth<0) continue;
var depth = focalPoint*5 / (z1 + focalDepth );
var x = x1 * depth + centreX;
var y = y1 * depth + centreY;
var sz = depth * 0.2;
// fill a rect
context.rect(x,y, 4, 4);
}
context.fillStyle = '#333333';
context.fill();
}
// entry point
init();
animate();

Star-Raiders : homage to old school

This is the frame work game for star-raiders remake in pure html 5 canvas. Oh yes we could make this in webGL but whats the fun of that. More interesting to see what we can render in 3D without using a 3D rendering api.

controls are based on touch screen as well for ipads etc, keyboard short cuts will be available however mapping to the originals.

version 0.1 Forked Galactic Scanner and added buttons hud items. This means that AI works for tokens on scanner and we can populate locals with enemys now.

version 0.2 Long Range Scanner. This gives enemies in local space and a means to align and target those enemies

version 0.3 started to break files apart

version 0.4 started to bring in pov 3d with overlays. Damn ship controls suck though. Needs new design for mouse

version 0.5 fixed long range scanner added transitions

version 0.6 changed controls. cleaned up scanners changed fonts to be more star-raiders. Added star-field. added warp. more energy management. starting to get fun!

version 0.7 can now shoot. can now shoot asteriods.. can now fragment the asteriods upto 4 times.. lots o fun

version 0.8 so NMEs will jump into sectors red alerts sounds and play can move around the galaxy consuming their precious energy

version 0.9 NMEs are rendered. targeting computer tracking works. cant shoot them or vice-versa. no ai yet.. just vector'd pathing.

version 0.95 starbase, basestar both in. No AI still. Text plotters with information displayed. Sparser field of stars, but stars pan with motion. lots of bugs fixed

version 0.96 clocks counting down starbase destruction. Started work on AI combat

A Pen by Andi Smithers on CodePen.

License.

body{
background:#000000;
margin: 0px;
padding: 0px;
}
.outer {
display: block;
width: 100%;
height: 100%;
background-color: #202020;
margin: 0px;
padding: 0px;
}
.letter {
display: inline-block;
width: 100%;
height: 100%;
background-color: #000000;
margin-top:auto;
margin-bottom:auto;
padding: 0px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment