/** * Game 28: "Giant Vex"
* Taunt the giant for fun and profit.
*
Go play more games at NMcCoy.net!
* Creative Commons License
This work by Nathan McCoy is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License. */ import ddf.minim.*; color BG_COLOR = color(128, 168, 64);// color(96, 168, 32); color BG_VARIATION_COLOR = color(133, 173, 64);// color(96, 168, 32); color BG_VARIATION_COLOR_2 = color(123, 163, 64);// color(96, 168, 32); color SHADOW_COLOR = color(96, 128, 32);//color(0, 32, 64, 64);// float DRAGONDOT_RADIUS = 16; color DRAGONDOT_FILL = color(192, 16, 64); color DRAGONDOT_STROKE = color(148, 0, 32); float KOBOLD_RADIUS = 10; color KOBOLD_FILL = color(175, 215, 180); color KOBOLD_STROKE = color(196, 176, 97); float PLAYER_WALK_SPEED = 3; float PLAYER_MASS = 10; float Z_SCALE = 0.5; float GRAVITY = 0.3; float JUMP_STRENGTH = 5; float FLAP_STRENGTH = 5; float FLAP_CEILING = 400; float FLAP_DRAG = 0.9; float FLAP_SPEED = 3; int COMBO_TIME = 8; int NAME_TIME = 120; int PLAYER_START_HP = 10; int WORLD_WIDTH = 1; int WORLD_HEIGHT = 1; int WORLD_START_X = 0; int WORLD_START_Y = 0; int LIFEBAR_W = 50; int LIFEBAR_H = 4; PFont font; ZSorter zSort = new ZSorter(); Screen testScreen; Screen[][] world; int world_x; int world_y; ParticleList smokeParticles; ParticleList textParticles; ArrayList loot_drop; boolean muted; boolean paused; int score; int level; int topscore; int toplevel = 1; boolean can_fly; boolean cheated; boolean won; boolean at_title; String overlay_text; boolean[] keys = new boolean[255]; int KEY_LEFT = 37; int KEY_RIGHT = 39; int KEY_UP = 38; int KEY_DOWN = 40; int KEY_JUMP = 90; //Z int KEY_ATTACK = 88; //X int KEY_SPECIAL = 67; //C int KEY_KILLALL = 8; //backspace int KEY_PHATLOOT = 61; Critter player; Giant theGiant; Minim minim; AudioSample clawSound; AudioSample biteSound; AudioSample sweepSound; AudioSample poofSound; AudioSample knifeSound; AudioSample hitSound; AudioSample lootSound; AudioSample lootBounceSound; AudioSample heartSound; AudioSample clearSound; AudioSample clubSound; AudioSample levelUpSound; AudioSample ghostSound; AudioSample thunderSound; AudioSample tauntSound; AudioPlayer bgm; /// UTILITY ////////////////////////////////////////////////////////////////////////////// void openSound() { minim = new Minim(this); clawSound = minim.loadSample("claw.wav", 1024); biteSound = minim.loadSample("bite.wav", 1024); sweepSound = minim.loadSample("sweep.wav", 1024); poofSound = minim.loadSample("poof.wav", 1024); knifeSound = minim.loadSample("knife.wav", 1024); hitSound = minim.loadSample("hit.wav", 1024); lootSound = minim.loadSample("loot.wav", 1024); lootBounceSound = minim.loadSample("lootbounce.wav", 1024); heartSound = minim.loadSample("heart.wav", 1024); clearSound = minim.loadSample("areaclear.wav", 1024); clubSound = minim.loadSample("club.wav", 1024); levelUpSound = minim.loadSample("levelup.wav", 2048); ghostSound = minim.loadSample("ghost.wav", 1024); thunderSound = minim.loadSample("thunder.wav", 1024); tauntSound = minim.loadSample("taunt.wav", 1024); bgm = minim.loadFile("koboldot.mp3", 4096); } void closeSound() { clawSound.close(); biteSound.close(); sweepSound.close(); poofSound.close(); knifeSound.close(); hitSound.close(); lootSound.close(); lootBounceSound.close(); heartSound.close(); clearSound.close(); clubSound.close(); levelUpSound.close(); ghostSound.close(); thunderSound.close(); tauntSound.close(); bgm.close(); minim.stop(); } boolean inRadius(PVector a, PVector b, float rad) { return rad*rad > (a.x - b.x)*(a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z); } PVector randomVector(float magn) { float angle = random(TWO_PI); return new PVector(cos(angle)*magn, sin(angle)*magn); } class ParticleList extends ArrayList { void tick() { Iterator i = iterator(); while(i.hasNext()) { Particle p = (Particle) i.next(); p.tick(); if(p.life <= 0) i.remove(); } } void draw() { Iterator i = iterator(); while(i.hasNext()) { Particle p = (Particle) i.next(); p.draw(); } } } void spawnEnemies() { int i = (int) (level * 1.5)+1; //i = 20; while(i > 0) { Critter c; if(i >= 15 && random(3)< 1) { c = new Drakeling(random(width), random(height)); i -= 10; }else if(i >= 10 && random(5)< 1) { c = new Ghost(random(width), random(height)); i -= 5; }else if(i >= 6 && random(4)< 1) { c = new Rogue(random(width), random(height)); i -= 4; }else if(i >= 4 && random(2)< 1) { c = new Worg(random(width), random(height)); i -= 3; }else if(i >= 3 && random(2)< 1) { c = new Hobgoblin(random(width), random(height)); i -= 2; } else { c = new Goblin(random(width), random(height)); i--; } c.pos.z = random(600)+600; world[0][0].critters.add(c); } } abstract class Particle { PVector pos; PVector vel; PVector accel; int life; int maxlife; Particle(PVector p, PVector v, int lifetime) { this(p, v, new PVector(0, 0), lifetime); } Particle(PVector p, PVector v, PVector a, int lifetime) { accel = new PVector(a.x, a.y); pos = new PVector(p.x, p.y); vel = new PVector(v.x, v.y); life = maxlife = lifetime; } void tick() { vel.add(accel); pos.add(vel); life--; } abstract void draw(); } class SmokePoof extends Particle { SmokePoof(PVector p, int bigness) { super(p, randomVector(random(1)), new PVector(0, -0.05), bigness*2); } void draw() { fill(230, 230, 220); noStroke(); ellipse(pos.x, pos.y, life/2, life/2); } } 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(p, 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); } } // CLASSES //////////////////////////////////////////////////////////////////////////////// class ZSorter implements Comparator { int compare(Object o1, Object o2) { Critter c1 = (Critter) o1; Critter c2 = (Critter) o2; if(c1.pos.z < c2.pos.z) return -1; if(c1.pos.z > c2.pos.z) return 1; return 0; } } /////// ATTACKS class Attack { String name; float radius; float distance; float knock_away; float knock_upward; int damage; int warm_frames; int active_frames; int cool_frames; int animation; int stun_time; static final int SLASH = 0; static final int SWEEP = 1; static final int BITE = 2; static final int FIREBALL = 3; color anim_color = color(255); AudioSample sound; void draw(int frame) { if(frame >= warm_frames && frame < warm_frames + active_frames) { if(animation == SLASH) { noFill(); stroke(anim_color, map(frame, warm_frames, warm_frames+active_frames, 255, 0)); strokeWeight(2); ellipse(0, 0, radius, radius); ellipse(0, 0, radius * 0.8, radius * 0.8); ellipse(0, 0, radius * 0.6, radius * 0.6); } else if(animation == BITE) { noFill(); float ratio = map(frame, warm_frames, warm_frames+active_frames, 0, 1); stroke(anim_color); strokeWeight(2); line(0, -radius, 0, -radius + 2 * ratio * radius); line(-radius * 0.8, -radius * 0.5, -radius * 0.8, (-radius + 2 * ratio * radius) * 0.5); line(radius * 0.8, -radius * 0.5, radius * 0.8, (-radius + 2 * ratio * radius) * 0.5); line(-radius * 0.4, radius * 0.75, -radius * 0.4, (radius - 2 * ratio * radius) * 0.75); line(radius * 0.4, radius * 0.75, radius * 0.4, (radius - 2 * ratio * radius) * 0.75); } else if(animation == SWEEP) { fill(anim_color, min(map(frame, warm_frames, warm_frames+active_frames, 510, 0), map(frame, warm_frames, warm_frames+active_frames, 0, 510))); noStroke(); ellipse(0, 0, radius, radius); } else { fill(anim_color); noStroke(); ellipse(0, 0, radius, radius); } } } } class DrakeClaw extends Attack { DrakeClaw() { name = "claw!"; radius = 22; distance = 9; warm_frames = 8; active_frames = 6; cool_frames = 8; animation = SLASH; damage = 1; knock_away = 3; knock_upward = 2; stun_time = 6; sound = clawSound; } } class Claw extends Attack { Claw() { name = "claw!"; radius = 22; distance = 9; warm_frames = 0; active_frames = 6; cool_frames = 8; animation = SLASH; damage = 1; knock_away = 3; knock_upward = 2; stun_time = 6; sound = clawSound; } } class Bite extends Attack { Bite() { name = "bite!"; radius = 20; distance = 30; warm_frames = 0; active_frames = 6; cool_frames = 10; animation = BITE; damage = 2; knock_away = 0; knock_upward = 0; stun_time = 12; sound = biteSound; } } class TailSweep extends Attack { TailSweep() { name = "tail sweep!"; radius = 43; distance = 9; warm_frames = 3; active_frames = 12; cool_frames = 20; anim_color = DRAGONDOT_FILL; animation = SWEEP; damage = 1; knock_away = 4; knock_upward = 8; stun_time = 30; sound = sweepSound; } } class Knife extends Attack { Knife() { name = "knife!"; radius = 10; distance = 8; warm_frames = 20; active_frames = 6; cool_frames = 8; animation = SLASH; damage = 1; knock_away = 4; knock_upward = 2; stun_time = 30; sound = knifeSound; } } class Club extends Attack { Club() { name = "club!"; radius = 16; distance = 10; warm_frames = 15; active_frames = 8; cool_frames = 10; animation = SWEEP; damage = 2; knock_away = 8; knock_upward = 5; stun_time = 30; anim_color = color(128, 64, 0); sound = clubSound; } } class WorgBite extends Attack { WorgBite() { name = "bite!"; radius = 14; distance = 15; warm_frames = 15; active_frames = 8; cool_frames = 60; animation = BITE; damage = 2; knock_away = 0; knock_upward = 0; stun_time = 20; sound = biteSound; } } class DireWorgBite extends Attack { DireWorgBite() { name = "bite!!"; radius = 32; distance = 35; warm_frames = 15; active_frames = 8; cool_frames = 80; animation = BITE; damage = 5; knock_away = 0; knock_upward = 0; stun_time = 30; sound = biteSound; } } class GhostAura extends Attack { GhostAura() { name = "BOO!"; radius = 17; distance = 0; warm_frames = 0; active_frames = 20; cool_frames = 20; animation = SWEEP; damage = 2; knock_away = 10; knock_upward = 10; stun_time = 30; sound = ghostSound; } } class ScorchingBurst extends Attack { ScorchingBurst() { name = "Fireball!"; radius = 35; distance = 150; warm_frames = 20; active_frames = 10; cool_frames = 120; animation = SWEEP; damage = 3; knock_away = 0; knock_upward = 20; stun_time = 30; anim_color = color(255, 192, 0); sound = sweepSound; } } class ThunderWave extends Attack { ThunderWave() { name = "Thunder Pulse!"; radius = 50; distance = 0; warm_frames = 0; active_frames = 10; cool_frames = 100; animation = SLASH; damage = 1; knock_away = 10; knock_upward = 10; stun_time = 30; anim_color = color(192, 192, 192); sound = thunderSound; } } class NinjaKnife extends Knife { NinjaKnife() { super(); warm_frames = 8; cool_frames = 15; } } class GiantClub extends Attack { GiantClub() { name = "club!!"; radius = 60; distance = 40; warm_frames = 20; active_frames = 12; cool_frames = 0;//100; anim_color = color(128, 64, 0); animation = SWEEP; damage = 4; knock_away = 20; knock_upward = 20; stun_time = 50; sound = clubSound; } } class BGFeature { float radius; color col; PVector pos; BGFeature(PVector p, float r, color c) { pos = p; radius = r; col = c; } void draw() { noStroke(); fill(col); ellipse(pos.x, pos.y, radius, radius); } } class Screen { int rescue_award = 5; ArrayList critters; ArrayList features; boolean cleared; boolean killedFriendly; Screen() { cleared = false; killedFriendly = false; critters = new ArrayList(); features = new ArrayList(); for(int i = 0; i < 5; i++) { features.add(new BGFeature(new PVector(random(width), random(height)), random(40)+40, BG_VARIATION_COLOR_2)); } for(int i = 0; i < 5; i++) { features.add(new BGFeature(new PVector(random(width), random(height)), random(20)+20, BG_VARIATION_COLOR)); } } void draw() { for(int i = 0; i < features.size(); i++) { BGFeature b = (BGFeature) features.get(i); b.draw(); } for(int i = 0; i < critters.size(); i++) { Critter c = (Critter) critters.get(i); c.drawShadow(); } for(int i = 0; i < critters.size(); i++) { Critter c = (Critter) critters.get(i); c.drawAttack(); c.draw(); } for(int i = 0; i < critters.size(); i++) { Critter c = (Critter) critters.get(i); if(c.name_timer > 0) c.drawName(); } } void stepPhysics() { if(cleared) spawnEnemies(); for(int i = 0; i < critters.size(); i++) { Critter c = (Critter) critters.get(i); c.step(); } for(int i = 0; i < critters.size(); i++) { Critter c = (Critter) critters.get(i); for(int j = i+1; j< critters.size(); j++) { Critter d = (Critter) critters.get(j); c.resolveCollision(d); } } for(int i = 0; i < critters.size(); i++) { Critter c = (Critter) critters.get(i); for(int j = 0; j< critters.size(); j++) { if(i != j) { Critter d = (Critter) critters.get(j); c.resolveAttack(d); } } } Iterator it = critters.iterator(); { boolean hadHostiles = false; boolean hadFriendly = false; boolean hasHostiles = false; while(it.hasNext()) { Critter c = (Critter) it.next(); if(c.hostile) hadHostiles = true; else if(!c.inanimate && c!=player) hadFriendly = true; if(c.hp <= 0 && c.stun_timer == 0 && c.max_hp > 0 && !c.airborne) { c.poof(); // may create loot it.remove(); if(!c.hostile && !c.inanimate) { killedFriendly = true; println("Killed friendly "+c.name); } } else if(c.hostile) hasHostiles = true; } if(hadHostiles && !hasHostiles && player.hp > 0) { if(!muted) clearSound.trigger(); textParticles.add(new TextBlip(new PVector(width/2, height/2), 120, "Wave "+level+" Cleared!", color(128, 255, 255))); level++; toplevel = max(level, toplevel); /* if(!killedFriendly && hadFriendly) { textParticles.add(new TextBlip(new PVector(width/2, height/2), 180, "Rescue Bonus!", color(128, 255, 255))); loot_drop.add(new Loot(width/2, height/2, rescue_award)); }*/ } if(!hasHostiles) cleared = true; else cleared = false; } critters.addAll(loot_drop); loot_drop.clear(); //sort by Z-value for drawing Collections.sort(critters, zSort); } } abstract class Critter { PVector pos; PVector vel; PVector desired_vel; PVector fidget; PVector heading; PVector facing; float radius; color fill_color; color stroke_color; int hp; int max_hp; String name; float walk_speed; float traction; float air_traction; boolean airborne; int walk_timer; int stun_timer; float mass; int loot_value; int combo = -1; ArrayList attacks; int attack_timer; boolean attack_cue; boolean hostile; boolean inanimate; int name_timer; void touched(Critter c) { if(max_hp > 0) { if(c.mass > mass && c.pos.z > pos.z && c.vel.z < -12 && stun_timer == 0) injure(1, 20); } } Critter(float x, float y, float z) { pos = new PVector(x, y, z); radius = z; vel = new PVector(0, 0); desired_vel = new PVector(0, 0); fidget = new PVector(0, 0); heading = new PVector(0, 0); facing = new PVector(0, -1); walk_speed = 1; traction = 1; name = "Something"; airborne = false; mass = 1; attacks = new ArrayList(); name_timer = NAME_TIME; loot_value = 2; } void resolveCollision(Critter c) { if(inRadius(pos, c.pos, radius+c.radius)) { touched(c); c.touched(this); float overlap = (radius+c.radius) - PVector.dist(pos, c.pos); float myEject; if(mass == 0) myEject = 0; //zero-mass objects have infinite mass else if(c.mass == 0) myEject = 1; else myEject = 1-mass/(mass + c.mass); PVector toThem = PVector.sub(c.pos, pos); toThem.normalize(); pos.add(PVector.mult(toThem, -overlap*myEject)); c.pos.add(PVector.mult(toThem, overlap*(1-myEject))); vel.add(PVector.mult(toThem, -myEject * overlap)); c.vel.add(PVector.mult(toThem, (1-myEject) * overlap)); } } void resolveAttack(Critter c) { if(combo > -1) { Attack a = (Attack) attacks.get(combo); if(attack_timer >= a.warm_frames && attack_timer < a.warm_frames + a.active_frames) { PVector attackPos = new PVector(pos.x, pos.y, pos.z); attackPos.add(PVector.mult(facing, a.distance)); if(inRadius(attackPos, c.pos, a.radius + c.radius) && c.stun_timer == 0) { c.injure(a.damage, a.stun_time); if(c == theGiant) { theGiant.vexed = true; theGiant.target = this; textParticles.add(new TextBlip(theGiant.pos, 60, ">:O", color(255, 64, 0))); } if(c.mass != 0) { PVector away = PVector.sub(c.pos, pos); away.z = 0; away.normalize(); away.mult(a.knock_away); away.z = a.knock_upward; away.mult(11/(10+c.mass)); c.vel = away; } } } } } void injure(int amount, int stun) { hp -= amount; stun_timer = stun; if(max_hp != 0)textParticles.add(new TextBlip(pos, 60, ""+amount)); if(!muted) hitSound.trigger(); name_timer = NAME_TIME; } void poof() { for(int i = 0; i < 5; i++) { smokeParticles.add(new SmokePoof(pos, (int)radius + 5)); } if(!muted) poofSound.trigger(); int lootValue = loot_value; //if(!hostile) lootValue /= 2; while(lootValue > 0) { int value = lootValue/4 + 1; int drop_type = (int)random(3); PVector location = randomVector(random(radius)); location.add(pos); if(drop_type == 0 || drop_type == 1 || player.hp == player.max_hp || value > player.max_hp) loot_drop.add(new Loot(location.x, location.y, value)); else if(drop_type == 2) loot_drop.add(new Heart(location.x, location.y, value)); lootValue -= value; } } void drawShadow() { pushMatrix(); translate(pos.x, pos.y); translate(fidget.x, fidget.y); noStroke(); fill(SHADOW_COLOR); ellipse(0, 0, radius, radius); popMatrix(); } void draw() { pushMatrix(); translate(pos.x, pos.y); translate(fidget.x, fidget.y); translate(0, -Z_SCALE * pos.z); strokeWeight(2); fill(fill_color); stroke(stroke_color); if(stun_timer/2 % 2 == 0) ellipse(0, 0, radius, radius); popMatrix(); } void drawName() { textFont(font, 8); textAlign(CENTER, BOTTOM); fill(0); text(name, (int)pos.x+1, (int)(pos.y - Z_SCALE * pos.z - radius - 8 + 1)); fill(255); text(name, (int)pos.x, (int)(pos.y - Z_SCALE * pos.z - radius - 8)); if(max_hp > 0) { fill(0); noStroke(); strokeWeight(1); rect(pos.x - LIFEBAR_W/2, pos.y - Z_SCALE * pos.z - radius - LIFEBAR_H, LIFEBAR_W, LIFEBAR_H); noStroke(); fill(255, 0, 0); rect(pos.x - LIFEBAR_W/2, pos.y - Z_SCALE * pos.z - radius - LIFEBAR_H, map(max(hp, 0), 0, max_hp, 0, LIFEBAR_W), LIFEBAR_H); } } void drawAttack() { if(combo > -1) { Attack a = (Attack) attacks.get(combo); pushMatrix(); translate(pos.x, pos.y - Z_SCALE * pos.z); translate(facing.x * a.distance, facing.y * a.distance); a.draw(attack_timer); popMatrix(); } } void step() { name_timer --; /// PHYSICS heading.normalize(); //hack to make monsters stop attacking a dead player if(player.hp <= 0) { heading.x = 0; heading.y = 0; } if(combo > -1 || stun_timer > 0) heading.mult(0); stun_timer--; if(stun_timer < 0) stun_timer = 0; //else attack_cue = false; if(pos.z <= radius) { pos.z = radius; airborne = false; if(vel.z<0) vel.z= 0; } else airborne = true; if(hp <= 0) air_traction = 0; float using_traction; if(airborne) using_traction = air_traction; else using_traction = traction; PVector flatvel = new PVector(vel.x, vel.y); //if(!airborne) //{ desired_vel.x = heading.x * walk_speed; desired_vel.y = heading.y * walk_speed; if(PVector.dist(desired_vel, flatvel) < using_traction) { flatvel.x = desired_vel.x; flatvel.y = desired_vel.y; } else { PVector steer = PVector.sub(desired_vel, flatvel); steer.normalize(); steer.mult(using_traction); flatvel.add(steer); } if((heading.x != 0 || heading.y != 0) && !airborne) //if heading's not zero and we're on the ground, update walk anim { walk_timer++; int walk_frame = (int)(walk_timer / (radius / walk_speed))%4; if(walk_frame %2 == 0) { fidget.x = heading.x; fidget.y = heading.y; } if(walk_frame == 1) { fidget.x = -heading.y; fidget.y = heading.x; } if(walk_frame == 3) { fidget.x = heading.y; fidget.y = -heading.x; } } else { walk_timer = 0; fidget.x = 0; fidget.y = 0; } //} vel.x = flatvel.x; vel.y = flatvel.y; if(airborne) vel.z -= GRAVITY; pos.add(vel); // if(this != player) /// bad practice! { if(pos.x < 0) { pos.x = 0; vel.x *= -1; } if(pos.x > width) { pos.x = width; vel.x *= -1; } if(pos.y < 0) { pos.y = 0; vel.y *= -1; } if(pos.y > height) { pos.y = height; vel.y *= -1; } } if(heading.x != 0 || heading.y != 0) { facing.x = heading.x; facing.y = heading.y; facing.normalize(); } ////// ATTACKS if(combo == -1 && attack_cue && !attacks.isEmpty() && player.hp > 0 && hp > 0) { combo = 0; attack_cue = false; attack_timer = 0; } if(combo > -1) { Attack a = (Attack) attacks.get(combo); if(attack_timer == a.warm_frames) { if(!muted) a.sound.trigger(); textParticles.add(new TextBlip(new PVector(pos.x, pos.y - radius), 30, a.name, color(192))); } attack_timer++; if(attack_timer >= a.warm_frames + a.active_frames + a.cool_frames) { if(attack_cue && attacks.size() > combo+1) { attack_cue = false; combo++; attack_timer = 0; } else if(attack_timer >= a.warm_frames + a.active_frames + a.cool_frames + COMBO_TIME) { combo = -1; attack_cue = false; } } } } } class Dragon extends Critter { Dragon(float x, float y) { super(x, y, DRAGONDOT_RADIUS); fill_color = DRAGONDOT_FILL; stroke_color = DRAGONDOT_STROKE; walk_speed = PLAYER_WALK_SPEED; traction = 1;//PLAYER_WALK_SPEED*4; air_traction = 0.05; hp = max_hp = PLAYER_START_HP; name = "Dragondot"; mass = PLAYER_MASS; } } class Kobold extends Critter { Kobold(float x, float y) { super(x, y, KOBOLD_RADIUS); fill_color = KOBOLD_FILL; stroke_color = KOBOLD_STROKE; walk_speed = 3; traction = 1.0;//PLAYER_WALK_SPEED*4; air_traction = 0.1; hp = max_hp = 5; name = "Kobold"; mass = 1; } void step() { /*if(!inRadius(pos, player.pos, 70)) heading = PVector.sub(player.pos, pos); else{ heading.x = 0; heading.y=0; } */ heading.z = 0; super.step(); //if(walk_timer == 1 && hp == max_hp) textParticles.add(new TextBlip(pos, 60, "<3", color(255, 128, 255))); } void poof() { super.poof(); textParticles.add(new TextBlip(pos, 60, ":(", color(128, 0, 128))); } } class Goblin extends Critter { Goblin(float x, float y) { super(x, y, KOBOLD_RADIUS); hostile = true; fill_color = color(64, 192, 96); stroke_color = color(128, 96, 64); walk_speed = 1.5; traction = 0.5;//PLAYER_WALK_SPEED*4; hp = max_hp = 4; name = "Goblin"; mass = 1; attacks.add(new Knife()); loot_value = 2; } void step() { heading = PVector.sub(player.pos, pos); heading.z = 0; if(inRadius(player.pos, pos, player.radius + 20)) attack_cue = true; super.step(); } } class Hobgoblin extends Critter { Hobgoblin(float x, float y) { super(x, y, 12); hostile = true; fill_color = color(255, 160, 96); stroke_color = color(128, 96, 64); walk_speed = 1; traction = 0.5;//PLAYER_WALK_SPEED*4; hp = max_hp = 8; name = "Hobgoblin"; mass = 5; attacks.add(new Club()); loot_value = 4; } void step() { heading = PVector.sub(player.pos, pos); heading.z = 0; if(inRadius(player.pos, pos, player.radius + 25)) attack_cue = true; super.step(); } } class Worg extends Critter { Worg(float x, float y) { super(x, y, 12); hostile = true; fill_color = color(172, 160, 145); stroke_color = color(162, 130, 96); walk_speed = 2; traction = 0.1;//PLAYER_WALK_SPEED*4; hp = max_hp = 8; name = "Worg"; mass = 3; attacks.add(new WorgBite()); loot_value = 6; } void step() { heading = PVector.sub(player.pos, pos); heading.z = 0; if(inRadius(player.pos, pos, player.radius + 26)) attack_cue = true; super.step(); } } class Drakeling extends Critter { Drakeling(float x, float y) { super(x, y, 13); hostile = true; fill_color = color(230, 50, 20); stroke_color = color(100, 30, 0); walk_speed = 2.5; traction = 0.3;//PLAYER_WALK_SPEED*4; air_traction = 0.3; hp = max_hp = 9; name = "Drakeling"; mass = 3; attacks.add(new DrakeClaw()); attacks.add(new Claw()); attacks.add(new Bite()); loot_value = 20; } void step() { heading = PVector.sub(player.pos, pos); heading.z = 0; if(inRadius(player.pos, pos, player.radius + 30)) attack_cue = true; super.step(); } } class DireWorg extends Critter { DireWorg(float x, float y) { super(x, y, 24); hostile = true; fill_color = color(172, 160, 145); stroke_color = color(162, 130, 96); walk_speed = 3; traction = 0.1;//PLAYER_WALK_SPEED*4; hp = max_hp = 20; name = "Dire Worg"; mass = 20; attacks.add(new DireWorgBite()); } void step() { heading = PVector.sub(player.pos, pos); heading.z = 0; //facing = heading; if(inRadius(player.pos, pos, player.radius + 54) && stun_timer == 0) attack_cue = true; super.step(); } } class Ghost extends Critter { Ghost(float x, float y) { super(x, y, 12); hostile = true; fill_color = color(200, 200, 230); stroke_color = color(0, 180, 230); walk_speed = 3; traction = 0.03;//PLAYER_WALK_SPEED*4; hp = max_hp = 8; name = "Ghost"; mass = 1; attacks.add(new GhostAura()); loot_value = 10; } void step() { heading = PVector.sub(player.pos, pos); heading.z = 0; if(inRadius(player.pos, pos, player.radius + radius + 5)) attack_cue = true; super.step(); } } class Peasant extends Critter { Peasant(float x, float y) { super(x, y, KOBOLD_RADIUS); fill_color = color(255, 222, 208); stroke_color = color(166, 79, 21); walk_speed = 1; traction = 0.5;//PLAYER_WALK_SPEED*4; hp = max_hp = 1; name = "Peasant"; mass = 1; } void step() { if(inRadius(pos, player.pos, 100)) heading = PVector.sub(pos, player.pos); else{ heading.x = 0; heading.y=0; } heading.z = 0; super.step(); if(walk_timer == 1) textParticles.add(new TextBlip(pos, 60, ":O", color(0, 0, 128))); } } class Wizard extends Critter { ArrayList closeAttacks; ArrayList farAttacks; Wizard(float x, float y) { super(x, y, KOBOLD_RADIUS); closeAttacks = new ArrayList(); farAttacks = new ArrayList(); hostile = true; fill_color = color(64, 96, 255); stroke_color = color(0, 32, 128); walk_speed = 2; traction = 0.5;//PLAYER_WALK_SPEED*4; hp = max_hp = 16; name = "Wizard"; mass = 1; closeAttacks.add(new ThunderWave()); farAttacks.add(new ScorchingBurst()); } void step() { PVector flatmypos = new PVector(pos.x, pos.y); PVector flatppos = new PVector(player.pos.x, player.pos.y); if(inRadius(flatppos, flatmypos, 125)) { heading = PVector.sub(flatmypos, flatppos); heading.z = 0; if(inRadius(player.pos, pos, player.radius + 50) && combo == -1) { attacks = closeAttacks; attack_cue = true; } } else if(!inRadius(flatmypos, flatppos, 175)) { heading = PVector.sub(flatppos, flatmypos); heading.z = 0; } else { if(combo == -1) { heading = PVector.sub(flatppos, flatmypos); attacks = farAttacks; attack_cue = true; } } super.step(); } } class Rogue extends Critter { Rogue(float x, float y) { super(x, y, KOBOLD_RADIUS); hostile = true; fill_color = color(96, 96, 96); stroke_color = color(16, 16, 16); walk_speed = 4; traction = 0.5;//PLAYER_WALK_SPEED*4; hp = max_hp = 8; name = "Ninja"; mass = 1; attacks.add(new NinjaKnife()); loot_value = 8; } void step() { if(inRadius(player.pos, pos, player.radius + 20)) { heading = PVector.sub(player.pos, pos); attack_cue = true; } else { PVector dest = PVector.sub(player.pos, PVector.mult(player.facing, player.radius)); heading = PVector.sub(dest, pos); } super.step(); } } class Giant extends Critter { boolean vexed; Critter target; Giant(float x, float y) { super(x, y, 20); hostile = false; fill_color = color(230, 200, 170); stroke_color = color(128, 96, 64); walk_speed = 1; traction = 0.5;//PLAYER_WALK_SPEED*4; hp = max_hp = 20; name = "Giant"; mass = 20; attacks.add(new GiantClub()); vexed = false; target = player; loot_value = 60; } void step() { if(vexed) heading = PVector.sub(target.pos, pos); else {heading.x = 0; heading.y = 0;} heading.z = 0; if(inRadius(target.pos, pos, target.radius + 100) && stun_timer == 0 && vexed) { attack_cue = true; vexed = false; } super.step(); } void draw() { super.draw(); if(vexed) { fill(255, 0, 0, 64); noStroke(); ellipse(pos.x, pos.y - pos.z*Z_SCALE, radius, radius); } } } class Loot extends Critter { int shiny_timer; color base_color; Loot(float x, float y, int value) { super(x, y, 3 + value); vel = randomVector(0.5); vel.z = 5+random(8); stroke_color = color(192, 172, 60); fill_color = base_color = color(255, 245, 64); shiny_timer = 0; walk_speed = 3; traction = 0.05;//PLAYER_WALK_SPEED*4; air_traction = 0.02; hp = max_hp = value; name = "Loot"; mass = 0.2; inanimate = true; } void injure(int amount, int stun) { } void touched(Critter c) { if(c == player) hp = 0; } void poof() { if(!muted) lootSound.trigger(); textParticles.add(new TextBlip(pos, 60, "+"+max_hp, base_color)); addScore(max_hp); } void drawName() { } void step() { //if(world[world_x][world_y].cleared) vel.add(PVector.mult(PVector.sub(player.pos, pos), 0.0012)); super.step(); shiny_timer += (int)random(3); if(shiny_timer > 60) shiny_timer = 0; if(shiny_timer < 6) fill_color = color(255); else fill_color = base_color; } } class Heart extends Loot { Heart(float x, float y, int value) { super(x, y, value); fill_color = base_color = color(255, 160, 192); stroke_color = color(255, 32, 32); } void poof() { if(!muted) heartSound.trigger(); if(player.hp < player.max_hp) textParticles.add(new TextBlip(player.pos, 120, "<3", color(255, 128, 255))); player.hp = min(player.hp + max_hp, player.max_hp); player.name_timer = NAME_TIME; textParticles.add(new TextBlip(pos, 60, "+"+max_hp, base_color)); } } void doControls() { player.heading.x = 0; player.heading.y = 0; if(keys[KEY_UP]) player.heading.y += -1; if(keys[KEY_DOWN]) player.heading.y += 1; if(keys[KEY_LEFT]) player.heading.x += -1; if(keys[KEY_RIGHT]) player.heading.x += 1; player.heading.normalize(); } void levelUpTune() { if(!muted) levelUpSound.trigger(); bgm.setGain(-50); bgm.shiftGain(-50.0, 0.0, 4000); } void addScore(int amt) { score+= amt; topscore = max(score, topscore); /* if(score >= 25 && level <= 1) { levelUpTune(); level++; textParticles.add(new TextBlip(player.pos, 180, "LEVEL UP!\n+5 HP!", color(128, 255, 255))); player.hp = player.max_hp = player.max_hp + 5; } if(score >= 50 && level <= 2) { levelUpTune(); level++; textParticles.add(new TextBlip(player.pos, 180, "LEVEL UP!\nGained Tail Sweep!", color(128, 255, 255))); player.attacks.add(new TailSweep()); player.hp = player.max_hp; //player.hp = player.max_hp = player.max_hp + 5; } if(score >= 75 && level <= 3) { levelUpTune(); level++; textParticles.add(new TextBlip(player.pos, 180, "LEVEL UP!\n+5 HP!", color(128, 255, 255))); player.hp = player.max_hp = player.max_hp + 5; } if(score >= 100 && level <= 4) { levelUpTune(); level++; textParticles.add(new TextBlip(player.pos, 180, "LEVEL UP!\nGained Flight!", color(128, 255, 255))); can_fly = true; player.hp = player.max_hp; } if(score >= 125 && level <= 5) { levelUpTune(); level++; textParticles.add(new TextBlip(player.pos, 180, "LEVEL UP!\n+5 HP!", color(128, 255, 255))); player.hp = player.max_hp = player.max_hp + 5; } if(score >= 150 && level <= 6) { levelUpTune(); level++; textParticles.add(new TextBlip(player.pos, 180, "LEVEL UP!\nClaw Power Up!", color(128, 255, 255))); ((Attack)player.attacks.get(0)).damage = 2; ((Attack)player.attacks.get(1)).damage = 2; player.hp = player.max_hp; } if(score >= 175 && level <= 7) { levelUpTune(); level++; textParticles.add(new TextBlip(player.pos, 180, "LEVEL UP!\n+5 HP!", color(128, 255, 255))); player.hp = player.max_hp = player.max_hp + 5; } if(score >= 200 && level <= 8) { levelUpTune(); level++; textParticles.add(new TextBlip(player.pos, 180, "LEVEL UP!\nBite and Tail Power Up!", color(128, 255, 255))); ((Attack)player.attacks.get(2)).damage = 4; ((Attack)player.attacks.get(3)).damage = 2; player.hp = player.max_hp; } if(score >= 225 && level <= 9) { levelUpTune(); level++; textParticles.add(new TextBlip(player.pos, 180, "LEVEL UP!\n+5 HP!", color(128, 255, 255))); player.hp = player.max_hp = player.max_hp + 5; } */ } void zonePlayer() { boolean zoned = false; if(player.pos.x < 0) { if(world_x > 0) { world_x --; player.pos.x += width; zoned = true; } else player.pos.x = 0; } if(player.pos.y < 0) { if(world_y > 0) { world_y --; player.pos.y += height; zoned = true; } else player.pos.y = 0; } if(player.pos.x > width) { if(world_x < WORLD_WIDTH - 1) { world_x ++; player.pos.x -= width; zoned = true; } else player.pos.x = width; } if(player.pos.y > height) { if(world_y < WORLD_HEIGHT - 1) { world_y ++; player.pos.y -= height; zoned = true; } else player.pos.y = height; } if(zoned) { for(int i = 0; i < world[world_x][world_y].critters.size(); i++) { Critter c = (Critter) world[world_x][world_y].critters.get(i); if(c != player) { c.hp = c.max_hp; c.name_timer = NAME_TIME; } } textParticles.clear(); smokeParticles.clear(); } } void drawMap(float x, float y, float w, float h) { float cellw = w / WORLD_WIDTH; float cellh = h / WORLD_HEIGHT; for(int mapx = 0; mapx < WORLD_WIDTH; mapx++) for(int mapy = 0; mapy < WORLD_HEIGHT; mapy++) { float offx = x + cellw * mapx; float offy = y + cellh * mapy; if(world[mapx][mapy].cleared) fill(0, 32, 64, 48); else fill(64, 0, 0, 48); noStroke();//stroke(192); strokeWeight(1); rect(offx, offy, cellw-1, cellh-1); for(int i = 0; i < world[mapx][mapy].critters.size(); i++) { Critter c = (Critter) world[mapx][mapy].critters.get(i); if(c != player && !c.inanimate) { noStroke(); if(c.hostile) fill(255, 0, 0); else fill(0, 255, 255); smooth(); ellipse(offx + map(c.pos.x, 0, width, 0, cellw), offy + map(c.pos.y, 0, height, 0, cellh), 2, 2); noSmooth(); } } } fill(255, 0, 255); noStroke(); ellipse(x + cellw*world_x + map(player.pos.x, 0, width, 0, cellw), y + cellh*world_y + map(player.pos.y, 0, height, 0, cellh), 2, 2); } ///////////////////////////////////////////////////////////////////////////////////////////// void initializeWorld() { bgm.close(); bgm = minim.loadFile("koboldot.mp3", 4096); if(muted) bgm.mute(); bgm.loop(); player = new Kobold(width/4, height*3/4); theGiant = new Giant(width*3/4, height/4); smokeParticles = new ParticleList(); textParticles = new ParticleList(); loot_drop = new ArrayList(); score = 0; level = 1; can_fly = false; cheated = false; won = false; overlay_text = ""; world = new Screen[WORLD_WIDTH][WORLD_HEIGHT]; for(int wx = 0; wx < WORLD_WIDTH; wx++) for(int wy = 0; wy < WORLD_HEIGHT; wy++) { world[wx][wy] = new Screen(); world[wx][wy].critters.add(player); } world_x = WORLD_START_X; world_y = WORLD_START_Y; testScreen = world[world_x][world_y]; world[0][0].critters.add(theGiant); //world[0][0].critters.add(new Goblin(width/2, height/2)); spawnEnemies(); //player.attacks.add(new Claw()); //player.attacks.add(new Claw()); //player.attacks.add(new Bite()); ///player.attacks.add(new TailSweep()); /* for(int i = 0; i < 5; i++) world[2][2].critters.add(new Kobold(random(width), random(height))); for(int i = 0; i < 4; i++) world[1][2].critters.add(new Goblin(random(width), random(height))); for(int i = 0; i < 3; i++) world[2][1].critters.add(new Goblin(random(width), random(height))); for(int i = 0; i < 3; i++) world[2][1].critters.add(new Kobold(random(width), random(height))); for(int i = 0; i < 3; i++) world[1][1].critters.add(new Goblin(random(width), random(height))); for(int i = 0; i < 2; i++) world[1][1].critters.add(new Hobgoblin(random(width), random(height))); world[1][1].critters.add(new Kobold(random(width), random(height))); world[1][1].rescue_award = 7; for(int i = 0; i < 3; i++) world[1][3].critters.add(new Goblin(random(width), random(height))); world[1][3].critters.add(new Hobgoblin(random(width), random(height))); for(int i = 0; i < 2; i++) world[2][3].critters.add(new Hobgoblin(random(width), random(height))); for(int i = 0; i < 2; i++) world[3][3].critters.add(new Worg(random(width), random(height))); for(int i = 0; i < 2; i++) world[3][2].critters.add(new Goblin(random(width), random(height))); world[3][2].critters.add(new Worg(random(width), random(height))); for(int i = 0; i < 2; i++) world[3][1].critters.add(new Hobgoblin(random(width), random(height))); world[3][1].critters.add(new Worg(random(width), random(height))); world[0][1].critters.add(new Ghost(random(width), random(height))); world[0][1].critters.add(new Kobold(random(width), random(height))); world[0][1].rescue_award = 5; world[1][0].critters.add(new Ghost(random(width), random(height))); world[1][0].critters.add(new Worg(random(width), random(height))); world[0][0].critters.add(new Ghost(random(width), random(height))); world[0][0].critters.add(new Ghost(random(width), random(height))); world[0][0].critters.add(new Ghost(random(width), random(height))); for(int i = 0; i < 4; i++) world[3][4].critters.add(new Worg(random(width), random(height))); for(int i = 0; i < 3; i++) world[4][3].critters.add(new Worg(random(width), random(height))); world[4][3].critters.add(new Kobold(random(width), random(height))); world[4][3].rescue_award = 10; //testScreen.critters.add(new Boulder(random(width), random(height))); world[4][4].critters.add(new DireWorg(width/2, height/2)); world[4][0].critters.add(new Wizard(width/2, height/2)); for(int i = 0; i < 5; i++) { world[3][0].critters.add(new Rogue(random(width), random(height))); } for(int i = 0; i < 4; i++) { world[2][0].critters.add(new Goblin(random(width), random(height))); } world[2][0].critters.add(new Rogue(random(width), random(height))); world[2][0].critters.add(new Kobold(random(width), random(height))); world[2][0].critters.add(new Kobold(random(width), random(height))); world[2][0].rescue_award = 10; world[4][2].critters.add(new Rogue(random(width), random(height))); world[4][2].critters.add(new Worg(random(width), random(height))); world[4][2].critters.add(new Worg(random(width), random(height))); for(int i = 0; i < 4; i++) { world[4][1].critters.add(new Rogue(random(width), random(height))); } for(int i = 0; i < 3; i++) { world[4][1].critters.add(new Kobold(random(width), random(height))); } world[4][1].rescue_award = 10; world[0][4].critters.add(new Giant(width/2, height/2)); world[0][4].critters.add(new Goblin(random(width), random(height))); world[0][4].critters.add(new Goblin(random(width), random(height))); for(int i = 0; i < 3; i++) { world[0][2].critters.add(new Kobold(random(width), random(height))); world[0][2].critters.add(new Goblin(random(width), random(height))); world[0][2].critters.add(new Hobgoblin(random(width), random(height))); } world[0][2].rescue_award = 7; for(int i = 0; i < 5; i++) { world[0][3].critters.add(new Goblin(random(width), random(height))); world[0][3].critters.add(new Hobgoblin(random(width), random(height))); } for(int i = 0; i < 9; i++) { world[1][4].critters.add(new Hobgoblin(random(width), random(height))); } world[2][4].critters.add(new Hobgoblin(random(width), random(height))); world[2][4].critters.add(new Hobgoblin(random(width), random(height))); world[2][4].critters.add(new Hobgoblin(random(width), random(height))); world[2][4].critters.add(new Worg(random(width), random(height))); world[2][4].critters.add(new Worg(random(width), random(height))); */ } void setup() { at_title = true; size(720, 480,P2D); frameRate(60); font = loadFont("PressStartK-32.vlw"); openSound(); //initializeWorld(); } void draw() { if(!at_title) { if(player.hp <=0) { overlay_text = "GAME OVER\npress SPACE to try again\n\n\nTop score: "+ topscore+" Highest wave: "+toplevel; bgm.pause(); } else doControls(); background(BG_COLOR); ellipseMode(RADIUS); if(!paused) world[world_x][world_y].stepPhysics(); zonePlayer(); world[world_x][world_y].draw(); smokeParticles.draw(); if(!paused) smokeParticles.tick(); textParticles.draw(); if(!paused) textParticles.tick(); //drawMap(width-158, 8, 150, 100); fill(255); textAlign(LEFT, TOP); text("Score: "+score+"\nWave: "+level+"\nHP: "+player.hp+"/"+player.max_hp, 8, 8); //won = true; boolean kobolds = true; boolean massacre = true; for(int x = 0; x < WORLD_WIDTH; x++) for(int y = 0; y < WORLD_HEIGHT; y++) { if(world[x][y].cleared == false) won = false; if(world[x][y].killedFriendly == true) kobolds = false; if(world[x][y].critters.isEmpty() == false) massacre = false; } /* if(won) { overlay_text = "VICTORY!"; if(kobolds) overlay_text += "\nAnd all the kobolds survived! w00t!"; if(massacre) overlay_text += "\n...There's nothing left. o_O"; if(cheated) overlay_text += "\nNow try to do it without cheating!"; overlay_text += "\n\nPress SPACE to play again."; }*/ textAlign(CENTER, TOP); text(overlay_text, width/2, height/3); } else { background(BG_COLOR); textFont(font, 8); textAlign(CENTER, TOP); text("GIANT VEX\n\nMonsters are back.\nDragon is not back.\nGiant is big stupid.\nTime is for kobold fun?\n\npress SPACE to play\n\nx = vex giant z = jump SPACE = pause", width/2, (int)(height/3)); } } void stop() { closeSound(); super.stop(); } void togglePause() { paused = !paused; if(paused) bgm.mute(); else if(!muted) bgm.unmute(); } /////////////// INPUT HANDLING CODE //input processing goes here: void keyPressed() { if(!keys[keyCode]) { keys[keyCode] = true; down(keyCode); } } void keyReleased() { if(keys[keyCode]) { keys[keyCode] = false; up(keyCode); } } void down(int theKey) { if(!at_title) { if(theKey == 32 && (player.hp <= 0 || won)) { initializeWorld(); // temporary game over } else if(theKey == 32) togglePause(); if(theKey == 'M') { muted = !muted; if(bgm != null) { if(muted) bgm.mute(); else bgm.unmute(); } } if(!paused) { println(theKey + " down"); if(theKey == KEY_JUMP && player.combo == -1 && player.stun_timer == 0) { if(!player.airborne) player.vel.z = JUMP_STRENGTH; else if(can_fly) { player.vel.mult(FLAP_DRAG); player.vel.add(PVector.mult(player.heading, FLAP_SPEED)); if(player.pos.z < FLAP_CEILING) player.vel.z += (FLAP_STRENGTH * 1-(player.pos.z / FLAP_CEILING)); } } if(theKey == KEY_ATTACK && player.hp > 0) //player.attack_cue = true; { if(!muted) tauntSound.trigger(); textParticles.add(new TextBlip(player.pos, 60, ":P", color(255, 192, 128))); if(!theGiant.vexed) textParticles.add(new TextBlip(theGiant.pos, 60, ">:O", color(255, 64, 0))); theGiant.vexed = true; theGiant.target = player; } /* if(theKey == KEY_PHATLOOT) { cheated = true; addScore(25); } if(theKey == KEY_KILLALL) { cheated = true; int sz = world[world_x][world_y].critters.size(); for(int i = 0; i < sz; i++) { Critter c = (Critter) world[world_x][world_y].critters.get(i); if(c.hostile) c.hp = 0; } } */ } } else { if(theKey == 32) { at_title = false; initializeWorld(); } } } void up(int theKey) { println(theKey + " up"); }