/**
* Game 04: "Wavespark"
* Hold any key (besides P or Q) to increase gravity.
* Use slopes to build speed.
* 
This work by Nathan McCoy is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.
*/
//
import ddf.minim.*;
import ddf.minim.effects.*;
import ddf.minim.signals.*;
PImage bg;
PImage bg2;
PImage buffer;
ArrayList levelMap;
AudioSample ouchSound;
AudioSample sweetSound;
AudioSample bonusSound;
AudioSample checkpointSound;
AudioSample timerSound;
PFont font;
AudioOutput out;
AudioPlayer bgm;
AudioPlayer bgm2;
Minim minim;
BandPass bpf;
WhiteNoise wn;
final int MODE_FREE_PLAY = 0;
final int MODE_BONUS = 1;
final int MODE_DISTANCE = 2;
final int MODE_FIVEMINUTE = 3;
int gameMode = MODE_BONUS;
int clock;
int CLOCK_CYCLE = 60;
float GAME_SCALE = 0.6;
float GRAVITY = 0.1 * sqrt(GAME_SCALE);
float BOOST_GRAVITY = 0.7 * sqrt(GAME_SCALE);
float BOOST_UP_GRAVITY = 0.2 * sqrt(GAME_SCALE);
float CAM_MIN_Y = -200;
float CAM_MAX_Y = 100;
float MIN_X_SPEED = 4.0;
PImage snapshot;
float FIRST_CHECKPOINT = 20000;
float CHECKPOINT_INCREMENT = 2500;
float nextCheckpoint;
float lastCheckpoint;
int checkpoints;
PVector camPos;
PVector playerPos;
PVector playerVel;
boolean grounded;
boolean locked;
boolean slogging;
int timerAir;
int timerSlog;
int maxAir;
int maxSlog;
int ouchCount;
int sweetCount;
int sweetChain;
int sweetMax;
int bonusCount;
String exclaimString;
color exclaimColor;
int exclaimTimer;
int EXCLAIM_TIME = 60;
int bonusTimer;
int bonusStack;
int BONUS_TIME = 120;
int BONUS_STAGGER = 10;
ArrayList bonusQueue;
String bonusString;
int AIR_BONUS_TIME = 120;
int AIR_BONUS_MULT = 2;
float topSpeed;
int bonuses;
float distance;
boolean ready;
int CHECKPOINT_TIME = 60 * 46;
int gameTimer;
int lastSegment;
float level_xpos = 100 * GAME_SCALE;
float level_ypos = 0;
float level_lastSlope = 0;
int MOUNTAINS = 0;
int WAVES = 1;
int HILLS = 2;
int RAMPS = 3;
int terrain = WAVES;
ArrayList grindParticles;
ArrayList checkpointParticles;
boolean paused;
int gameState;
int GAME_OVER = 0;
int GAMEPLAY = 1;
int TITLE_SCREEN = 2;
int gameOverTimer;
int keyTimer;
class Particle
{
PVector pos;
PVector vel;
int life;
int maxlife;
Particle(PVector p, PVector v, int lifetime)
{
pos = new PVector(p.x, p.y);
vel = new PVector(v.x, v.y);
life = maxlife = lifetime;
}
}
PVector randomVector(float magnitude)
{
float dir = random(TWO_PI);
return new PVector(cos(dir)*magnitude, sin(dir)*magnitude);
}
void addSegment()
{
if(terrain == WAVES)
{
level_xpos += 200*GAME_SCALE;
if(level_ypos < 80) level_lastSlope = 30 + random(80);
else if(level_lastSlope < -10 && random(5)<1 && level_ypos < 300) level_lastSlope = random(50)+30;
else if(level_ypos > 350) level_lastSlope = -30;
else if(level_lastSlope < -70 /*was -100*/) level_lastSlope = 40;
else level_lastSlope -= random(50)+10;
level_ypos += level_lastSlope;
levelMap.add(new PVector(level_xpos, level_ypos));
}
if(terrain == MOUNTAINS)
{
level_xpos += 100*GAME_SCALE+(int)random(300*GAME_SCALE);
levelMap.add(new PVector(level_xpos, random(200*GAME_SCALE)+random(200*GAME_SCALE)+80*GAME_SCALE));
}
if(terrain == HILLS)
{
level_xpos += 300*GAME_SCALE;
level_lastSlope = random(200)-100 - (level_ypos-250);
level_ypos += level_lastSlope;
levelMap.add(new PVector(level_xpos, level_ypos));
}
if(terrain == RAMPS)
{
level_xpos += 100*GAME_SCALE;
level_ypos += 100*GAME_SCALE;
levelMap.add(new PVector(level_xpos, level_ypos));
level_xpos += -0*GAME_SCALE;
level_ypos += -100*GAME_SCALE;
levelMap.add(new PVector(level_xpos, level_ypos));
}
}
void reset()
{
ready = false;
level_xpos = 100;
level_ypos = 0;
level_lastSlope = 0;
lastSegment = 0;
clock = 0;
playerPos = new PVector(0, -200);
playerVel = new PVector(0,0);
levelMap = new ArrayList();
grindParticles = new ArrayList();
checkpointParticles = new ArrayList();
grounded = false;
locked = false;
levelMap.add(new PVector(-200, -200));
for(int i = 0; i < 20; i++)
{
addSegment();
}
camPos = new PVector(50, 0);
grounded = false;
locked = false;
timerAir = 0;
timerSlog = 0;
maxAir = 0;
maxSlog = 0;
topSpeed = 0;
ouchCount = 0;
sweetCount = 0;
sweetChain = 0;
sweetMax = 0;
distance = 0;
bonuses = 0;
bonusCount = 0;
bonusString = "";
bonusTimer = 0;
checkpoints = 0;
nextCheckpoint = FIRST_CHECKPOINT;
if(gameMode != MODE_FIVEMINUTE)
gameTimer = CHECKPOINT_TIME;
else gameTimer = 121*60;
bonusQueue = new ArrayList();
gameState = GAMEPLAY;
pause(false);
bgm.loop();
}
void setup()
{
size(640, 480, P2D);
frameRate(60);
font = loadFont("TartineScriptBlack-48.vlw");
bg = loadImage("abstractsunset.png");
bg.loadPixels();
minim = new Minim(this);
bgm = minim.loadFile("wavespark_bgmusic.mp3", 2048);
bgm2 = minim.loadFile("wavespark_scores.mp3", 2048);
ouchSound = minim.loadSample("ouch.wav", 512);
sweetSound = minim.loadSample("sweet.wav", 512);
bonusSound = minim.loadSample("points.wav", 512);
checkpointSound = minim.loadSample("checkpoint.wav", 512);
timerSound = minim.loadSample("timer2.wav", 512);
out = minim.getLineOut();
wn = new WhiteNoise(1.0);
out.mute();
out.addSignal(wn);
bpf = new BandPass(440, 200, out.sampleRate());
out.addEffect(bpf);
titleScreen();
}
void ouch()
{
ouchSound.trigger();
ouchCount++;
exclaimString = "Ouch!";
exclaimColor = color(255, 0, 0);
exclaimTimer = EXCLAIM_TIME;
}
void sweet()
{
sweetSound.trigger();
sweetCount++;
sweetChain++;
sweetMax = max(sweetMax, sweetChain);
exclaimString = "Sweet!";
if(sweetChain > 1) exclaimString+="\nx"+sweetChain;
exclaimColor = color(0, 192, 255);
exclaimTimer = EXCLAIM_TIME;
if(sweetChain == 1)
awardBonus("Sweet", 200*sweetChain);
else
awardBonus("Sweet Chain", 200*sweetChain);
if(sweetChain == 5)
awardBonus("Dessert", 5000);
}
void drawParticles()
{
Iterator i = grindParticles.iterator();
while(i.hasNext())
{
Particle p = (Particle) i.next();
p.pos.add(p.vel);
fill(color(255,255, random(255)));
noStroke();
ellipse(p.pos.x, p.pos.y, 4, 4);
p.life--;
if(p.life <= 0) i.remove();
}
i = checkpointParticles.iterator();
while(i.hasNext())
{
Particle p = (Particle) i.next();
p.pos.add(p.vel);
colorMode(HSB);
fill((clock*30)%255, 128, 255);
colorMode(RGB);
//fill(color(255,255, random(255)));
noStroke();
float rad = map(p.life, p.maxlife, 0, 7, 0);
ellipse(p.pos.x, p.pos.y, rad, rad);
p.life--;
if(p.life <= 0) i.remove();
}
}
void playerPhysics()
{
if(keyPressed)
{
playerVel.y += BOOST_GRAVITY;
if(playerVel.y <0)playerVel.y += BOOST_UP_GRAVITY;
}
else playerVel.y += GRAVITY;
if(grounded && (playerVel.mag() <= MIN_X_SPEED || playerVel.x <= 0))
{
playerVel.normalize();
playerVel.mult(MIN_X_SPEED);
if(playerVel.x <= 0) playerVel.mult(-1);
slogging = true;
}
else slogging = false;
//find collision segment
playerPos.add(playerVel);
int levelSegmentIndex = lastSegment;
while(((PVector)levelMap.get(levelSegmentIndex)).x < playerPos.x)
{
levelSegmentIndex++;
}
levelSegmentIndex--;
PVector origin = (PVector)levelMap.get(levelSegmentIndex);
PVector segment = PVector.sub((PVector)levelMap.get(levelSegmentIndex+1), origin);
PVector toPlayer = PVector.sub(playerPos, origin);
PVector c = toPlayer.cross(segment);
PVector d = playerVel.cross(segment);
if((c.z < 0 && d.z < 0) || locked)
{
float proj = toPlayer.dot(segment) / segment.dot(segment);
playerPos = PVector.add(origin, PVector.mult(segment, proj));
segment.normalize();
if(grounded && levelSegmentIndex != lastSegment) playerVel = PVector.mult(segment, playerVel.mag());
else
{
if(PVector.angleBetween(segment, playerVel) > PI * 0.4 && timerAir > 3)
{
ouch();
sweetChain = 0;
if(playerVel.mag() > 20) awardBonus("Pancake", 500);
}
else
{
if(!grounded)
if(PVector.angleBetween(segment, playerVel) < PI * 0.2 && playerVel.y > 12) sweet();
else sweetChain = 0;
}
playerVel = PVector.mult(segment, segment.dot(playerVel));
}
playerVel.mult(0.999);
grounded = true;
}
else grounded = false;
if(grounded && keyPressed) locked = true;
else locked = false;
lastSegment = levelSegmentIndex;
topSpeed = max(topSpeed, playerVel.x);
}
void drawPlayer()
{
if(keyPressed)
fill(0, 255, clock%5*50);
else
fill(0, clock%5*50, 255);
noStroke();
ellipse(playerPos.x, playerPos.y, 15, 15);
if(!slogging)
fill(clock%5*25+128, 255, 255);
else fill(clock%5*25+128-70, 255-70, 255-70);
ellipse(playerPos.x, playerPos.y, 13, 13);
if(playerPos.y < camPos.y + 80)
{
PVector indicator = new PVector(camPos.x + width/2 - 40, camPos.y + 70);
noFill();
//fill(clock%5*25+128, 255, 255, map(playerPos.y, camPos.y + 80, camPos.y, 0, 255));
//ellipse(indicator.x, indicator.y, 9, 9);
stroke(clock%5*25+128, 255, 255, map(playerPos.y, camPos.y + 80, camPos.y, 0, 255));
strokeWeight(3);
PVector to = PVector.sub(playerPos, indicator);
to.normalize();
to.mult(25);
to.add(indicator);
line(indicator.x, indicator.y, to.x, to.y);
int alt_radius = ((int)map(playerPos.y, camPos.y, camPos.y-height, 0, 50))%50;
stroke(clock%5*25+128, 255, 255, map(alt_radius, 0, 50, 255, 0));
if(playerPos.y < camPos.y) ellipse(indicator.x, indicator.y, alt_radius, alt_radius);
}
}
void drawTerrain()
{
int levelSegmentIndex = 0;
stroke(color(255, 230, 200));
strokeWeight(3);
fill(color(50, 20, 60, 192));
//if(terrain == WAVES)
// fill(color(20, 50, 50, 192));
while(((PVector)levelMap.get(levelSegmentIndex)).x < camPos.x)
{
levelSegmentIndex++;
}
beginShape();
levelSegmentIndex--;
PVector v = (PVector)levelMap.get(levelSegmentIndex);
vertex(v.x, v.y);
while(((PVector)levelMap.get(levelSegmentIndex)).x < camPos.x+width && levelMap.size() > levelSegmentIndex+1)
{
levelSegmentIndex++;
v = (PVector)levelMap.get(levelSegmentIndex);
vertex(v.x, v.y);
}
vertex(camPos.x+width+50, 600);
vertex(camPos.x-50, 600);
endShape(CLOSE);
}
void doCheckpoints()
{
if(playerPos.x > nextCheckpoint)
{
checkpointSound.trigger();
for(int i=0; i 60) awardBonus("Punctuality", (int)(gameTimer * (FIRST_CHECKPOINT + checkpoints * CHECKPOINT_INCREMENT)/CHECKPOINT_TIME /10));
else awardBonus("Panic", 2000);
checkpoints++;
lastCheckpoint = nextCheckpoint;
nextCheckpoint += FIRST_CHECKPOINT + checkpoints * CHECKPOINT_INCREMENT;
gameTimer = CHECKPOINT_TIME;
if(playerPos.y < CAM_MIN_Y) awardBonus("Flagpole", 2000);
// exclaimString = "Time Extend!";
// exclaimColor = color(0, 140, 140); //color(0, 230, 120);
// exclaimTimer = EXCLAIM_TIME;
}
colorMode(HSB);
strokeWeight(5);
stroke(color((clock*30)%255, 128, 255));
if(camPos.x+width > nextCheckpoint)
line(nextCheckpoint, camPos.y, nextCheckpoint, camPos.y+height);
float check_distance_limit = 3000;
if(camPos.x+width > nextCheckpoint - check_distance_limit && camPos.x+width < nextCheckpoint)
{
float distto = (nextCheckpoint - (camPos.x+width))/check_distance_limit;
stroke((clock*30)%255, 128, 255, map(distto, 1, 0, 0, 128));
noFill();
ellipse(camPos.x+width, camPos.y+height/2, distto*300, distto*300);
noStroke();
fill((clock*30)%255, 128, 255, map(distto, 1, 0, 0, 128));
triangle(camPos.x+width - distto*170, camPos.y+height/2, camPos.x+width - 30 - distto*170, camPos.y+height/2 - 30, camPos.x+width - 30 - distto*170, camPos.y+height/2 + 30);
}
colorMode(RGB);
}
void pruneSegments()
{
while(lastSegment > 10)
{
levelMap.remove(0);
lastSegment--;
while(levelMap.size() < 40)
addSegment();
}
}
void doTimers()
{
if(!grounded) timerAir++;
else
{
maxAir = max(timerAir, maxAir);
if(timerAir > AIR_BONUS_TIME) awardBonus("Air", (timerAir-AIR_BONUS_TIME)*AIR_BONUS_MULT);
timerAir = 0;
}
if(slogging) timerSlog++;
else
{
maxSlog = max(timerSlog, maxSlog);
timerSlog = 0;
}
textFont(font, 32);
textAlign(LEFT, TOP);
// if(timerSlog > 60)
if(timerAir > 2)
{
fill(color(0, 192, 255, timerAir/60.0*255 - 128));
stroke(0);
text("Air Time: "+timerAir/60+"."+(timerAir/6)%10, 5, 50);
}
else
{
fill(color(255, 32, 32, timerSlog/60.0*255 - 128));
stroke(0);
text("Slog Time: "+timerSlog/60+"."+(timerSlog/6)%10, 5, 50);
}
gameTimer--;
exclaimTimer--;
textFont(font, 48);
textAlign(CENTER, TOP);
if(gameTimer/60 > CHECKPOINT_TIME/60 - 2 && (gameTimer/3)%3==0 && gameMode != MODE_FIVEMINUTE) fill(color(255, 255, 0));
else if(gameTimer/60 < 6 && (gameTimer/3)%3==0) fill(color(255, 0, 0));
else fill(255);
text(""+(gameTimer/60), width/2, 8);
if(gameTimer/60 <= 6 && (gameTimer%60)==0) timerSound.trigger();
if(bonusTimer <= 0 && bonusQueue.isEmpty())
{
bonusString = "";
bonusStack = 0;
}
//else
{
if(!bonusQueue.isEmpty() && bonusTimer < BONUS_TIME - BONUS_STAGGER)
{
bonusTimer = BONUS_TIME;
bonusSound.trigger();
bonusString += bonusQueue.remove(0)+"\n";
}
textAlign(RIGHT, TOP);
fill(color(255, 255, 255.0/BONUS_TIME*bonusTimer));
textFont(font, 24);
text(bonusString, width - 5, 5);
}
bonusTimer--;
if(gameTimer <= 0) gameOver();
}
void awardBonus(String reason, int amount)
{
if(gameMode == MODE_BONUS || gameMode == MODE_FIVEMINUTE)
{
bonusCount ++;
bonuses += amount;
bonusQueue.add(reason + " Bonus +"+amount);
bonusStack++;
if(bonusStack == 5) awardBonus("Bonus", +5000);
// print(bonusStack);
}
}
void drawProgress()
{
stroke(255, 128);
strokeWeight(2);
noFill();
ellipse(width/2-200, height-20, 10, 10);
fill(255, 128);
ellipse(width/2+200, height-20, 10, 10);
noStroke();
fill(color(255, 0, 0, 128));
float timeGhost = map(gameTimer, CHECKPOINT_TIME, 0, -200, 200);
ellipse(width/2+timeGhost, height-20, 7, 7);
fill(color(128, 255, 255, 128));
float progress = map(playerPos.x, lastCheckpoint, nextCheckpoint, -200, 200);
ellipse(width/2+progress, height-20, 7, 7);
}
void drawExclamation()
{
if(exclaimTimer >= EXCLAIM_TIME - 6 && exclaimTimer%2==0) fill(255);
else fill(exclaimColor, 255.0/EXCLAIM_TIME*exclaimTimer);
if(exclaimTimer > 0)
{
textFont(font, 48);
textAlign(CENTER, CENTER);
text(exclaimString, width/2, height/2);
}
}
void pause(boolean toPause)
{
paused = toPause;
if(paused)
{
capScreen();
// snapshot.filter(GRAY);
bgm.pause();
out.mute();
}
else
{
bgm.play();
out.unmute();
}
}
void capScreen()
{
snapshot = createImage(width, height, RGB);
loadPixels();
snapshot.loadPixels();
for(int i=0; i 30)
{ gameOverTimer = 2000; keyTimer = 0;}
if(gameState == GAME_OVER && gameOverTimer > 1200 && keyTimer > 30) titleScreen();
if(gameState == TITLE_SCREEN && keyTimer > 30)
{
if(key == 'f') {gameMode = MODE_FREE_PLAY; reset();}
if(key == 't') {gameMode = MODE_FIVEMINUTE; reset();}
if(key == 'b') {gameMode = MODE_BONUS; reset();}
if(key == 'd') {gameMode = MODE_DISTANCE; reset();}
}
}
void stop()
{
bgm.close();
bgm2.close();
out.close();
ouchSound.close();
sweetSound.close();
bonusSound.close();
checkpointSound.close();
timerSound.close();
minim.stop();
super.stop();
}