static final int KICK = 0; static final int SNARE = 1; static final int HAT = 2; static final int NONE = 3; static final int NEUTRAL = 0; static final int HOPEFUL = 1; static final int AFRAID = 2; static final int HAPPY = 3; static final int VORACIOUS = 4; static final int GLEE = 5; class Pulser { int type; boolean pulsing, rendered, targeted, caught, dying; private color c, g; private int emote, points; private Particle p; private Spring s; private int attachCounter, caughtCounter, deathCounter, awareRadius; private float baseScale, scaleAmt, mass, chaseSpeed, enemyBounceAmt; private float faceOffX, faceOffY; Pulser(int t, float x, float y) { type = t; switch(t) { case KICK: scaleAmt = baseScale = kickRadius; c = kickColor; g = kickGlow; mass = kickMass; break; case SNARE: scaleAmt = baseScale = snareRadius; c = snareColor; g = snareGlow; mass = snareMass; break; case HAT: scaleAmt = baseScale = hatRadius; c = hatColor; g = hatGlow; mass = hatMass; break; } p = phys.makeParticle(mass, x, y, 0); s = phys.makeSpring(pl.peg, p, suckStrength, 1, pl.radius() + baseScale + 15); s.turnOff(); faceOffX = faceOffY = attachCounter = caughtCounter = deathCounter = 0; emote = NEUTRAL; pulsing = rendered = targeted = caught = dying = false; awareRadius = 300; chaseSpeed = 30; enemyBounceAmt = 20; points = 0; } void update() { rendered = false; puls.look(x(), y()); if ( isCaught() ) { caughtTick(); setMood(GLEE); } if ( isMoving() ) setMood(HAPPY); else resetMood(); if ( isOffScreen() ) p.kill(); } // interaction void interact(Player pl) { if ( awareOf(pl) ) { look(pl.x(), pl.y()); if ( s.isOff() ) setMood(HOPEFUL); } // collision float d = dist(x(), y(), pl.x(), pl.y()); float dx = (x() - pl.x())/d; float dy = (y() - pl.y())/d; if ( d < scaleAmt + pl.radius() ) { p.moveTo(pl.x() + dx*(scaleAmt + pl.radius()), pl.y() + dy*(scaleAmt + pl.radius()), 0); } } void interact(Deathskull sk) { float d = dist(x(), y(), sk.x(), sk.y()); float vx = (sk.x() - x())/d; float vy = (sk.y() - y())/d; // looking and attacking if ( awareOf(sk) ) { look(sk.x(), sk.y()); setMood(AFRAID); if ( s.isOff() && emote == VORACIOUS ) { p.setVelocity(vx*chaseSpeed, vy*chaseSpeed, 0); } } // collision: only damage if *not* attached to player()); if ( d < scaleAmt + sk.radius() && isMoving() ) { if ( s.isOff() ) { float dam = ( abs(vx()) + abs(vy()) - 10 ); float mult = ( emote == VORACIOUS ? 2 : 1 ); sk.damage(dam*mult); } p.moveTo(sk.x() - vx*(scaleAmt+sk.radius()), sk.y() - vy*(scaleAmt+sk.radius()), 0); p.setVelocity(-vx*enemyBounceAmt, -vy*enemyBounceAmt, 0); } } void interact(Sucker sb) { float d = dist(x(), y(), sb.x(), sb.y()); float vx = (sb.x() - x())/d; float vy = (sb.y() - y())/d; // looking and attacking if ( awareOf(sb) ) { look(sb.x(), sb.y()); // override possible happy mood if ( sb.isSucking() && emote != VORACIOUS ) emote = AFRAID; if ( s.isOff() && emote == VORACIOUS ) { p.setVelocity(vx*chaseSpeed, vy*chaseSpeed, 0); } } // collision: only damage if *not* attached to player if ( d < scaleAmt + sb.radius() ) { if ( s.isOff() ) { float dam = ( abs(vx()) + abs(vy()) - 10 ); float mult = ( emote == VORACIOUS ? 2 : 1 ); sb.damage(dam*mult); } p.moveTo(sb.x() - vx*(scaleAmt+sb.radius()), sb.y() - vy*(scaleAmt+sb.radius()), 0); p.setVelocity(-vx*enemyBounceAmt, -vy*enemyBounceAmt, 0); } } void interact(Blower sb) { float d = dist(x(), y(), sb.x(), sb.y()); float vx = (sb.x() - x())/d; float vy = (sb.y() - y())/d; // looking and attacking if ( awareOf(sb) ) { look(sb.x(), sb.y()); if ( s.isOff() && emote == VORACIOUS ) { p.setVelocity(vx*chaseSpeed, vy*chaseSpeed, 0); } } // collision: only damage if *not* attached to player if ( d < scaleAmt + sb.radius() ) { if ( s.isOff() ) { float dam = ( abs(vx()) + abs(vy()) - 10 ); float mult = ( emote == VORACIOUS ? 2 : 1 ); sb.damage(dam*mult); } p.moveTo(sb.x() - vx*(scaleAmt+sb.radius()), sb.y() - vy*(scaleAmt+sb.radius()), 0); p.setVelocity(-vx*enemyBounceAmt, -vy*enemyBounceAmt, 0); } } // update methods void look(float mx, float my) { if ( caught ) { mx = x(); my = y(); } float d = dist(x(), y(), mx, my); if ( d == 0 ) faceOffX = faceOffY = 0; else { faceOffX = 0.3f*(mx - x())/d; faceOffY = 0.3f*(my - y())/d; } } void pulse() { if ( !pulsing ) { pulsing = true; scaleAmt = baseScale * pulseAmt; } } void resetMood() { if ( emote != GLEE ) emote = NEUTRAL; } void setMood(int mood) { if ( mood > emote ) emote = mood; } void tether(Particle _p) { phys.makeSpring(p, _p, 0.5, 0.5f, 20); p.setVelocity(vx()*0.2, vy()*0.2, 0); } void caughtTick() { caughtCounter++; if ( caughtCounter/frameRate > 2 ) kill(); } void attach() { if ( s.isOff() ) { s.turnOn(); pl.setMass(pl.mass() + mass/20); } p.setVelocity(0, 0, 0); attachCounter++; } void detach() { if ( s.isOn() ) { s.turnOff(); pl.setMass(pl.mass() - mass/20); } attachCounter = 0; } void score(int s) { points = s; } void kill() { dying = true; p.makeFixed(); score.addScore(points, x(), y()); } void deathTick() { deathCounter++; if ( deathCounter/frameRate > 0.5f ) { dying = false; p.kill(); } } void wallBounce() { float force = pulsing ? 1.2 : 0.8; if ( x() < scaleAmt ) { p.moveTo(scaleAmt, y(), 0); p.setVelocity(-vx()*force, vy(), 0); } if ( x() > width - scaleAmt ) { p.moveTo(width-scaleAmt, y(), 0); p.setVelocity(-vx()*force, vy(), 0); } if ( y() < scaleAmt ) { p.moveTo(x(), scaleAmt, 0); p.setVelocity(vx(), -vy()*force, 0); } if ( y() > height - scaleAmt ) { p.moveTo(x(), height-scaleAmt, 0); p.setVelocity(vx(), -vy()*force, 0); } } void pulseBounce(Pulser puls) { float d = dist(x(), y(), puls.x(), puls.y()); if ( d < scaleAmt + puls.radius() + 5 ) { float vx = ((x() - puls.x())/d); float vy = ((y() - puls.y())/d); float forceX = puls.pulsing ? abs(vx())*2 : abs(vx()); float forceY = puls.pulsing ? abs(vy())*2 : abs(vy()); puls.setVelocity(-vx*10, -vy*10); p.setVelocity(vx*forceX, vy*forceY, 0); } } void setVelocity(float x, float y) { p.setVelocity(x, y, 0); } void addVelocity(float x, float y) { p.addVelocity(x, y, 0); } void moveBy(float x, float y) { p.moveBy(x, y, 0); } // state/relationship methods int mood() { return emote; } boolean awareOf(Player pl) { return dist(x(), y(), pl.x(), pl.y()) < awareRadius && !caught; } boolean awareOf(Deathskull sk) { boolean d = dist(x(), y(), sk.x(), sk.y()) < awareRadius; return ( d && emote == VORACIOUS && !caught ) || ( d && s.isOff() && !caught ); } boolean awareOf(Sucker sb) { float d = dist(x(), y(), sb.x(), sb.y()); if ( emote == VORACIOUS ) return ( d < awareRadius ); else return ( d < sb.forceRadius() + scaleAmt && sb.isSucking() && !caught ); } boolean awareOf(Blower sb) { boolean d = dist(x(), y(), sb.x(), sb.y()) < awareRadius; return d && emote == VORACIOUS && !caught; } boolean isFree() { return !caught; } boolean isCaught() { return caught; } boolean isAttached() { return s.isOn(); } float attachedFor() { return attachCounter / frameRate; } boolean isMoving() { return (abs(vx()) > 0.05f || abs(vy()) > 0.05f); } boolean isOnWall() { if ( !dying && ( x() < scaleAmt || x() > width - scaleAmt || y() < scaleAmt || y() > height - scaleAmt ) ) return true; return false; } boolean isOffScreen() { if ( !dying && ( x() < -scaleAmt || x() > width + scaleAmt || y() < -scaleAmt || y() > height + scaleAmt ) ) return true; return false; } boolean isDying() { return dying; } boolean isDead() { return p.isDead(); } float radius() { return scaleAmt; } float mass() { return mass; } float eyeXL() { return x() - (scaleAmt*0.55) + faceOffX; } float eyeXR() { return x() + (scaleAmt*0.55) + faceOffX; } float eyeYL() { return y() + scaleAmt*faceOffY; } float eyeYR() { return y() + scaleAmt*faceOffY; } float x() { return p.position().x(); } float y() { return p.position().y(); } float vx() { return p.velocity().x(); } float vy() { return p.velocity().y(); } float age() { return p.age(); } // render section void render() { float glow = glowIntensity; // draw the avatar noFill(); pushMatrix(); translate(x(), y()); if ( dying ) { stroke(c); scale(scaleAmt * (1 + deathCounter)); float inc = 1.2f; line(0, 1, 0, inc); line(0.95f, 0.31f, 0.95f*inc, 0.31f*inc); line(0.59f, -0.81f, 0.59f*inc, -0.81f*inc); line(-0.59f, -0.81f, -0.59f*inc, -0.81f*inc); line(-0.95f, 0.31f, -0.95f*inc, 0.31f*inc); } else { scale(scaleAmt); if ( drawGlow ) { // inner glow pushMatrix(); for (int i = 1; i < 10; i++) { stroke(red(g), green(g), blue(g), glow/i); scale(0.98); sept(1); pushMatrix(); translate(faceOffX, faceOffY); leftEye(); rightEye(); mouth(); popMatrix(); } popMatrix(); //outer glow pushMatrix(); for (int i = 1; i < 10; i++) { stroke(red(g), green(g), blue(g), glow/i); scale(1.02); sept(1); pushMatrix(); translate(faceOffX, faceOffY); leftEye(); rightEye(); mouth(); popMatrix(); } popMatrix(); } stroke(c); sept(1); pushMatrix(); translate(faceOffX, faceOffY); leftEye(); rightEye(); mouth(); popMatrix(); } popMatrix(); // update the current scale if ( pulsing ) scaleAmt = constrain(scaleAmt*0.96, baseScale, baseScale*pulseAmt); if ( scaleAmt == baseScale ) { pulsing = false; } rendered = true; } private void leftEye() { pushMatrix(); translate(-0.55, 0); rotate(PI); scale(0.26); if ( emote == NEUTRAL ) { beginShape(); vertex(0.951056516f, 0.309016994f); vertex(0.587785252f, -0.809016994f); vertex(-0.587785252f, -0.809016994f); vertex(-0.951056516f, 0.309016994f); endShape(CLOSE); line(0, 0.309016994f, 0, -0.809016994f); } else if ( emote == HAPPY || emote == AFRAID || emote == VORACIOUS ) { pent(1); switch(emote) { case HAPPY: float eyeY = -0.723947474f * abs(faceOffX) + 1; line(-faceOffX*1.2, eyeY, -faceOffX*1.2, -0.8); break; case AFRAID: rotate(TWO_PI/5); line(0.951056516f, 0.309016994f, 0.951056516f + 0.2, 0.309016994f + .75); line(0, 1, 0, -0.8); break; case VORACIOUS: rotate(-TWO_PI/5); line(-0.951056516f, 0.309016994f, -0.951056516f - 0.2, 0.309016994f + .75); line(0, 1, 0, -0.8); break; } } else { if ( emote == GLEE ) translate(0, 0.5); pent(1); // eyebrow line(0, 1, -0.4, 1.25); // slit float eyeY = -0.723947474f * abs(faceOffX) + 1; line(-faceOffX*1.2, eyeY, -faceOffX*1.2, -0.25); // lid line(0.8f, -0.25, -0.8f, -0.25); } popMatrix(); } private void rightEye() { pushMatrix(); translate(0.55, 0); rotate(PI); scale(0.26); if ( emote == NEUTRAL ) { beginShape(); vertex(0.951056516f, 0.309016994f); vertex(0.587785252f, -0.809016994f); vertex(-0.587785252f, -0.809016994f); vertex(-0.951056516f, 0.309016994f); endShape(CLOSE); line(0, 0.309016994f, 0, -0.809016994f); } else if ( emote == HAPPY || emote == AFRAID || emote == VORACIOUS ) { pent(1); switch(emote) { case HAPPY: float eyeY = faceOffX > 0 ? -0.723947474f * faceOffX + 1 : 0.723947474f * faceOffX + 1; line(-faceOffX*1.2, eyeY, -faceOffX*1.2, -0.8); break; case VORACIOUS: rotate(TWO_PI/5); line(0.951056516f, 0.309016994f, 0.951056516f + 0.2, 0.309016994f + .75); line(0, 1, 0, -0.8); break; case AFRAID: rotate(-TWO_PI/5); line(-0.951056516f, 0.309016994f, -0.951056516f - 0.2, 0.309016994f + .75); line(0, 1, 0, -0.8); break; } } else { if ( emote == GLEE ) translate(0, 0.5); pent(1); // eyebrow line(0, 1, 0.4, 1.25); // slit float eyeY = faceOffX > 0 ? -0.723947474f * faceOffX + 1 : 0.723947474f * faceOffX + 1; line(-faceOffX*1.2, eyeY, -faceOffX*1.2, -0.25); // lid line(0.8f, -0.25, -0.8f, -0.25); } popMatrix(); } private void mouth() { if ( emote == NEUTRAL ) { line(-0.55f, 0.55f, 0.55f, 0.55f); } else if ( emote == HAPPY || emote == AFRAID || emote == GLEE ) { pushMatrix(); switch(emote) { case AFRAID: translate(0, 0.95f); rotate(PI); break; case GLEE: beginShape(); vertex(-0.65f, 0.35f); vertex(-0.7375f, 0.15f); vertex(0.7375f, 0.15f); vertex(0.65f, 0.35f); endShape(); break; } beginShape(); vertex(-0.65f, 0.35f); vertex(0.65f, 0.35f); vertex(0.5625f, 0.55f); vertex(0, 0.8f); vertex(-0.5625f, 0.55f); endShape(CLOSE); popMatrix(); } else if ( emote == HOPEFUL ) { triangle(-0.5625, 0.55, 0.5626, 0.55, 0, 0.8f); } else { // mouth quad(-0.65f, 0.55f, -0.5625f, 0.35f, 0.5625f, 0.35f, 0.65f, 0.55f); // teeth beginShape(); vertex(-0.60625f, 0.45f); vertex(-0.4f, 0.45f); vertex(-0.36f, 0.35f); vertex(-0.33f, 0.45f); vertex(0.33f, 0.45f); vertex(0.36f, 0.35f); vertex(0.4f, 0.45f); vertex(0.60625f, 0.45f); endShape(); } } }