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; int points; color c; private int emote; private Particle p; private Spring s; private int attachCounter, caughtCounter, deathCounter, awareRadius; private float baseScale, scaleAmt, weight; private float faceOffX, faceOffY; Pulser(int t) { type = t; switch(t) { case KICK: scaleAmt = baseScale = kickRadius; c = kickColor; weight = kickWeight; break; case SNARE: scaleAmt = baseScale = snareRadius; c = snareColor; weight = snareWeight; break; case HAT: scaleAmt = baseScale = hatRadius; c = hatColor; weight = hatWeight; break; } p = phys.makeParticle(weight, random(width), random(height), 0); s = phys.makeSpring(pl.p, p, suckStrength, 1, pl.radius + baseScale + 5); s.turnOff(); faceOffX = faceOffY = attachCounter = caughtCounter = deathCounter = 0; emote = NEUTRAL; pulsing = rendered = targeted = caught = dying = false; points = 0; awareRadius = 200; } // interaction void interact(Player pl) { if ( puls.awareOf(pl) ) { look(pl.x(), pl.y()); if ( s.isOff() ) setMood(HOPEFUL); } // collision float actualDist = dist(x(), y(), pl.x(), pl.y()); float allowDist = pl.radius + scaleAmt + 5; float cx = x() - pl.x(); float cy = y() - pl.y(); if ( actualDist < allowDist ) { float diffDist = allowDist - actualDist; p.moveTo(x() + (cx*diffDist/actualDist), y() + (cy*diffDist/actualDist), 0); } } void interact(Deathskull sk) { // looking and attacking if ( awareOf(sk) ) { look(sk.x(), sk.y()); setMood(AFRAID); if ( s.isOff() && emote == VORACIOUS ) { p.setVelocity((sk.x() - x())/4, (sk.y() - y())/4, 0); } } // collision: only damage if *not* attached to player if ( dist(x(), y(), sk.x(), sk.y()) < scaleAmt + 30 ) { if ( s.isOff() ) { float dam = ( abs(vx()) + abs(vy()) - 10 ); float mult = ( emote == VORACIOUS ? 2 : 1 ); sk.damage(dam*mult); } int force = pulsing ? 1 : 2; float cx = x() - skull.x(); float cy = y() - skull.y(); p.setVelocity(cx/force, cy/force, 0); } } void interact(SuckBlow sb) { // 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((sb.x() - x())/4, (sb.y() - y())/4, 0); } } // collision: only damage if *not* attached to player if ( dist(x(), y(), sb.x(), sb.y()) < scaleAmt + 30 ) { if ( s.isOff() ) { float dam = ( abs(vx()) + abs(vy()) - 10 ); float mult = ( emote == VORACIOUS ? 2 : 1 ); sb.damage(dam*mult); } int force = pulsing ? 1 : 2; float cx = x() - sb.x(); float cy = y() - sb.y(); p.setVelocity(cx/force, cy/force, 0); } } // update methods void look(float mx, float my) { if ( caught ) { mx = x(); my = y(); } float x = x(); float y = y(); float hypo = dist(x, y, mx, my); float high = dist(x, y, x, my); float wide = dist(x, my, mx, my); if ( hypo == 0 ) faceOffX = faceOffY = 0; else { faceOffX = ( mx > x ? (0.3)*wide/hypo : -(0.3)*wide/hypo); faceOffY = ( my > y ? (0.3)*high/hypo : -(0.4)*high/hypo); } } 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(); attachCounter++; } void detach() { s.turnOff(); attachCounter = 0; } void kill() { dying = true; p.makeFixed(); } void deathTick() { deathCounter++; if ( deathCounter/frameRate > 0.5f ) { dying = false; p.kill(); } } void wallBounce() { int force = pulsing ? 8 : 2; if ( x() < scaleAmt ) p.setVelocity(force, vy(), 0); if ( x() > width - scaleAmt ) p.setVelocity(-force, vy(), 0); if ( y() < scaleAmt ) p.setVelocity(vx(), force, 0); if ( y() > height - scaleAmt ) p.setVelocity(vx(), -force, 0); } void pulseBounce(Pulser puls) { if ( dist(x(), y(), puls.x(), puls.y()) < scaleAmt + puls.radius() + 5) { int force = puls.pulsing ? 2 : 3; float cx = x() - puls.x(); float cy = y() - puls.y(); p.setVelocity(cx/force, cy/force, 0); } } void setVelocity(float x, float y) { p.setVelocity(x, y, 0); } void addVelocity(float x, float y) { p.addVelocity(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(SuckBlow sb) { boolean d = dist(x(), y(), sb.x(), sb.y()) < awareRadius; return ( d && emote == VORACIOUS && !caught ) || ( d && sb.isSucking() && !caught ); } boolean isAttached() { return s.isOn(); } float attachedFor() { return attachCounter / frameRate; } boolean isMoving() { return (abs(vx()) > 0.05f || abs(vy()) > 0.05f); } boolean isOffScreen() { if ( !dying && ( x() < -scaleAmt || x() > width + scaleAmt || y() < -scaleAmt || y() > height + scaleAmt ) ) return true; return false; } boolean isDead() { return p.isDead(); } float radius() { return scaleAmt; } 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() { if ( screenStats ) { fill(255, 0, 0); if ( targeted ) text("T", x(), y()); } float glow = glowIntensity; // draw the avatar noFill(); stroke(c); pushMatrix(); translate(x(), y()); if ( dying ) { fill(c); text(points, 0, 0); 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); sept(1); pushMatrix(); translate(faceOffX, faceOffY); leftEye(); rightEye(); mouth(); popMatrix(); if ( drawGlow ) { // inner glow pushMatrix(); for (int i = 1; i < 7; i++) { stroke(red(c), green(c), blue(c), glow/i); scale(0.98); sept(1); pushMatrix(); translate(faceOffX, faceOffY); leftEye(); rightEye(); mouth(); popMatrix(); } popMatrix(); //outer glow pushMatrix(); for (int i = 1; i < 7; i++) { stroke(red(c), green(c), blue(c), glow/i); scale(1.02); sept(1); pushMatrix(); translate(faceOffX, faceOffY); leftEye(); rightEye(); mouth(); popMatrix(); } 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 = faceOffX > 0 ? -0.723947474f * faceOffX + 1 : 0.723947474f * 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 = 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 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(); } } }