int IDLE = 0; int WARMUP = 1; int ACTIVE = 2; int COOLDOWN = 3; float cb_size = 16; class Cloudbush extends GameObject { color col; int bumps; Cloudbush(PVector p, color c, int b) { pos = p; col = c; bumps = b; } void tick(){} void draw() { ellipseMode(RADIUS); noStroke(); strokeWeight(1); fill(col); for(int i=0; i 0 && i < bumps-1) ellipse(px+i*cb_size*2, pos.y-cb_size*2, cb_size*2, cb_size*2); } } } int this_stack = 0; class WeaponDrop extends GameObject { CheapWeapon weapon; PVector vel; int flicker; int avoid; WeaponDrop(PVector p, CheapWeapon wpn) { vel = PV(0, -2); pos = p; weapon = wpn; radiu = 8; flicker = 0; avoid = this_stack++; } void tick() { vel.y += 0.1; pos.add(vel); if(pos.y > GROUND_LEVEL-8) pos.y = GROUND_LEVEL-8; if(inRadius(pc.pos, pos, radiu+pc.radiu) && pc.hp>0) { removal_flag = true; pc.giveWeapon(weapon); this_stack--; if(weapon instanceof Nothing) pc.hp=pc.max_hp; } } void draw() { if(weapon instanceof Nothing) { stroke(255, 128, 230); strokeWeight(2); fill(255, phys_frame%25*5+125, 255); ellipse(pos.x, pos.y, radiu, radiu); } else { strokeWeight(4); stroke(weapon.fade1); line(pos.x+8, pos.y-8, pos.x-8, pos.y+8); strokeWeight(2); stroke(weapon.fade2); line(pos.x+8, pos.y-8, pos.x-8, pos.y+8); } flicker++; textFont(info_font, 8); fill(255, 255, 255*(flicker/4%2)); textAlign(CENTER, CENTER); text("Take this!", pos.x, pos.y-24 - 10*avoid); } } class Coin extends GameObject { int worth; PVector vel; Coin(PVector p) { pos=p; vel=randomVector(2); vel.y-=3; radiu=4; } void tick() { vel.y += 0.05; pos.add(vel); if(pos.y > GROUND_LEVEL-radiu) { pos.y = GROUND_LEVEL-radiu; vel.y = -vel.y/2; vel.x = vel.x/2; } if(pos.x < 0) { pos.x = 0; vel.x = -vel.x; } if(pos.x > width) { pos.x = width; vel.x = -vel.x; } if(inRadius(pc.pos, pos, radiu+pc.radiu) && pc.hp>0) { pickup(); removal_flag=true; } } void pickup() { score+=200; particles.add(new FlingText(pos, "+200", color(255, 255, 96))); if(!muted) coin_sound.trigger(); } void draw() { stroke(255, 230, 0); strokeWeight(2); fill(255, 255, phys_frame%50*5); ellipse(pos.x, pos.y, radiu, radiu); } } class Heart extends Coin { Heart(PVector p) { super(p); radiu = 6; } void draw() { stroke(255, 128, 230); strokeWeight(2); fill(255, phys_frame%25*5+125, 255); ellipse(pos.x, pos.y, radiu, radiu); } void pickup() { pc.hp = min(pc.max_hp, pc.hp+2); particles.add(new FlingText(pos, "<3", color(255, 160, 192))); if(!muted) heart_sound.trigger(); if(pc.hitstun_timer < 0) pc.hitstun_timer = -1; } } abstract class Attack { int warmup_time; int active_time; int cooldown_time; abstract Hitbox getbox(int frame); } class Hitbox { PVector pos; float rad; Hitbox(PVector p, float r) { pos=p; rad=r; } Hitbox(float x, float y, float r) { pos = new PVector(x, y); rad = r; } } class Critter extends GameObject { float reachy=0; color fill_color; color stroke_color; int reflexes_timer; int reflexes = 40; int coward = 1; float gravity = GLOBAL_GRAVITY; //y-accel to +jump_speed in the air, if jump_timer == 0 float jump_gravity = GLOBAL_JUMP_GRAVITY; float dash_gravity = GLOBAL_DASH_GRAVITY; float jump_speed = GLOBAL_JUMP_SPEED; //velocity to rise or fall at when jumping float x_speed = GLOBAL_X_SPEED; //ideal horizontal speed when moving float air_control = GLOBAL_AIR_CONTROL; //x-accel to x_wish*s_speed in the air float ground_control = GLOBAL_GROUND_CONTROL; //x-accel to x_wish*s_speed on the ground int jump_maxtime = GLOBAL_JUMP_TIME; //maximum frames spendable in rising jump state int dash_maxtime = GLOBAL_DASH_TIME; //length of a dash float dash_kick = GLOBAL_DASH_KICK; float dash_drag = GLOBAL_DASH_DRAG; float dash_bump = GLOBAL_DASH_BUMP; float air_jump = GLOBAL_AIR_JUMP; PVector vel; //critter's velocity float x_wish; //desired x steering, -1 to 1. boolean jump_button; boolean attack_button; boolean prev_attack_button; int facing_sign = 1; //which way is the critter facing? 1 or -1. boolean grounded; //is the critter on the ground in this frame? boolean jump_ready; boolean dash_ready; int jump_timer; //timer for 'rising jump' state int dash_timer; //timer for 'dashing' state int hitstun_timer; int attack_timer; int chargeup_timer; float knockback_resist = 1; int hp; int max_hp; CheapWeapon weapon; //int attack_phase; int combo_count; boolean attack_cue; Hitbox active_hitbox; void AIthink() { if(this != pc && !pc.removal_flag) { if(pc.pos.x < this.pos.x) x_wish = -1; if(pc.pos.x > this.pos.x) x_wish = 1; if(hitstun_timer > 1) x_wish *= coward; if(inRadius(pc.pos, pos, max((pc.radiu+radiu)*1.5, reachy))) { if(reflexes_timer == reflexes) {if(pc.pos.x < pos.x)facing_sign = -1; else facing_sign=1;} reflexes_timer--; x_wish=0; if(reflexes_timer <= 0) attack_button = !attack_button; } else { if(reflexes_timer <= 0) reflexes_timer = reflexes; if(reflexes_timer < reflexes) reflexes_timer--; if(reflexes_timer <3) attack_button = !attack_button; } } } void giveWeapon(CheapWeapon wpn) { weapon = wpn; combo_count = 0; attack_cue = false; chargeup_timer = 0; attack_timer = 0; particles.add(new GrandRing(pos)); //particles.add(new FlingText(pos, "<3", color(255, 160, 192))); particles.add(new DramaticAnnouncement(PV(pos.x, pos.y-32), wpn.name+" GET!")); score += WEAPON_BONUS; if(!muted) get_sound.trigger(); } Critter(PVector p, CheapWeapon wpn) { pos = p; vel = new PVector(0, 0); weapon = wpn; hp = max_hp = 4; //attack_phase = IDLE; radiu = 10; combo_count = 0; } Hitbox tickAttack() { if(combo_count == 0) attack_timer=0; else attack_timer++; if(attack_button && !prev_attack_button && hitstun_timer <= 3) attack_cue = true; if(hitstun_timer > 0) { chargeup_timer = 0; //reflexes_timer = reflexes; //attack_button = false; } if(weapon instanceof Nothing) { if(attack_cue) { attack_cue=false; particles.add(new FlingText(pos, "<3", color(255, 160, 192))); if(!muted) love_sound.trigger(); } } if(attack_cue && (combo_count == 0 || attack_timer > weapon.speed)) { if(combo_count < weapon.damage.length-1) { combo_count++; attack_timer=0; if(!muted) weapon.sound.trigger(); if(grounded) { PVector impulse = weapon.lunge[combo_count-1].get(); impulse.x *= facing_sign; vel.add(impulse); } } attack_cue = false; } if(attack_timer > weapon.speed + weapon.cooldown) { combo_count = 0; attack_timer = 0; } if(weapon.damage.length > 0) { if(combo_count == 0 && attack_button) { chargeup_timer++; if(chargeup_timer == weapon.charge_time) if(!muted) charge_sound.trigger(); } else { if(chargeup_timer > weapon.charge_time) { combo_count = weapon.damage.length; attack_timer=0; if(!muted) weapon.sound.trigger(); if(!muted) super_sound.trigger(); if(grounded) { PVector impulse = weapon.lunge[combo_count-1].get(); impulse.x *= facing_sign; vel.add(impulse); } } chargeup_timer=0; } } prev_attack_button = attack_button; if(combo_count > 0 && attack_timer <= weapon.speed) return cheapGetbox(weapon.reach_mult, weapon.reach_mult*weapon.sweep[combo_count-1], weapon.rad, attack_timer/(float)weapon.speed); else return null; } void tick() { active_hitbox = tickAttack(); AIthink(); if(pc.hp <= 0) chargeup_timer=0; if(active_hitbox != null) { active_hitbox.pos.mult(radiu); active_hitbox.rad *= radiu; if(combo_count == weapon.damage.length) { active_hitbox.pos.mult(1.5); active_hitbox.rad *= 1.5; } active_hitbox.pos.x *= facing_sign; active_hitbox.pos.add(pos); particles.add(new WeaponTrail(active_hitbox.pos, active_hitbox.rad, blendColors(weapon.fade1, weapon.fade2, sin(attack_timer/float(weapon.speed)*PI)))); } if (dash_timer > 0 || combo_count != 0) { x_wish = 0; } else { if (grounded && jump_button && jump_ready) { jump_timer = jump_maxtime; jump_ready = false; if(!muted) hop_sound.trigger(); } if(!grounded && jump_button && jump_ready && dash_ready && x_wish != 0) { dash_timer = dash_maxtime; jump_ready = false; dash_ready = false; vel.x = x_wish*dash_kick/weapon.weight; vel.y = dash_bump/weapon.weight; if(!muted) dash_sound.trigger(); particles.add(new DashRing(pos)); } if(!grounded && jump_button && jump_ready && dash_ready && x_wish == 0) { //dash_timer = dash_maxtime; jump_ready = false; dash_ready = false; vel.y = -air_jump; if(!muted) hop2_sound.trigger(); } } if (grounded) { vel.x = tend(vel.x, x_wish*x_speed/weapon.weight, ground_control); dash_ready = true; } //(x_wish * vel.x>=0 && abs(vel.x) > abs(x_speed)) else if(dash_timer > 0 || x_wish == 0) vel.x = tend(vel.x, x_wish*x_speed/weapon.weight, dash_drag); else vel.x = tend(vel.x, x_wish*x_speed/weapon.weight, air_control); if (!jump_button) { jump_timer=0; jump_ready = true; } if (jump_timer > 0) vel.y = -jump_speed/weapon.weight; else { if(dash_timer > 0) vel.y = tend(vel.y, jump_speed, dash_gravity); else if((jump_button && vel.y < 0)) vel.y = tend(vel.y, jump_speed, jump_gravity); else vel.y = tend(vel.y, jump_speed, gravity); } if(x_wish != 0 && combo_count == 0) { if(x_wish > 0) facing_sign = 1; else facing_sign = -1; } pos.add(vel); rectifyCollisions(); if (pos.y+radiu > GROUND_LEVEL) { pos.y=GROUND_LEVEL - radiu; //if(!grounded) if(!muted) land_sound.trigger(); grounded = true; dash_timer=0; vel.y = 0; } else grounded = false; if(pos.x < radiu) { pos.x = radiu; vel.x =- vel.x/4; } if(pos.x > width - radiu) { if(this==pc && (creatures.size() == 1 && pc.hp > 0)) { onward=true; } else { pos.x = width - radiu; vel.x =- vel.x/4; } } checkAttacks(); dash_timer--; if(jump_button && dash_timer == DASH_LINGER) dash_timer++; jump_timer--; hitstun_timer--; considerDying(); } void rectifyCollisions() { for(Object go:creatures) { if(go != this) { Critter c = (Critter) go; if(inRadius(pos, c.pos, radiu+c.radiu)) { PVector to = PVector.sub(pos, c.pos); float overlap = radiu+c.radiu-to.mag(); to.normalize(); to.mult(0.5 * overlap); pos.add(to); c.pos.sub(to); } } } } void checkAttacks() { if(active_hitbox != null) { for(Object go:creatures) { if(go != this) { Critter c = (Critter) go; if(inRadius(active_hitbox.pos, c.pos, active_hitbox.rad+c.radiu)) { // println("Hit!"); if(c.hitstun_timer <= 0) { c.hitstun_timer = GLOBAL_STUN; c.hp -= weapon.damage[combo_count-1]; PVector kb = weapon.knockback[combo_count-1].get(); kb.x *= facing_sign; kb.mult(1/c.knockback_resist); c.vel.add(kb); // c.vel.y += weapon.knockback[combo_count-1].y; // c.vel.x += weapon.knockback[combo_count-1].x*facing_sign; if(!muted) hit_sound.trigger(); particles.add(new FlingText(c.pos, ""+weapon.damage[combo_count-1], color(255), PVector.mult(kb, 0.5))); if(c!=pc) { score += weapon.damage[combo_count-1]*10; if(this != pc) score += weapon.damage[combo_count-1]*10; } } } } } } } void considerDying() { if(hp <= 0 && hitstun_timer <= 0) { removal_flag = true; if(!muted) die_sound.trigger(); for(int i=0; i<20;i++) { particles.add(new DeathDot(pos, radiu)); } particles.add(new GrandRing(pos)); if(pc.hp == pc.max_hp) loot.add(new WeaponDrop(pos.get(), weapon)); else loot.add(new Heart(pos.get())); for(int i=0; i*4 weapon.charge_time) { noStroke(); fill(255, chargeup_timer%12 * 10); ellipse(pos.x, pos.y, radiu+1, radiu+1); } } drawHP(); } }