import ddf.minim.*;
/**
* Game 37: "Dragondot's Extreme Beach Volleyball"
* Player 1 controls: C=Move Left, V = Move Right, Z = Jump/Hit
* Player 2 controls: LEFT ARROW=Move Left, RIGHT ARROW = Move Right, CTRL = Jump/Hit
* P = pause, M = mute, Q = quit (when paused)
*
No slimes were harmed in the making of this game.
*
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.
*/
color sand_shadow = color(204, 203, 198);
boolean debug_aiming = false;
String MAIN_CHARACTERS = "Dragondot;Kobold;Hobgoblin";
String ALL_CHARACTERS = MAIN_CHARACTERS + ";Giant;Phoenix;Wavespark";
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;
float wave_phase = 0;
float WAVE_SPEED = 0.01;
float debug_left_bound;
float debug_right_bound;
float GROUND_LEVEL = 440;
float NET_TOP = 330;
float BALL_GRAVITY = 0.02;
float NET_CLEARANCE = 16;
float MARGIN = 8;
int MAX_POINTS = 5;
float expected_landing;
Critter p1;
Critter p2;
Ball ball;
boolean p2ai = true;
boolean game_on = true;
int point_timer;
int POINT_TIME = PHYSICS_FPS *5 / 2;
boolean serve;
Critter server;
int anim_frame;
color FADE_COLOR = color(0);//color(38, 115, 169);
ParticleList fg_particles;
ParticleList bg_particles;
FlipMenu mode_menu;
FlipMenu p1_char_select;
FlipMenu p2_char_select;
AudioSample point_sound;
AudioSample dash_sound;
AudioSample claw_sound;
AudioSample hit_sound;
AudioSample club_sound;
AudioSample blip_sound;
AudioSample select_sound;
AudioSample wave_sound;
AudioSample ace_sound;
AudioSample secret_sound;
PFont font;
int game_state;
int TITLE = 0;
int PLAYING = 1;
int menu_state;
int MODE_MENU = 0;
int ONEPLAYER_SELECT = 1;
int TWOPLAYER_SELECT = 2;
boolean p1_ready;
boolean p2_ready;
boolean secret;
int gamestart_timer;
int GAMESTART_TIME = PHYSICS_FPS * 5/2;
int game_frames;
int IRIS_TIME = PHYSICS_FPS / 2;
int p1score;
int p2score;
boolean p1s_point;
int tourneyround;
int NUM_CRITTERS = 6;
void audioInit()
{
minim = new Minim(this);
point_sound = minim.loadSample("whistle.wav", 1024);
dash_sound = minim.loadSample("dash.wav", 1024);
claw_sound = minim.loadSample("claw.wav", 1024);
claw_sound.setGain(-8);
club_sound = minim.loadSample("club.wav", 1024);
club_sound.setGain(-8);
hit_sound = minim.loadSample("hit.wav", 1024);
hit_sound.setGain(-8);
blip_sound = minim.loadSample("menu_blip.wav", 1024);
select_sound = minim.loadSample("menu_select.wav", 1024);
wave_sound = minim.loadSample("wave.wav", 1024);
ace_sound = minim.loadSample("ace.wav", 1024);
secret_sound = minim.loadSample("areaclear.wav", 1024);
}
void audioClose()
{
point_sound.close();
dash_sound.close();
claw_sound.close();
hit_sound.close();
blip_sound.close();
select_sound.close();
wave_sound.close();
ace_sound.close();
secret_sound.close();
minim.stop();
}
void newGame(Critter player1, Critter player2, int level)
{
game_frames = 0;
p1score = 0;
p2score = 0;
p1 = player1;
p2 = player2;
if(level == -1) p2ai = false;
else p2ai = true;
tourneyround = level;
game_state = PLAYING;
ball = new Ball();
server = p1;
serve = true;
if(p2ai || random(2)<1)
bgmLoop(p2.themesong);
else bgmLoop(p1.themesong);
gamestart_timer = GAMESTART_TIME;
}
void doTournamentRound(int level)
{
int opp_index = level;
if(opp_index >= p1_char_select.index) opp_index++;
Critter opponent = newCritterByIndex(opp_index, RIGHT);
Critter player = newCritterByIndex(p1_char_select.index, LEFT);
newGame(player, opponent, level);
}
void titleSetup()
{
mode_menu = new FlipMenu("1 Player Tournament;Player 1 vs. Player 2");
p1_char_select = new FlipMenu(MAIN_CHARACTERS);
//p1_char_select.base_size = 8;
p2_char_select = new FlipMenu(MAIN_CHARACTERS);
//p2_char_select.base_size = 8;
menu_state = MODE_MENU;
game_state = TITLE;
bgmLoop("DEBV.mp3");
p1_ready = false;
p2_ready = false;
secret = false;
gamestart_timer = GAMESTART_TIME;
paused = false;
}
void setup()
{
size(720, 480);
frameRate(600);
audioInit();
info_font = loadFont("PressStartK-16.vlw");
font = loadFont("PressStartK-16.vlw");
fg_particles = new ParticleList();
bg_particles = new ParticleList();
titleSetup();
//newGame(new Dragondot(LEFT), new Hobgoblin(RIGHT), 0);
}
void drawBeach()
{
noStroke();
strokeWeight(1);
rectMode(CORNERS);
fill(65, 127, 212);
rect(0, 0, width, 120);
fill(91, 155, 217);
rect(0, 120, width, 200);
fill(122, 179, 222);
rect(0, 200, width, 240);
fill(blendColors(color(38, 115, 169), color(255), sin(wave_phase)*0.05));
rect(0, 240, width, 253);
fill(blendColors(color(32, 131, 172), color(255), 0.0 + sin(wave_phase - PI/2)*0.05));
rect(0, 253, width, 278);
fill(blendColors(color(83, 173, 200), color(255), 0.0 + sin(wave_phase - PI)/10));
rect(0, 278, width, 309);
fill(blendColors(color(216, 201, 194), color(255), 0.30+ sin(wave_phase - PI*3/2)*0.25));
rect(0, 309, width, 311);
fill(216, 201, 194);
rect(0, 311, width, 360);
}
void drawNet()
{
stroke(255);
strokeWeight(5);
line(width/2, NET_TOP, width/2, GROUND_LEVEL);
stroke(0);
strokeWeight(1);
line(width/2, NET_TOP, width/2, height);
}
void drawScore()
{
float INSET = 32;
float SPACING = 32;
float RAD = 12;
float TOPMARGIN = 40;
fill(255);
textFont(font, 16);
textAlign(LEFT, TOP);
text(p1.name, INSET - RAD, 8);
textAlign(RIGHT, TOP);
text(p2.name, width-INSET + RAD, 8);
if(point_timer > 0)
{
fill(255, 255, 400*point_timer/POINT_TIME, 500*point_timer/POINT_TIME);
textAlign(CENTER, CENTER);
if(p1s_point) text("<-- POINT!", width/2, TOPMARGIN);
else text("POINT! -->", width/2, TOPMARGIN);
if(p1score >= MAX_POINTS || p2score >= MAX_POINTS)
{
textFont(font, 32);
text("GAME!", width/2, height/2);
}
}
if(p1score >= MAX_POINTS)
{
textFont(font, 16);
textAlign(LEFT, TOP);
fill(210, 160, 0);
if((anim_frame/2) % 30 == MAX_POINTS+1) fill(255);
text("Winner!", INSET - RAD + 4, TOPMARGIN + RAD * 2 + 4);
fill(255, 210, 0);
if((anim_frame/2) % 30 == MAX_POINTS) fill(255);
text("Winner!", INSET - RAD + 2, TOPMARGIN + RAD * 2 + 2);
}
if(p2score >= MAX_POINTS)
{
textFont(font, 16);
textAlign(RIGHT, TOP);
fill(210, 160, 0);
if((anim_frame/2) % 30 == MAX_POINTS+1) fill(255);
text("Winner!", width - INSET + RAD + 2, TOPMARGIN + RAD * 2 + 4);
fill(255, 210, 0);
if((anim_frame/2) % 30 == MAX_POINTS) fill(255);
text("Winner!", width - INSET + RAD, TOPMARGIN + RAD * 2 + 2);
}
for(int i = 0; i < MAX_POINTS; i++)
{
if(p1score > i)
{
strokeWeight(2);
fill(255, 210, 0);
if((anim_frame/2) % 30 == i) fill(255);
stroke(210, 160, 0);
ellipse(INSET + SPACING*i, TOPMARGIN, RAD, RAD);
stroke(255);
if((anim_frame/2 + 1) % 30 == i) arc(INSET + SPACING*i, TOPMARGIN, RAD, RAD, TWO_PI*3/8, TWO_PI*7/8);
if((anim_frame/2 + 29) % 30 == i) arc(INSET + SPACING*i, TOPMARGIN, RAD, RAD, TWO_PI*7/8, TWO_PI*11/8);
}
else
{
strokeWeight(1);
noFill();
stroke(160);
ellipse(INSET + SPACING*i, TOPMARGIN, RAD, RAD);
}
if(p2score > i)
{
strokeWeight(2);
fill(255, 210, 0);
if((anim_frame/2) % 30 == 4-i) fill(255);
stroke(210, 160, 0);
ellipse(width - INSET - SPACING*i, TOPMARGIN, RAD, RAD);
stroke(255);
if((anim_frame/2 + 1) % 30 == 4-i) arc(width - INSET - SPACING*i, TOPMARGIN, RAD, RAD, TWO_PI*3/8, TWO_PI*7/8);
if((anim_frame/2 + 29) % 30 == 4-i) arc(width - INSET - SPACING*i, TOPMARGIN, RAD, RAD, TWO_PI*7/8, TWO_PI*11/8);
}
else
{
strokeWeight(1);
noFill();
stroke(160);
ellipse(width - INSET - SPACING*i, TOPMARGIN, RAD, RAD);
}
}
}
void stop()
{
audioClose();
super.stop();
}
void physicsStep()
{
game_frames++;
wave_phase += WAVE_SPEED;
fg_particles.tick();
bg_particles.tick();
if(game_state == TITLE)
{
if(menu_state == TWOPLAYER_SELECT && p1_ready && p2_ready)
{
if(gamestart_timer == GAMESTART_TIME)
{
bgmStop();
wave_sound.trigger();
}
gamestart_timer --;
if(gamestart_timer <= 0)
newGame(newCritterByIndex(p1_char_select.index, LEFT),
newCritterByIndex(p2_char_select.index, RIGHT), -1);
}
if(menu_state == ONEPLAYER_SELECT && p1_ready)
{
if(gamestart_timer == GAMESTART_TIME)
{
bgmStop();
wave_sound.trigger();
}
gamestart_timer --;
if(gamestart_timer <= 0)
doTournamentRound(0);
}
}
if(game_state == PLAYING)
{
if(p2ai) doAI();
if(p1score >= MAX_POINTS || p2score >= MAX_POINTS)
{
if(point_timer == POINT_TIME -1)
{
bgmStop();
}
if(point_timer == -POINT_TIME && gamestart_timer == GAMESTART_TIME)
{
if(!(p2ai && p2score >= MAX_POINTS)) bgmLoop("ddbeach.mp3");
}
p1.control_action = false;
p1.control_left = false;
p1.control_right = false;
p2.control_action = false;
p2.control_left = false;
p2.control_right = false;
}
p1.tick();
p2.tick();
ball.tick();
if(serve)
{
ball.pos.x = server.pos.x;
ball.pos.y = server.pos.y;
ball.vel.x = 0;
ball.vel.y = 0;
}
point_timer--;
if((p1score >= MAX_POINTS || p2score >= MAX_POINTS) && gamestart_timer != GAMESTART_TIME)
{
gamestart_timer --;
if(gamestart_timer <= 0)
{
if(tourneyround == -1 || p2score == MAX_POINTS || tourneyround >= NUM_CRITTERS -2) titleSetup();
else doTournamentRound(tourneyround +1);
}
}
}
//jukebox stuff
info_time--;
}
float AI_FUZZ = 10;
float random_serve_point;
float PANIC_LEVEL = 30;
void doAI()
{
if(ball.pos.x < width/2 || ball.vel.x < -0.001)
{
p2.control_left = (p2.pos.x > width*7/8 - p1.pos.x/2 + 10);
p2.control_right = (p2.pos.x < width*7/8 - p1.pos.x/2 - 10);
p2.control_action = false;
}
else
{
p2.control_left = (expected_landing + AI_FUZZ < p2.pos.x);
p2.control_right = (expected_landing - AI_FUZZ > p2.pos.x);
if(p2.state == p2.STATE_GROUND)
{
if(ball.pos.y < p2.pos.y || inRadius(ball.pos, p2.pos, p2.reach) && point_timer <= 0) p2.control_action = !p2.control_action;
}
if(p2.state == p2.STATE_RISE)
{
if(ball.vel.y < 0 && ball.pos.y < p2.pos.y) p2.control_action = true;
else p2.control_action = false;
if(PVector.angleBetween(ball.vel, PVector.sub(p2.pos, ball.pos)) < 0.5) p2.control_action = true;
}
if(p2.state == p2.STATE_FALL)
{
if(PVector.angleBetween(ball.vel, PVector.sub(p2.pos, ball.pos)) < 0.5 || (ball.pos.y > p2.pos.y) ) p2.control_action = true;
}
if(p2.state == p2.STATE_FOCUS)
{
if(inRadius(ball.pos, p2.pos, p2.reach) || ball.pos.y > p2.pos.y || ball.pos.y > GROUND_LEVEL - PANIC_LEVEL) p2.control_action = false;
p2.control_left = p2.control_right = false;
if(p1.pos.x < width/4) p2.control_right = true;
else p2.control_left = true;
}
}
}
void ballLand(float xpos)
{
if(game_on)
{
game_on = false;
point_timer = POINT_TIME;
if(!muted) point_sound.trigger();
if(ball.pos.x > width/2)
{
p1score ++;
server = p2;
p1s_point = true;
}
else
{
p2score ++;
server = p1;
p1s_point = false;
}
}
}
void drawTitle()
{
color fill_c = color(192, 16, 64);
color stroke_c = color(148, 0, 32);
textFont(font, 32);
textAlign(CENTER, TOP);
fill(stroke_c);
text("Dragondot's", width/2+3, 32+3);
fill(fill_c);
text("Dragondot's", width/2, 32);
fill(255);
textFont(font, 16);
text("EXTREME BEACH VOLLEYBALL", width/2, 72);
if(menu_state == MODE_MENU)
{
mode_menu.drawAt(width/2, 260);
}
if(menu_state == ONEPLAYER_SELECT || menu_state == TWOPLAYER_SELECT)
{
fill(255, 128+64*sin(anim_frame*0.1));
textFont(font, 16);
textAlign(LEFT, TOP);
if(gamestart_timer == GAMESTART_TIME) text("Q = back", 8, 8);
fill(0, 128);
rectMode(CORNERS);
noStroke();
rect(30, 320, width/2-30, 450);
textAlign(CENTER, TOP);
textFont(font, 8);
fill(128, 192, 255);
text("Player 1", width/4, 326);
p1_char_select.drawAt(width/4, 336);
if(p1_ready)
{
textFont(font, 8);
fill(255, 255 - (anim_frame%16)*4, 0);
text("READY!", width/4, 438);
}
}
if(menu_state == TWOPLAYER_SELECT)
{
fill(0, 128);
rectMode(CORNERS);
noStroke();
rect(width/2+30, 320, width-30, 450);
textAlign(CENTER, TOP);
textFont(font, 8);
fill(128, 192, 255);
text("Player 2", width*3/4, 326);
p2_char_select.drawAt(width*3/4, 336);
if(p2_ready)
{
textFont(font, 8);
fill(255, 255 - (anim_frame%16)*4, 0);
text("READY!", width*3/4, 438);
}
}
if(gamestart_timer != GAMESTART_TIME)
{
mosaic((GAMESTART_TIME - gamestart_timer)/3+1);
fill(FADE_COLOR, 255-255*gamestart_timer/GAMESTART_TIME);
noStroke();
rect(0, 0, width, height);
}
}
void drawingStep()
{
if(!paused) anim_frame++;
background(234,233, 228);
drawBeach();
if(game_state == TITLE)
{
drawTitle();
}
if(game_state == PLAYING)
{
bg_particles.draw();
p1.draw();
p2.draw();
ball.draw();
drawNet();
fg_particles.draw();
//debug physics
if(debug_aiming)
{
strokeWeight(1);
stroke(0, 0, 255);
line(debug_left_bound, GROUND_LEVEL, debug_right_bound, GROUND_LEVEL);
stroke(255, 0, 0);
line(expected_landing, GROUND_LEVEL - 5, expected_landing, GROUND_LEVEL + 5);
}
drawScore();
if(game_frames < IRIS_TIME)
{
diamonds(game_frames * 100.0 / IRIS_TIME);
fill(255, 255 - (255 * game_frames / IRIS_TIME));
textFont(font, 32);
textAlign(CENTER, CENTER);
if(tourneyround >= 0)
{
if(tourneyround < NUM_CRITTERS - 2)
text("ROUND "+(tourneyround+1), width/2, height/2);
else
text("FINAL", width/2, height/2);
}
}
if(p1score >= MAX_POINTS && tourneyround == NUM_CRITTERS - 1)
{
fill(0, min(128 * (1 - 1.0 * point_timer / POINT_TIME), 128));
noStroke();
rect(0, 0, width, height);
fill(255, min(255 * (0 - 1.0 * point_timer / POINT_TIME), 255));
textFont(font, 32);
textAlign(CENTER, TOP);
text("CONGRATULATIONS!", width/2, 100);
fill(255, min(255 * (-1 - 1.0 * point_timer / POINT_TIME), 255));
textFont(font, 16);
text("Title screen music by John Axon\nEverything else by Nathan McCoy\n\nInspired by Slime Volleyball\nand Mario Tennis\n\nDedicated to my awesome fans\n\nTo unlock extra characters,\npress \"!\" on title screen.", width/2, 160);
}
if(gamestart_timer != GAMESTART_TIME)
{
mosaic((GAMESTART_TIME - gamestart_timer)/3+1);
fill(FADE_COLOR, 255-255*gamestart_timer/GAMESTART_TIME);
noStroke();
rect(0, 0, width, height);
}
}
if(paused)
{
fill(0, 128);
noStroke();
strokeWeight(1);
rectMode(CORNERS);
rect(0, 0, width, height);
fill(255);
textAlign(CENTER, CENTER);
textFont(font, 32);
text("PAUSED", width/2, height/2);
textFont(font, 8);
text("Q = exit game", width/2, height/2 + 32);
}
drawSongInfo();
}
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') jbToggleMute();
if(theKey == 'P' && game_state != TITLE)
{
paused = !paused;
if(!muted) blip_sound.trigger();
if(paused) bgmDuck(-10);
else bgmDuck(0);
}
if(game_state == PLAYING)
{
if(!paused)
{
if(theKey == 'C') p1.control_left = true;
if(theKey == 'V') p1.control_right = true;
if(theKey == 'Z') p1.control_action = true;
if(!p2ai)
{
if(theKey == LEFT) p2.control_left = true;
if(theKey == RIGHT) p2.control_right = true;
if(theKey == CONTROL) p2.control_action = true;
}
}
else if(theKey == 'Q')
{
titleSetup();
if(!muted) select_sound.trigger();
}
if((p1score >= MAX_POINTS || p2score >= MAX_POINTS) && gamestart_timer == GAMESTART_TIME && point_timer <= 0)
{
if(theKey == 'Z' || theKey == CONTROL)
{
gamestart_timer--;
if(!muted)wave_sound.trigger();
bgmStop();
}
}
}
if(game_state == TITLE)
{
if(!secret && keys[SHIFT] && theKey == '1')
{
secret = true;
p1_char_select = new FlipMenu(ALL_CHARACTERS);
//p1_char_select.base_size = 8;
p2_char_select = new FlipMenu(ALL_CHARACTERS);
//p2_char_select.base_size = 8;
if(!muted) secret_sound.trigger();
}
if(menu_state == MODE_MENU)
{
if(theKey == 'C' || theKey == LEFT)
{
if(!muted) blip_sound.trigger();
mode_menu.prev();
}
if(theKey == 'V' || theKey == RIGHT)
{
if(!muted) blip_sound.trigger();
mode_menu.next();
}
if(theKey == 'Z' || theKey == CONTROL)
{
if(!muted) select_sound.trigger();
if(mode_menu.index == 0) menu_state = ONEPLAYER_SELECT;
if(mode_menu.index == 1) menu_state = TWOPLAYER_SELECT;
}
}
else
{
if(gamestart_timer == GAMESTART_TIME)
{
if(menu_state == ONEPLAYER_SELECT || menu_state == TWOPLAYER_SELECT)
{
if(theKey == 'Q')
{
menu_state = MODE_MENU;
p1_ready = false;
p2_ready = false;
if(!muted) blip_sound.trigger();
}
if(theKey == 'C')
{
if(!muted) blip_sound.trigger();
p1_char_select.prev();
p1_ready = false;
}
if(theKey == 'V')
{
if(!muted) blip_sound.trigger();
p1_char_select.next();
p1_ready = false;
}
if(theKey == 'Z' && !p1_ready)
{
if(!muted) select_sound.trigger();
p1_ready = true;
}
}
if(menu_state == TWOPLAYER_SELECT)
{
if(theKey == LEFT)
{
if(!muted) blip_sound.trigger();
p2_char_select.prev();
p2_ready = false;
}
if(theKey == RIGHT)
{
if(!muted) blip_sound.trigger();
p2_char_select.next();
p2_ready = false;
}
if(theKey == CONTROL && !p2_ready)
{
if(!muted) select_sound.trigger();
p2_ready = true;
}
}
}
}
}
}
void up(int theKey)
{
//println(theKey + " up");
if(game_state == PLAYING)
{
if(!paused)
{
if(theKey == 'C') p1.control_left = false;
if(theKey == 'V') p1.control_right = false;
if(theKey == 'Z') p1.control_action = false;
if(!p2ai)
{
if(theKey == LEFT) p2.control_left = false;
if(theKey == RIGHT) p2.control_right = false;
if(theKey == CONTROL) p2.control_action = false;
}
}
}
}