import ddf.minim.*;
import ddf.minim.analysis.*;
FFT fft;
/**
* Weekly Game 13 / Ludum Dare #17 Entry: "The Floor Is Lava"
* Z to jump, Arrows to move. Move right to earn points.
* M to mute, P to pause.
*
Go play more games at NMcCoy.net!
* 
This work by Nathan McCoy is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.
*/
boolean[] keys = new boolean[256];
boolean title;
boolean paused;
boolean muted;
int drawing_ms_last;
int physics_ms_last;
int CORE_FPS = 540;
int DRAWING_FPS = 60;
int PHYSICS_FPS = 180;
int DRAW_MS = 1000 / DRAWING_FPS;
int PHYS_MS = 1000 / PHYSICS_FPS;
Minim minim;
ArrayList islands;
PVector player_pos;
PVector player_vel;
ParticleList ripples;
ParticleList lava;
ParticleList smoke;
ParticleList blips;
PVector cam;
float ISLAND_DRAG = 0.007;
float ISLAND_ACCEL = 0.003;
float ISLAND_SINK_RATE = 0.02;
float GRAVITY = 0.02;
float JUMP = 1.2;
float HOT_HOP = 2;
int RIPPLE_TIME = 30;
float MOVE_SPEED = 0.6;
int MINIMUM_ISLANDS = 15;
int ripple_timer;
float life;
float LIFE_DRAIN_RATE = 0.00005;
float LIFE_BURN_RATE = 0.3;
int spinny;
Island on_island;
PFont font;
AudioSample jumpSound;
AudioSample burnSound;
AudioSample shinySound;
AudioSample skipSound;
AudioSample poofSound;
AudioPlayer bgm;
PVector shiny_thing;
float ICE_START_DISTANCE = 1000;
float ICE_DISTANCE_INCREMENT = 100;
int ice_count = 0;
int bonus;
int ice_skip;
int ICE_SKIP_BONUS = 10;
int announce_timer;
int ANNOUNCE_TIME = 360;
boolean dead;
void setup()
{
font = loadFont("PressStartK-32.vlw");
size(720, 480, P2D);
frameRate(600);
minim = new Minim(this);
ellipseMode(RADIUS);
strokeWeight(2);
burnSound = minim.loadSample("burnt.wav", 1024);
jumpSound = minim.loadSample("jump.wav", 1024);
shinySound = minim.loadSample("gotice.wav", 1024);
skipSound = minim.loadSample("iceskip.wav", 1024);
poofSound = minim.loadSample("poof.wav", 1024);
bgm = minim.loadFile("lava.mp3", 2048);
bgm.loop();
fft = new FFT(bgm.bufferSize(), bgm.sampleRate());
fft.linAverages(5);
fft.forward(bgm.mix);
reset();
print((-4)/70);
}
void stop()
{
burnSound.close();
jumpSound.close();
shinySound.close();
skipSound.close();
poofSound.close();
minim.stop();
super.stop();
}
void reset()
{
islands = new ArrayList();
player_pos = new PVector(width/2, height/2);
player_vel = new PVector(0, 0);
islands.add(new Island(new PVector(width/2, height/2), 100));
for(int i = 0; i < MINIMUM_ISLANDS; i++)
islands.add(new Island(new PVector(random(width * 3), random(height)), random(50)+30));
collideIslands();
collideIslands();
collideIslands();
ripples = new ParticleList();
lava = new ParticleList();
smoke = new ParticleList();
blips = new ParticleList();
cam = new PVector(0, 0);
shiny_thing = new PVector(ICE_START_DISTANCE, random(height));
life = 1;
ice_count = 0;
bonus = 0;
ice_skip = 0;
dead = false;
paused = true;
title = true;
}
class TextBlip extends Particle
{
String myText;
color myColor;
TextBlip(PVector p, int life, String label)
{
super(p, PVector.add(randomVector(0.5), new PVector(0, -2)), new PVector(0, 4.0/life), life);
myText = label;
myColor = color(255);
}
TextBlip(PVector p, int life, String label, color c)
{
super(new PVector(p.x, p.y-p.z), PVector.add(randomVector(0.5), new PVector(0, -2)), new PVector(0, 4.0/life), life);
myText = label;
myColor = c;
}
void draw()
{
textAlign(CENTER, BOTTOM);
textFont(font, 8);
fill(myColor);
text(myText, pos.x, pos.y);
}
}
class Ripple extends Particle
{
float radius;
Ripple(PVector p, float r)
{
super(p, new PVector(0, 0), 180);
radius = r;
}
void draw()
{
noFill();
stroke(255, 192, 64, 255*life/(float)maxlife);
ellipse(pos.x, pos.y, radius, radius);
}
void tick()
{
radius += 0.03;
super.tick();
}
}
class LavaDot extends Particle
{
LavaDot()
{
super(new PVector(cam.x + random(width), random(height)), new PVector(0, 0), 500);
}
void draw()
{
noStroke();
float bright = fft.getAvg(0) * life / maxlife;
fill(160 + bright * 5, 32+ bright*5, 0);
float r = (maxlife - life) * 0.1;
ellipse(pos.x, pos.y, r, r);
}
}
class SmokePoof extends Particle
{
SmokePoof(PVector p, int bigness)
{
super(p, randomVector(random(1.0/6)), new PVector(0, -0.05/36), bigness*6);
}
void draw()
{
fill(230, 230, 220);
noStroke();
ellipse(pos.x, pos.y, life/6, life/6);
}
}
class Island
{
PVector pos;
PVector vel;
float radius;
boolean sinking;
Island(PVector p, float r)
{
pos = new PVector(p.x, p.y);
vel = new PVector(0, 0);
radius = r;
}
void tick()
{
if(inRadius(player_pos, pos, radius) && player_pos.z <= 0 && !dead)
{
sinking = true;
PVector to_player = PVector.sub(player_pos, pos);
to_player.div(radius);
to_player.mult(ISLAND_ACCEL);
vel.add(to_player);
on_island = this;
}
//else sinking = false;
if(sinking) radius -= ISLAND_SINK_RATE;
vel.mult(1 - ISLAND_DRAG);
//if(sinking) player_pos.add(vel);
pos.add(vel);
}
void draw()
{
if(sinking) stroke(255, 192, 64);
else stroke(130, 120, 110);
fill(160, 150, 140);
ellipseMode(RADIUS);
ellipse(pos.x, pos.y, radius, radius);
}
}
color KOBOLD_FILL = color(175, 215, 180);
color KOBOLD_STROKE = color(196, 176, 97);
void collideIslands()
{
for(int ia = 0; ia < islands.size(); ia++) for(int ib = ia+1; ib < islands.size(); ib++)
{
Island a = (Island) islands.get(ia);
Island b = (Island) islands.get(ib);
if(inRadius(a.pos, b.pos, a.radius+b.radius))
{
PVector a_to_b = PVector.sub(b.pos, a.pos);
float overlap = a.radius + b.radius - a_to_b.mag();
float ab_weight_ratio = a.radius*a.radius / (a.radius*a.radius + b.radius*b.radius);
a_to_b.normalize();
a_to_b.mult(overlap);
a.pos.sub(PVector.mult(a_to_b, 1 - ab_weight_ratio));
b.pos.add(PVector.mult(a_to_b, ab_weight_ratio));
PVector new_vel = PVector.add(PVector.mult(a.vel, ab_weight_ratio), PVector.mult(b.vel, 1-ab_weight_ratio));
a.vel.x = new_vel.x;
b.vel.x = new_vel.x;
a.vel.y = new_vel.y;
b.vel.y = new_vel.y;
}
}
}
void physicsStep()
{
ripple_timer -= 1;
announce_timer --;
life -= LIFE_DRAIN_RATE;
if(ripple_timer <= 0)
{
ripple_timer = RIPPLE_TIME;
lava.add(new LavaDot());
}
if(player_pos.x < cam.x + width/4 && blips.isEmpty() && !dead)
{
//if(random(2) < 1)
blips.add(new TextBlip(player_pos, 20, "--> !", color(198, 255, 128)));
//else blips.add(new TextBlip(player_pos, 20, "-->", color(198, 255, 128)));
}
if(life < 0.2 && blips.size()<3 && !dead)
{
blips.add(new TextBlip(player_pos, 20, "!", color(192, 220, 255)));
}
on_island = null;
Iterator i = islands.iterator();
while(i.hasNext())
{
Island is = (Island) i.next();
is.tick();
if(ripple_timer == RIPPLE_TIME && (is.sinking || is.vel.mag() > 0.01)) ripples.add(new Ripple(is.pos, is.radius));
if(is.radius <= 0 || is.pos.x < cam.x - width/2) i.remove();
}
collideIslands();
ripples.tick();
lava.tick();
smoke.tick();
player_vel.z -= GRAVITY;
PVector steering = new PVector(0, 0);
if(keys[LEFT]) steering.x -= 0.5;
if(keys[RIGHT]) steering.x += 0.5;
if(keys[UP]) steering.y -= 0.5;
if(keys[DOWN]) steering.y += 0.5;
steering.normalize();
steering.mult(MOVE_SPEED);
if(player_pos.z <= 0 && on_island != null)
{
player_vel.x = on_island.vel.x;
player_vel.y = on_island.vel.y;
player_vel.add(steering);
}
else {
PVector new_vel = new PVector(player_vel.x, player_vel.y);
float magn = max(new_vel.mag(), MOVE_SPEED);
new_vel.add(steering);
if(new_vel.mag() > magn) new_vel.mult(magn / new_vel.mag());
player_vel.x = new_vel.x;
player_vel.y = new_vel.y;
}
if(player_pos.z < 0) {
if(on_island != null)
{ player_pos.z = 0; player_vel.z = 0;
}
else
{
//BURN
if(!muted)burnSound.trigger();
ripples.add(new Ripple(player_pos, 1));
life-= LIFE_BURN_RATE;
player_pos.z = 0;
player_vel.z = HOT_HOP;
blips.add(new TextBlip(player_pos, 60, "o_O;", color(255, 220, 128)));
for(int si = 0; si < 5; si++)
{
smoke.add(new SmokePoof(player_pos, 10));
}
}
}
if(keys['Z'] && player_pos.z <= 0 && on_island != null)
{
player_vel.z = JUMP;
if(!muted)jumpSound.trigger();
}
if(!dead) player_pos.add(player_vel);
if(player_pos.y < 0) player_pos.y = 0;
if(player_pos.y > height) player_pos.y = height;
if(player_pos.x < cam.x) player_pos.x = cam.x;
if(inRadius(player_pos, shiny_thing, 22) && player_pos.z < 10)
{
if(!muted) shinySound.trigger();
life = 1;
ice_count++;
ice_skip = 0;
shiny_thing = new PVector(shiny_thing.x+ICE_START_DISTANCE+ICE_DISTANCE_INCREMENT*ice_count, random(height));
blips.add(new TextBlip(player_pos, 60, "<3", color(255, 160, 255)));
}
if(shiny_thing.x + 12 < cam.x)
{
ice_count++;
shiny_thing = new PVector(shiny_thing.x+ICE_START_DISTANCE+ICE_DISTANCE_INCREMENT*ice_count, random(height));
ice_skip++;
bonus += ice_skip * ICE_SKIP_BONUS;
announce_timer = ANNOUNCE_TIME;
if(!muted) skipSound.trigger();
blips.add(new TextBlip(player_pos, 60, ":D", color(255, 255, 128)));
}
if(life <0 && player_vel.z <= 0 && !dead)
{
dead = true;
if(!muted) poofSound.trigger();
for(int si = 0; si < 5; si++)
{
smoke.add(new SmokePoof(new PVector(player_pos.x, player_pos.y - player_pos.z), 15));
}
}
while(islands.size() < MINIMUM_ISLANDS)
{
islands.add(new Island(new PVector(cam.x + width*1.5 + random(width/2), cam.y + random(height)), random(50)+30));
}
}
void drawingStep()
{
fft.forward(bgm.mix);
background(160, 32, 0);
float avg = fft.getAvg(0);
pushMatrix();
if(player_pos.x > cam.x + width/2)
cam.x = player_pos.x - width/2;
translate(-cam.x, -cam.y);
lava.draw();
ripples.draw();
Iterator i = islands.iterator();
while(i.hasNext())
{
Island is = (Island) i.next();
is.draw();
}
smoke.draw();
noStroke();
fill(0, 64);
if(!dead) ellipse(player_pos.x, player_pos.y + 5, 10, 10);
spinny++;
regPoly(shiny_thing.x, shiny_thing.y + 5, 12, 4, spinny*0.15);
if(!dead)
{
fill(KOBOLD_FILL);
stroke(KOBOLD_STROKE);
ellipse(player_pos.x, player_pos.y - player_pos.z, 10, 10);
}
color spinnycolor = color(255 - (spinny * 10)%255, 192 + 255 - (spinny * 10)%255, 255);
fill(spinnycolor);
noStroke();
regPoly(shiny_thing.x, shiny_thing.y, 12, 4, spinny*0.15);
if(!paused) blips.tick();
blips.draw();
popMatrix();
fill(0, 64);
noStroke();
rectMode(CORNER);
if(paused) rect(0, 0, width, height);
fill(255);
textFont(font, 16);
textAlign(LEFT, TOP);
text("Awesome: "+(int)(cam.x / 50 + bonus), 8, 8);
if(announce_timer > 0)
text("+"+(ice_skip * ICE_SKIP_BONUS)+ ": Ice Skip Bonus x"+ice_skip, 8, 24);
rectMode(CENTER);
noStroke();
fill(0);
rect(width/2, height - 16, (width - 32), 3);
fill(spinnycolor);
rect(width/2, height - 16, max((width - 32) * life, 0), 3);
textAlign(CENTER, BOTTOM);
textFont(font, 8);
text("Coolth", width/2, height - 20);
fill(255);
textFont(font, 16);
if(paused)
{
if(title)
{
textAlign(CENTER, BOTTOM);
text("The Floor Is Lava", width/2, height/2);
textAlign(CENTER, TOP);
textFont(font, 8);
text("prove kobold awesome by going --> that way\n\np = play also pause\nz = jump\narrows = move\nlava = ouch\nkobold = awesome? <3", width/2, height/2);
}
else
{
textAlign(CENTER, CENTER);
text("Pauseified", width/2, height/2);
}
}
if(dead)
{
textAlign(CENTER, BOTTOM);
text("Game Over", width/2, height/2);
textAlign(CENTER, TOP);
textFont(font, 8);
text("kobold dead due to lack of coolth :(\n\np to again? <3", width/2, height/2);
}
}
void draw()
{
while(physics_ms_last + PHYS_MS < millis())
{
if(!paused)
{
physicsStep();
}
physics_ms_last += PHYS_MS;
}
if(drawing_ms_last + DRAW_MS < millis())
{
drawingStep();
drawing_ms_last = millis();
}
}
void keyPressed()
{
if(!keys[keyCode])
{
keys[keyCode] = true;
down(keyCode);
}
}
void keyReleased()
{
if(keys[keyCode])
{
keys[keyCode] = false;
up(keyCode);
}
}
void down(int theKey)
{
println(theKey + " down");
if(theKey == 'M')
{muted = !muted;
if(muted) bgm.mute();
else bgm.unmute();
}
if(theKey == 'P')
{
if(dead) reset();
else
{
paused = !paused;
title = false;
}
}
}
void up(int theKey)
{
println(theKey + " up");
}