diff --git a/.DS_Store b/.DS_Store index 7cdfdf7..aa4a2e5 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Examples/.DS_Store b/Examples/.DS_Store new file mode 100644 index 0000000..6f18c9c Binary files /dev/null and b/Examples/.DS_Store differ diff --git a/StudentFolders/A1/29kingstont/side_scroller/Body.pde b/StudentFolders/A1/29kingstont/side_scroller/Body.pde new file mode 100644 index 0000000..93a8edd --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Body.pde @@ -0,0 +1,40 @@ +public abstract class Body { + protected PVector pos; + + // protected PVector vel, acc; + protected float mass; + + Body(PVector pos, float mass) { + this.pos = pos; + + // this.vel = new PVector(); + // this.acc = new PVector(); + this.mass = mass; + } + + public PVector getPos() { + return this.pos; + } + public abstract PVector getBottom(); + public abstract PVector getTop(); + public abstract PVector getCenter(); + public final PVector getAppropriatePos(boolean isUpsideDown) { + return isUpsideDown ? getBottom() : getTop(); + } + public float getMass() { + return this.mass; + } + + public void setPos(PVector pos) { + this.pos = pos; + } + public abstract void setBottom(PVector p); + public abstract void setTop(PVector p); + public abstract void setCenter(PVector p); + + public abstract void resolveAllCollisions(Entity entity, Terrain terr, ArrayList platforms, boolean isUpsideDown); + public abstract boolean isGrounded(Terrain terr, boolean isUpsideDown); + public abstract boolean isGrounded(Terrain terr, ArrayList platforms, boolean isUpsideDown); + + public abstract boolean isWithin(float minX, float maxX); +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/BoringZombie.pde b/StudentFolders/A1/29kingstont/side_scroller/BoringZombie.pde new file mode 100644 index 0000000..9e9f945 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/BoringZombie.pde @@ -0,0 +1,56 @@ +public class BoringZombie extends Enemy { + protected Weapon weapon; + + BoringZombie(PVector pos, boolean isUpsideDown) { + super(new RectBody(pos, 40, 80, 1), 0.5, 5, 1000, 100, 2, isUpsideDown); + + this.weapon = new Knife(0, this.damage, 30, 30); // cooldown 0 because managed by Enemy class + } + + BoringZombie(PVector pos, int movementSpeed, float damage, int cooldown, int maxHealth, int sacrificialHealth, boolean isUpsideDown) { + super(new RectBody(pos, 40, 80, 1), movementSpeed, damage, cooldown, maxHealth, sacrificialHealth, isUpsideDown); + + this.weapon = new Knife(0, this.damage, 30, 30); // cooldown 0 because managed by Enemy class + } + + @Override + protected void move(Meeple meeple) { + float diff = meeple.getPos().x - this.getPos().x; + + if (diff > 0) { + if (abs(diff) >= 20) this.getPos().x += movementSpeed; + this.setIsFacingRight(true); + } else if (diff < 0) { + if (abs(diff) >= 20) this.getPos().x -= movementSpeed; + this.setIsFacingRight(false); + } + } + + @Override + protected boolean attackConditionSatisfied(Meeple meeple) { + return !this.weapon.getTargetsInRange(this, meeple).isEmpty(); + } + + @Override + protected void attack(World world) { + Meeple meeple = world.getMeeple(); + if (meeple == null) return; + + weapon.useAction(this, meeple); + } + + @Override + public void display() { + stroke(0); + strokeWeight(1); + fill(RED); + + PVector pos = this.getPos(); + RectBody body = this.getBody(); + rect(pos.x, pos.y, body.getW(), body.getH()); + + this.weapon.handheldDisplay(this); + + displayHp(this); + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Bullet.pde b/StudentFolders/A1/29kingstont/side_scroller/Bullet.pde new file mode 100644 index 0000000..1fb9853 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Bullet.pde @@ -0,0 +1,53 @@ +public class Bullet extends Entity { + private ArrayList targets; + private float damage; + + private int lifetime; + private int startOfLife; + + Bullet(PVector pos, PVector vel, PVector acc, float damage, ArrayList targets) { + super(new RectBody(pos, 10, 5, 0)); // 10, 5 + + this.damage = damage; + this.targets = targets; + this.vel = vel; + this.acc = acc; + + this.startOfLife = millis(); + this.lifetime = 10*1000; + } + + @Override + public void update(World world) { + super.update(world); + this.attack(); + + if (millis()-this.startOfLife >= this.lifetime) this.die(); + } + + private void attack() { + for (Entity e : targets) { + if (!(e instanceof HasHealth) || e.getIsDead()) continue; + HasHealth damageable = (HasHealth) e; + + if (e.getBody() instanceof RectBody) { + if (Collision.check((RectBody) e.getBody(), this.getBody()).collided) { + damageable.damage(this.damage); + this.die(); + } + } else if (e.getBody() instanceof CircleBody) { + if (Collision.check((CircleBody) e.getBody(), this.getBody()).collided) { + damageable.damage(this.damage); + this.die(); + } + } + } + } + + @Override + public void display() { + fill(255, 0, 0); + noStroke(); + rect(this.getPos().x, this.getPos().y, this.getBody().getW(), this.getBody().getH()); + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Bush.pde b/StudentFolders/A1/29kingstont/side_scroller/Bush.pde new file mode 100644 index 0000000..9912ebd --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Bush.pde @@ -0,0 +1,12 @@ +class Bush extends Static { + Bush(PVector pos, int w, int h) { + super(pos, w, h); + } + + public void display() { + fill(8, 107, 57, 255); + stroke(0); + strokeWeight(1); + ellipse(pos.x, pos.y, w, h); + } +} diff --git a/StudentFolders/A1/29kingstont/side_scroller/CircleBody.pde b/StudentFolders/A1/29kingstont/side_scroller/CircleBody.pde new file mode 100644 index 0000000..a111975 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/CircleBody.pde @@ -0,0 +1,76 @@ +public class CircleBody extends Body { + float r; + + CircleBody(PVector pos, float r) { + super(pos, 0); + this.r = r; + } + CircleBody(PVector pos, float r, float mass) { + super(pos, mass); + this.r = r; + } + + @Override + public PVector getTop() { + return PVector.add(this.pos, new PVector(0, r)); + } + @Override + public PVector getBottom() { + return PVector.sub(this.pos, new PVector(0, r)); + } + @Override + public PVector getCenter() { + return this.pos; + } + public float getR() { + return this.r; + } + + @Override + public void setTop(PVector p) { + this.pos = p.add(0, r); + } + @Override + public void setBottom(PVector p) { + this.pos = p.sub(0, r); + } + @Override + public void setCenter(PVector p) { + this.pos = p; + } + + @Override + public void resolveAllCollisions(Entity entity, Terrain rightsideUpTerr, ArrayList platforms, boolean isUpsideDown) { + if (isGrounded(rightsideUpTerr, platforms, isUpsideDown)) { + float bestY = rightsideUpTerr.getHeightAt(this.pos.x)-this.r; + + entity.vel.y = 0; + this.pos.y = bestY; + } + } + + @Override + public boolean isGrounded(Terrain rightsideUpTerr, boolean isUpsideDown) { + float terrH = rightsideUpTerr.getHeightAt(this.pos.x); + return this.pos.y+this.r >= terrH; + } + + @Override + public boolean isGrounded(Terrain rightsideUpTerr, ArrayList platforms, boolean isUpsideDown) { + float terrH = rightsideUpTerr.getHeightAt(this.pos.x); + boolean isOnFloor = this.pos.y+this.r >= terrH; + boolean isOnPlatform = false; + // for (Platform p : platforms) { + // if (p.intersects(this)) { + // isOnPlatform = true; + // break; + // } + // } + return isOnFloor || isOnPlatform; + } + + @Override + public boolean isWithin(float minX, float maxX) { + return pos.x+r >= minX && pos.x-r <= maxX; + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Collision.pde b/StudentFolders/A1/29kingstont/side_scroller/Collision.pde new file mode 100644 index 0000000..18882aa --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Collision.pde @@ -0,0 +1,95 @@ +public static class CollisionResult { + public boolean collided; + public String side; + public float penetration; + + public CollisionResult(boolean collided, String side, float penetration) { + this.collided = collided; + this.side = side; + this.penetration = penetration; + } + + public static CollisionResult none() { + return new CollisionResult(false, "", 0); + } +} + +public static class Collision { + public static CollisionResult check(RectBody a, RectBody b) { + float overlapX = min(a.getX() + a.getW() - b.getX(), b.getX() + b.getW() - a.getX()); + float overlapY = min(a.getY() + a.getH() - b.getY(), b.getY() + b.getH() - a.getY()); + + if (overlapX > 0 && overlapY > 0) { + if (overlapX < overlapY) { + return new CollisionResult( + true, + (a.getX() < b.getX() ? "left" : "right"), + overlapX + ); + } else { + return new CollisionResult( + true, + (a.getY() < b.getY() ? "top" : "bottom"), + overlapY + ); + } + } + + return CollisionResult.none(); + } + + + + // ChatGPTed because this is literally never used. Idk why I don't just remove CircleBody -_- + public static CollisionResult check(CircleBody circle, RectBody rect) { + float cx = circle.getPos().x; + float cy = circle.getPos().x; + float r = circle.getR(); + + float rx = rect.getX(); + float ry = rect.getY(); + float rw = rect.getW(); + float rh = rect.getH(); + + // 1. Find closest point on rectangle to circle center + float closestX = clamp(cx, rx, rx + rw); + float closestY = clamp(cy, ry, ry + rh); + + // 2. Distance from circle to closest point + float dx = cx - closestX; + float dy = cy - closestY; + + float distSq = dx * dx + dy * dy; + + if (distSq > r * r) { + return CollisionResult.none(); // No collision + } + + // 3. Determine penetration along X/Y + float distance = (float)Math.sqrt(distSq); + float penetration = r - distance; + + // Normalize direction + float nx = dx / distance; + float ny = dy / distance; + + // Choose axis of strongest push + if (Math.abs(nx) > Math.abs(ny)) { + return new CollisionResult( + true, + nx < 0 ? "left" : "right", + Math.abs(penetration * nx) + ); + } else { + return new CollisionResult( + true, + ny < 0 ? "top" : "bottom", + Math.abs(penetration * ny) + ); + } + } + + private static float clamp(float value, float min, float max) { + return Math.max(min, Math.min(max, value)); + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Constants.pde b/StudentFolders/A1/29kingstont/side_scroller/Constants.pde new file mode 100644 index 0000000..eb9ec3e --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Constants.pde @@ -0,0 +1,21 @@ +public static class Constants { + static final int MAX_LAYER = 4; + static final int MAX_ITEMS = 5; // must be less than 9 + static int CHUNK_W; // Will be set to half of width + static final int RENDER_CHUNK_RADIUS = 0; // max # chunks from the current on-screen chunks that will be rendered + static final int LIVE_CHUNK_RADIUS = 2; // max # chunks away from the current on-screen chunks that will be updated + static final boolean MINIMIZE_GRAPHICS = false; // minimize details + + static final float TRAMPOLINE_SPAWN_CHANCE = 0.25; // [0, 1] + static final int TRAMPOLINE_W = 100; + static final int TRAMPOLINE_H = 20; + + static final float FLAG_SPAWN_CHANCE = 1; + + static final int BLOCK_UNIT = 40; + + // Debugging + static final boolean SPAWN_ENEMIES = true; // default: true + static final boolean CONSTANT_DAYTIME = false; // default: false + static final boolean ALLOW_FLYING = false; // default: false +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Dirt.pde b/StudentFolders/A1/29kingstont/side_scroller/Dirt.pde new file mode 100644 index 0000000..406174e --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Dirt.pde @@ -0,0 +1,13 @@ +public class Dirt extends Particle { + Dirt(PVector p, PVector vel, PVector acc) { + super(p, vel, acc, 15); + } + + void display() { + stroke(0, this.lifespan); + + float alpha = map(this.lifespan, 0, this.maxLifespan, 0, 255); + fill(PLATFORM_BLUE, alpha); + circle(pos.x, pos.y, 8); + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Displayable.pde b/StudentFolders/A1/29kingstont/side_scroller/Displayable.pde new file mode 100644 index 0000000..2b14dd1 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Displayable.pde @@ -0,0 +1,21 @@ +// public abstract class Displayable { +// protected PVector pos; + +// Displayable(PVector pos) { +// this.pos = pos; +// } + +// public PVector getPos() { +// return pos; +// } +// public void setPos(PVector pos) { +// this.pos = pos; +// } + +// // protected float getBrightness(float dayPercentage) { +// // if (layer <= 2) return map(1-dayPercentage, 0, 1, 0.8, 0.15); +// // return map(1-dayPercentage, 0, 1, 0.33, 0.078); +// // } + +// public abstract void display(); +// } \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Enemy.pde b/StudentFolders/A1/29kingstont/side_scroller/Enemy.pde new file mode 100644 index 0000000..fb113da --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Enemy.pde @@ -0,0 +1,82 @@ +public abstract class Enemy extends Entity implements HasHealth { + private float health; + private float maxHealth; + + protected float movementSpeed; // px + private float sacrificialHealth; + + private int cooldown; // ms + protected float damage; + private Integer lastAttacked; + + Enemy(T body, float movementSpeed, float damage, int cooldown, int maxHealth, int sacrificialHealth, boolean isUpsideDown) { + super(body, isUpsideDown); + + this.maxHealth = maxHealth; + this.health = maxHealth; + this.sacrificialHealth = sacrificialHealth; + + this.movementSpeed = movementSpeed; + + this.damage = damage; + this.cooldown = cooldown; + } + + public float getHealth() { + return this.health; + } + public float getMaxHealth() { + return this.maxHealth; + } + public float getSacrificialHealth() { + return this.sacrificialHealth; + } + public void damage(float h) { + this.health -= min(this.health, h); + if (this.health == 0) this.isDead = true; + } + public void heal(float h) { + this.health += min(this.maxHealth-this.health, h); + } + + public void setHealth(float h) { + this.health = h; + } + public void setMovementSpeed(float m) { + this.movementSpeed = m; + } + + protected abstract void move(Meeple meeple); + + protected boolean cooldownSatisfied() { + if (lastAttacked == null) return true; + return millis() - lastAttacked >= cooldown; + } + protected void resetCooldown() { + this.lastAttacked = null; + } + + protected abstract boolean attackConditionSatisfied(Meeple meeple); + protected abstract void attack(World world); + + @Override + public void update(World world) { + super.update(world); + + Meeple meeple = world.getMeeple(); + + if (meeple != null) { + move(meeple); + + if (attackConditionSatisfied(meeple)) { + if (cooldownSatisfied()) { + attack(world); + lastAttacked = millis(); + } + } else { + resetCooldown(); + } + } + } + public abstract void display(); +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Entity.pde b/StudentFolders/A1/29kingstont/side_scroller/Entity.pde new file mode 100644 index 0000000..77bcc73 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Entity.pde @@ -0,0 +1,137 @@ +static int entityCount = 0; + +public abstract class Entity implements Displayable, Killable { + public int id; + protected T body; + protected PVector prevPos; + protected PVector vel, acc; + protected boolean isFacingRight = true; + + protected boolean isDead = false; + protected boolean isSuicide = false; + protected boolean isUpsideDown; + protected boolean getIsFlipping; + + Entity(T body) { + this.id = entityCount; + this.body = body; + + this.prevPos = body.getPos().copy(); + this.vel = new PVector(); + this.acc = new PVector(); + + this.isUpsideDown = false; + + entityCount++; + } + + Entity(T body, boolean isUpsideDown) { + this(body); + this.isUpsideDown = isUpsideDown; + } + + public T getBody() { + return this.body; + } + public PVector getPrevPos() { + return this.prevPos; + } + public PVector getPos() { + return this.body.getPos(); + } + public PVector getCenter() { + return this.body.getCenter(); + } + public PVector getTop() { + return this.body.getTop(); + } + public PVector getBottom() { + return this.body.getBottom(); + } + public PVector getVel() { + return this.vel; + } + public PVector getAcc() { + return this.acc; + } + public float getMass() { + return this.body.getMass(); + } + public boolean getIsFacingRight() { + return this.isFacingRight; + } + public int getDirection() { + return this.isUpsideDown ? -1 : 1; + } + public boolean getIsUpsideDown() { + return this.isUpsideDown; + } + public boolean getIsFlipping() { + return this.getIsFlipping; + } + public boolean getIsDead() { + return this.isDead; + } + public boolean getIsSuicide() { + return this.isSuicide; + } + + + public void setPos(PVector pos) { + this.body.setPos(pos); + } + public void setVel(PVector vel) { + this.vel = vel; + } + public void setAcc(PVector acc) { + this.acc = acc; + } + public void stop() { + this.vel.mult(0); + this.acc.mult(0); + } + public void setIsUpsideDown(boolean b) { + if (this.isUpsideDown != b) this.flip(); + } + public void setgetIsFlipping(boolean b) { + this.getIsFlipping = b; + } + public void setIsFacingRight(boolean b) { + this.isFacingRight = b; + } + public void flip() { + if (this.getIsFlipping && !Constants.ALLOW_FLYING) return; + + this.isUpsideDown = !this.isUpsideDown; + this.getIsFlipping = true; + } + public void die() { + this.isDead = true; + this.isSuicide = true; + } + + public void applyForce(PVector F) { + if (body.mass == 0) return; + acc.add(PVector.div(F, body.mass)); + } + + public void update(World world) { + PVector grav = PVector.mult(GRAVITY, getDirection()); + + applyForce(grav); + prevPos = body.getPos().copy(); + + vel.add(PVector.mult(this.acc, dt())); + body.getPos().add(PVector.mult(this.vel, dt())); + acc.mult(0); + + // Check for collisions + Terrain terr = isUpsideDown ? world.upsideDownTerr : world.rightsideUpTerr; + body.resolveAllCollisions(this, terr, world.platforms, isUpsideDown); + + if (prevPos.x != body.getPos().x) { + isFacingRight = prevPos.x < body.getPos().x; + } + } + public abstract void display(); +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Environment.pde b/StudentFolders/A1/29kingstont/side_scroller/Environment.pde new file mode 100644 index 0000000..ee33075 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Environment.pde @@ -0,0 +1,98 @@ +public class Environment { + private final color c = color(0, 63, 101); + private float ticks; + private float dayLen; + + private boolean isRaining; + + private final float MIN_DARKNESS = 50; + + private PGraphics lightMask; + private PGraphics darknessFilter; + + Environment() { + ticks = 0; + dayLen = pow(2, 12); + + isRaining = true; + + lightMask = createGraphics(width, height); + darknessFilter = createGraphics(width, height); + } + + public void update() { + lightMask.beginDraw(); + float alpha = map(getDayPercentage(), 0, 1, 255-MIN_DARKNESS, 0); // inverse the alpha because it's applied to a darkness layer + lightMask.background(alpha); + lightMask.endDraw(); + + ticks += 1; + } + + public float getTimeToday() { + return ticks % dayLen; + } + + // [0, 1) + public float getDayPercentage() { + if (Constants.CONSTANT_DAYTIME) return 1; + + float todayTime = ticks % dayLen; + // q1 - Night + // q2 - Progress to day + // q3 - Day + // q4 - Progress to night + + float nightEnd = dayLen * 1/4.0; + float nightToDayEnd = dayLen * 2/4.0; + float dayEnd = dayLen * 3/4.0; + float dayToNightEnd = dayLen * 4/4.0; + + if (todayTime < nightEnd) return 0; + else if (todayTime < nightToDayEnd) return map(todayTime, nightEnd, nightToDayEnd, 0, 1); + else if (todayTime < dayEnd) return 1; + + return map(todayTime, dayEnd, dayToNightEnd, 1, 0); + } + + void glow(RectBody b, int r, PVector offset) { + PVector c = PVector.sub(b.getCenter(), offset); + float fadeStartR = 0; + + float alpha = map(getDayPercentage(), 0, 1, 255-MIN_DARKNESS, 0); + + lightMask.beginDraw(); + lightMask.noStroke(); + + int increment = 5; + if (Constants.MINIMIZE_GRAPHICS) increment = 7; + + for (int i=r; i>=0; i-=increment) { + if (r <= fadeStartR) { + lightMask.fill(0); + } else { + lightMask.fill(map(i, fadeStartR, r, 0, alpha)); + } + lightMask.circle(c.x, c.y, i*2); + } + + lightMask.endDraw(); + } + + public void displayBackground(PVector offset) { + float daypercentage = getDayPercentage(); + fill(0, map(daypercentage, 0, 1, 12.6, 63), map(daypercentage, 0, 1, 20.2, 101)); + noStroke(); + rect(offset.x, offset.y, width, height); + } + + public void displayFilter(PVector offset) { + darknessFilter.beginDraw(); + darknessFilter.background(0); + darknessFilter.endDraw(); + + darknessFilter.mask(lightMask); + + image(darknessFilter, offset.x, offset.y); + } +} diff --git a/StudentFolders/A1/29kingstont/side_scroller/Flag.pde b/StudentFolders/A1/29kingstont/side_scroller/Flag.pde new file mode 100644 index 0000000..1a11607 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Flag.pde @@ -0,0 +1,34 @@ +public class Flag extends Item { + private final int w = 50; + private final int h = 50; + Flag() { + super(true); + } + + @Override + public void useAction(Entity user, World world) { + throw new Error("Flag doesn't have use() yet"); + } + + @Override + public void handheldDisplay(Entity user) { + // throw new Error("Flag doesn't have handheld display yet"); + } + + @Override + public void iconDisplay(PVector pos) { + fill(255); + noStroke(); + rect(pos.x, pos.y, Constants.BLOCK_UNIT, Constants.BLOCK_UNIT); + + fill(0); + textSize(16); + textAlign(CENTER, CENTER); + text("flag", pos.x+25, pos.y+25); + } + + @Override + public boolean equals(Object that) { + return that instanceof Flag; // there are no flag variants + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Item.pde b/StudentFolders/A1/29kingstont/side_scroller/Item.pde new file mode 100644 index 0000000..e5a076d --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Item.pde @@ -0,0 +1,41 @@ +public abstract class Item implements Killable { + private boolean isStackable; + private int durability; + private boolean isDead = false; + + Item(boolean isStackable) { + this.isStackable = isStackable; + this.durability = 1000; + } + Item(boolean isStackable, int durability) { + this.isStackable = isStackable; + this.durability = durability; + } + + public boolean getIsDead() { + return this.isDead; + } + + public boolean getIsStackable() { + return this.isStackable; + } + + // Some items might need to update every tick + public void update() {} + + public void use(Entity user, World world) { + durability -= 1; + + if (durability <= 0) isDead = true; + + useAction(user, world); + }; + public abstract void useAction(Entity user, World world); + public abstract void handheldDisplay(Entity user); + // public abstract void entityDisplay(PVector pos); // FIXME: SHOULDN'T BE HERE- SHOULD BE IN SEPARATE ITEM ENTITY CLASS + // THAT WAY IT CAN MANAGE ITS OWN POSITION, VELOCITY, AND ACCELERATION + public abstract void iconDisplay(PVector pos); + + @Override + public abstract boolean equals(Object that); +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/ItemEntity.pde b/StudentFolders/A1/29kingstont/side_scroller/ItemEntity.pde new file mode 100644 index 0000000..4f54547 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/ItemEntity.pde @@ -0,0 +1,21 @@ +public class ItemEntity extends Entity implements Displayable { + Item item; + ItemEntity(PVector pos, Item item) { + super(new RectBody(pos, Constants.BLOCK_UNIT, Constants.BLOCK_UNIT, 1)); + this.item = item; + } + ItemEntity(PVector pos, Item item, boolean isUpsideDown) { + super(new RectBody(pos, Constants.BLOCK_UNIT, Constants.BLOCK_UNIT, 1), isUpsideDown); + this.item = item; + } + + public Item collect() { + die(); + return this.item; + } + + @Override + public void display() { + item.iconDisplay(getPos()); + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Knife.pde b/StudentFolders/A1/29kingstont/side_scroller/Knife.pde new file mode 100644 index 0000000..d00fbcc --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Knife.pde @@ -0,0 +1,111 @@ +public class Knife extends Weapon { + private int hitboxW; + private int hitboxH; + + Knife() { + super(400, 30); + this.hitboxW = 50; + this.hitboxH = 30; + } + Knife(int cooldown, float damage, int w, int h) { + super(cooldown, damage); + this.hitboxW = w; + this.hitboxH = h; + } + + @Override + protected boolean conditionSatisfied() { return true; } + + @Override + protected void attack(Entity user, ArrayList targets, World world) { + if (!(user.getBody() instanceof RectBody)) throw new Error("I'm too lazy to make this work for circle bodies so this will only work for rectangles!!!"); + + Entity rectUser = (Entity) user; + + PVector hitboxPos = rectUser.getPos().copy(); + hitboxPos.add(this.getOffset(user)); + RectBody hitbox = new RectBody(hitboxPos, hitboxW, hitboxH, 0); + + ArrayList targetsInRange = getTargetsInRange(user, targets); + + for (Entity e : targetsInRange) { + if (e instanceof HasHealth) { + HasHealth hpEntity = (HasHealth) e; + hpEntity.damage(this.damage); + } + } + } + + @Override + protected ArrayList getTargetsInRange(Entity user, ArrayList targets) { + if (!(user.getBody() instanceof RectBody)) throw new Error("I'm too lazy to make this work for circle bodies so this will only work for rectangles!!!"); + + Entity rectUser = (Entity) user; + + PVector hitboxPos = rectUser.getPos().copy(); + hitboxPos.add(this.getOffset(user)); + RectBody hitbox = new RectBody(hitboxPos, hitboxW, hitboxH, 0); + + ArrayList targetsInRange = new ArrayList<>(); + + for (Entity e : targets) { + if (e.getIsDead()) continue; + + if (e.getBody() instanceof RectBody) { + if (Collision.check((RectBody) e.getBody(), hitbox).collided || Collision.check((RectBody) e.getBody(), (RectBody) user.getBody()).collided) { + targetsInRange.add(e); + } + } else if (e.getBody() instanceof CircleBody) { + if (Collision.check((CircleBody) e.getBody(), hitbox).collided || Collision.check((RectBody) e.getBody(), (RectBody) user.getBody()).collided) { + targetsInRange.add(e); + } + } + } + + return targetsInRange; + } + + @Override + protected PVector getOffset(Entity user) { + if (!(user.getBody() instanceof RectBody)) throw new Error("I'm too lazy to make this work for circle bodies so this will only work for Meeple!!!"); + + Entity rectUser = (Entity) user; + RectBody rectUserBody = rectUser.getBody(); + + PVector offset = new PVector(); + if (user.getIsFacingRight()) { + offset.x = rectUserBody.getW(); + offset.y = rectUserBody.getH()/2-hitboxH/2; + } else { + offset.x = -hitboxW; + offset.y = rectUserBody.getH()/2-hitboxH/2; + } + + return offset; + } + + @Override + public void handheldDisplay(Entity user) { + if (!this.isInUse) fill(255, 80); + else fill(255, 0, 0); + + PVector offset = this.getOffset(user); + rect(user.getPos().x+offset.x, user.getPos().y+offset.y, hitboxW, hitboxH); + + this.isInUse = false; + } + + @Override + public void iconDisplay(PVector pos) { + fill(255); + noStroke(); + rect(pos.x, pos.y, Constants.BLOCK_UNIT, Constants.BLOCK_UNIT); + + fill(0); + textSize(16); + textAlign(CENTER, CENTER); + text("knife", pos.x+25, pos.y+25); + + // throw new Error("Knife doesn't have entity display yet"); + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Layer.pde b/StudentFolders/A1/29kingstont/side_scroller/Layer.pde new file mode 100644 index 0000000..bab2401 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Layer.pde @@ -0,0 +1,84 @@ +public class Layer { + ArrayList items; + ArrayList platforms; + ArrayList entities; + ArrayList particleSystems; + + Layer() { + items = new ArrayList<>(); + platforms = new ArrayList<>(); + entities = new ArrayList<>(); + particleSystems = new ArrayList<>(); + } + + int getNumItems() { + return items.size() + entities.size() + particleSystems.size(); + } + + void register(Static d) { + items.add(d); + } + void register(Platform p) { + platforms.add(p); + } + void register(Entity d) { + entities.add(d); + } + void register(ParticleSystem d) { + particleSystems.add(d); + } + + public void deregisterEntity(int id) { + entities.removeIf(e -> e.id == id); + } + + void applyForce(PVector F) { + for (Entity e : entities) { + e.applyForce(F); + } + } + + + void cleanup() { + for (int i = entities.size() - 1; i >= 0; i--) { + if (entities.get(i).getIsDead()) { + entities.remove(i); + } + } + } + + void update(World world, float minX, float maxX) { + cleanup(); + + for (int i = entities.size()-1; i>=0; i--) { + Entity e = entities.get(i); + if (e.getBody().isWithin(minX, maxX)) e.update(world); + // else println("SKIPPED update for entity " + e.toString()); + } + for (ParticleSystem d : particleSystems) { + d.update(); + } + } + + void display(float minX, float maxX) { + for (Static d : items) { + if (d.getPos().x >= minX && d.getPos().x <= maxX) { + d.display(); + } + } + + for (ParticleSystem ps : particleSystems) { + ps.display(); + } + + for (Platform p : platforms) { + if (p.getBody().isWithin(minX, maxX)) p.display(); + // else println("SKIPPED display for platform " + p.toString()); + } + + for (Entity e : entities) { + if (e.getBody().isWithin(minX, maxX)) e.display(); + // else println("SKIPPED display for entity " + e.toString()); + } + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/LongHealthBar.pde b/StudentFolders/A1/29kingstont/side_scroller/LongHealthBar.pde new file mode 100644 index 0000000..2a774d1 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/LongHealthBar.pde @@ -0,0 +1,90 @@ +public class LongHealthBar implements Displayable { + private PVector pos; + + private float barW = 300; + private float barH = 10; + + private float actualHealth; + private float maxHealth; + + // animation + private float displayHealth; + private float startHealth; + private float targetHealth; + + private float animStart; + private float animDuration = 200; + + LongHealthBar(PVector pos, float maxHealth) { + this.pos = pos; + + this.maxHealth = maxHealth; + this.actualHealth = this.maxHealth; + this.displayHealth = this.actualHealth; + + this.startHealth = this.displayHealth; + this.targetHealth = this.startHealth; + + this.animStart = 0; + } + + void setHealth(float newHealth) { + if (this.actualHealth != newHealth) { + this.startHealth = displayHealth; + this.targetHealth = newHealth; + this.animStart = millis(); + + this.actualHealth = newHealth; + } + } + + void update() { + float timePassed = millis() - this.animStart; + float t = timePassed/this.animDuration; + t = constrain(t, 0, 1); + + this.displayHealth = lerp(this.startHealth, this.targetHealth, t); + } + + @Override + void display() { + display(color(255)); + } + + void display(color c) { + // Health bar + + float barX = this.pos.x; + float barY = this.pos.y; + + textSize(16); + fill(c); + textAlign(LEFT, TOP); + text("You", barX, barY-20); + + noStroke(); + fill(255, 50); + rect(barX, barY, barW, barH); + + fill(255); + textAlign(RIGHT, TOP); + textSize(16); + text(""+round(actualHealth), barX+barW, barY-20); + + fill(c); + float actualHealthW = map(actualHealth, 0, maxHealth, 0, barW); + rect(barX, barY, actualHealthW, barH); + + if (targetHealth - startHealth > 0) { // healing + fill(255); + float changeIndicatorMaxW = map(abs(targetHealth-startHealth), 0, maxHealth, 0, barW); + float changeIndicatorW = map(displayHealth, startHealth, targetHealth, changeIndicatorMaxW, 0); + rect(barX+actualHealthW-changeIndicatorW, barY, changeIndicatorW, barH); + } else { + fill(RED); + float changeIndicatorMaxW = map(abs(targetHealth-startHealth), 0, maxHealth, 0, barW); + float changeIndicatorW = map(displayHealth, startHealth, targetHealth, changeIndicatorMaxW, 0); + rect(barX+actualHealthW, barY, changeIndicatorW, barH); + } + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Meeple.pde b/StudentFolders/A1/29kingstont/side_scroller/Meeple.pde new file mode 100644 index 0000000..639702c --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Meeple.pde @@ -0,0 +1,169 @@ +public class Meeple extends Entity implements HasHealth { + private float health; + private float maxHealth; + + private int movementSpeed; // px + + private float jumpVel = 500; + private boolean didDoubleJump; + private int layer; + private boolean isHatOn; + + private Item currentlyHeld; + + Meeple(int layer) { + super(new RectBody(new PVector(500, 50), 40, 80, 1)); + this.maxHealth = 100; + this.health = this.maxHealth; + + this.didDoubleJump = false; + + this.movementSpeed = 200; + + this.layer = layer; + this.isHatOn = true; + this.currentlyHeld = null; + } + + public int getLayer() { + return this.layer; + } + public float getW() { + return this.getBody().getW(); + } + public float getH() { + return this.getBody().getH(); + } + public Item getCurrentlyHeldItem() { + return this.currentlyHeld; + } + public float getHealth() { + return this.health; + } + public float getSacrificialHealth() { + return 0; + } + public void damage(float h) { + this.health -= min(this.health, h); + if (this.health == 0) this.isDead = true; + } + public void heal(float h) { + this.health += min(this.maxHealth-this.health, h); + } + public float getMaxHealth() { + return this.maxHealth; + } + + public void setCurrentlyHeldItem(Item item) { + this.currentlyHeld = item; + } + public void setHealth(float h) { + this.health = h; + } + + // ACTIONS + public void move(boolean isRight) { + if (isRight) this.getPos().x += movementSpeed*dt(); + else this.getPos().x -= movementSpeed*dt(); + + this.setIsFacingRight(isRight); + } + public void jump(Terrain terr, ArrayList platforms) { + if (isGrounded(terr, platforms, isUpsideDown) || !this.didDoubleJump) { + float directionalJumpVel = -this.jumpVel * this.getDirection(); + this.setVel(new PVector(0, directionalJumpVel)); + + if (!isGrounded(terr, platforms, isUpsideDown)) this.didDoubleJump = true; + } + } + + public void attack(World world) { + if (currentlyHeld instanceof Weapon) { + Weapon weapon = (Weapon) currentlyHeld; + ArrayList targets = new ArrayList(); + targets.addAll(world.enemies); + + weapon.useAction(this, targets); + } + } + + + public boolean isGrounded(Terrain terr, boolean isUpsideDown) { + return this.body.isGrounded(terr, isUpsideDown); + } + public boolean isGrounded(Terrain terr, ArrayList platforms, boolean isUpsideDown) { + return this.body.isGrounded(terr, platforms, isUpsideDown); + } + + @Override + void update(World world) { + super.update(world); + logGeneralStats("Meeple Y Vel", vel.y); + + ArrayList itemEntities = world.itemEntities; + for (ItemEntity e : itemEntities) { + if (Collision.check(this.body, e.getBody()).collided) { + if (world.itemCapacityLeft() > 0) { + Item i = e.collect(); + world.addItem(i); + } + } + } + + if (isGrounded(world.getTerrainToUse(isUpsideDown), world.platforms, isUpsideDown)) didDoubleJump = false; + } + + @Override + // ChatGPTed below display code + void display() { + pushMatrix(); + + // origin → bottom‐center of character box + translate(getPos().x + getBody().getW()/2, + getPos().y + getBody().getH()); + + // --- sizes --- + float bodyW = this.getBody().getW(); + float bodyH = this.getBody().getH(); // total height (torso + head), no hat + + // head is same width as body, and square + float headSize = bodyW; + float torsoH = bodyH - headSize; + + // --- TORSO --- + noStroke(); + fill(40, 110, 200); + rect(-bodyW/2, -torsoH, bodyW, torsoH); + + // --- HEAD --- + fill(255, 220, 185); + float headY = -torsoH - headSize; + rect(-bodyW/2, headY, bodyW, headSize); + + noFill(); + stroke(0); + strokeWeight(1); + rect(-bodyW/2, headY, bodyW, bodyH); + + // --- TOP HAT (much smaller) --- + float hatTopY = headY; // top of head + float hatW = bodyW * 0.7; + float hatH = bodyW * 0.5; + + // brim (narrow + short) + fill(30); + rect(-hatW/2 - 2, hatTopY - 4, hatW + 4, 4); + + // cylinder (small block) + fill(20); + rect(-hatW/2, hatTopY - hatH - 4, hatW, hatH); + + // band (thin stripe) + fill(100); + rect(-hatW/2, hatTopY - hatH - 8, hatW, 4); + + popMatrix(); + + if (currentlyHeld != null) currentlyHeld.handheldDisplay(this); + } +} diff --git a/StudentFolders/A1/29kingstont/side_scroller/Minion.pde b/StudentFolders/A1/29kingstont/side_scroller/Minion.pde new file mode 100644 index 0000000..ca15d96 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Minion.pde @@ -0,0 +1,43 @@ +public class Minion extends Enemy { + Minion(PVector pos, boolean isUpsideDown) { + super(new RectBody(pos, 40, 40, 1), 3, 1, 500, 5, 0, isUpsideDown); + } + + @Override + protected void move(Meeple meeple) { + float diff = meeple.getPos().x - this.getPos().x; + if (abs(diff) < 20) return; + + if (diff > 0) { + this.getPos().x += movementSpeed; + } else if (diff < 0) { + this.getPos().x -= movementSpeed; + } + } + + @Override + protected boolean attackConditionSatisfied(Meeple meeple) { + return Collision.check(this.getBody(), meeple.getBody()).collided; + } + + @Override + protected void attack(World world) { + Meeple meeple = world.getMeeple(); + if (meeple == null) return; + + meeple.damage(damage); + } + + @Override + public void display() { + stroke(0); + strokeWeight(1); + fill(255, 0, 0); + + PVector pos = this.getPos(); + RectBody body = this.getBody(); + rect(pos.x, pos.y, body.getW(), body.getH()); + + displayHp(this); + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Particle.pde b/StudentFolders/A1/29kingstont/side_scroller/Particle.pde new file mode 100644 index 0000000..b5e2366 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Particle.pde @@ -0,0 +1,34 @@ +public abstract class Particle { + protected PVector pos; + protected PVector vel; + protected PVector acc; + protected float lifespan; + protected float maxLifespan; + + Particle(PVector p, PVector vel, PVector acc, int lifespan) { + pos = p; + this.acc = acc; + this.vel = vel; + + this.lifespan = lifespan; + this.maxLifespan = lifespan; + } + + void applyForce(PVector f) { + acc.add(f); + } + + void update() { + vel.add(acc); + pos.add(vel); + lifespan -= 1.0; + + acc.mult(0); + } + + boolean getIsDead() { + return lifespan < 0.0; + } + + abstract void display(); +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/ParticleSystem.pde b/StudentFolders/A1/29kingstont/side_scroller/ParticleSystem.pde new file mode 100644 index 0000000..2ba2782 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/ParticleSystem.pde @@ -0,0 +1,70 @@ +@FunctionalInterface +public interface ParticleSupplier { + T create(float x, float y, PVector vel, PVector acc); +} + +@FunctionalInterface +public interface ParticleVel { + PVector get(); +} + +@FunctionalInterface +public interface ParticleAcc { + PVector get(); +} + +public class ParticleSystem { + PVector pos; + Entity parent; + PVector offset; + + ArrayList particles; + ParticleSupplier factory; + + ParticleSystem(PVector pos, ParticleSupplier factory) { + this.pos = pos; + this.offset = new PVector(); + this.particles = new ArrayList<>(); + this.factory = factory; + } + + void attachTo(Entity e) { + this.parent = e; + this.pos = parent.getPos().copy(); + } + void attachTo(Entity e, PVector offset) { + this.parent = e; + this.offset = offset; + this.pos = parent.getPos().copy().add(offset); + } + void setOffset(PVector offset) { + this.offset = offset; + } + + void spawn(int N) { + for (int i=0; i=0; i--) { + particles.get(i).update(); + } + } + + void display() { + for (int i=particles.size()-1; i>=0; i--) { + particles.get(i).display(); + } + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Platform.pde b/StudentFolders/A1/29kingstont/side_scroller/Platform.pde new file mode 100644 index 0000000..3683ed6 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Platform.pde @@ -0,0 +1,31 @@ +class Platform implements Displayable { + RectBody platform; + + // THERE EXIST TWO POSITIONS... + Platform(PVector pos, int w, int h) { + platform = new RectBody(pos, w, h); + } + + public PVector getPos() { + return this.platform.getPos(); + } + public RectBody getBody() { + return this.platform; + } + + public float getW() { + return this.platform.getW(); + } + public float getH() { + return this.platform.getH(); + } + + public void display() { + fill(PLATFORM_BLUE); + noStroke(); + + drawWavyBox2(platform, 5, PLATFORM_BLUE, PLATFORM_BLUE); + + // rect(this.getPos().x, this.getPos().y, this.getW(), this.getH()); + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/RectBody.pde b/StudentFolders/A1/29kingstont/side_scroller/RectBody.pde new file mode 100644 index 0000000..97f3a2e --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/RectBody.pde @@ -0,0 +1,138 @@ +public class RectBody extends Body { + float w, h; + + RectBody(PVector pos, float w, float h) { + super(pos, 0); + this.w = w; + this.h = h; + } + RectBody(PVector pos, float w, float h, float mass) { + super(pos, mass); + this.w = w; + this.h = h; + } + + @Override + public PVector getTop() { + return this.pos; + } + @Override + public PVector getBottom() { + return PVector.add(this.pos, new PVector(0, this.h)); + } + @Override + public PVector getCenter() { + return PVector.add(pos, new PVector(w/2, h/2)); + } + public float getX() { + return this.pos.x; + } + public float getY() { + return this.pos.y; + } + public float getW() { + return this.w; + } + public float getH() { + return this.h; + } + public PVector getDim() { + return new PVector(w, h); + } + + @Override + public void setTop(PVector p) { + this.pos = p; + } + @Override + public void setBottom(PVector p) { + this.pos = p.sub(0, h); + } + @Override + public void setCenter(PVector p) { + this.pos = p.sub(w/2, h/2); + } + + @Override + public void resolveAllCollisions(Entity entity, Terrain terr, ArrayList platforms, boolean isUpsideDown) { + boolean grounded = false; + boolean damageExempt = false; + + if (isGrounded(terr, isUpsideDown)) { + grounded = true; + + entity.getVel().y = 0; + + float terrY = terr.getHeightAt(this); + if (!isUpsideDown) setBottom(new PVector(pos.x, terrY)); + else setTop(new PVector(pos.x, terrY)); + } + + for (Platform p : platforms) { + boolean falling = abs(entity.getVel().y) > 0; + boolean wasAbove = false; + if (!entity.getIsUpsideDown()) wasAbove = entity.getPrevPos().y+this.h <= p.getPos().y; + else wasAbove = entity.getPrevPos().y >= p.getPos().y+p.getH(); + + if (falling && wasAbove) { + CollisionResult res = Collision.check(p.getBody(), this); + + boolean correctCollisionPlace; + if (!entity.getIsUpsideDown()) correctCollisionPlace = res.side == "bottom"; + else correctCollisionPlace = res.side == "top"; + + if ((res.collided || wasAbove) && correctCollisionPlace) { + grounded = true; + + entity.getVel().y = 0; + this.pos.y -= res.penetration * entity.getDirection(); + + if (p instanceof Trampoline) { + damageExempt = true; + + // Boost jump! + entity.getVel().y = -1000 * entity.getDirection(); + } + } + } + } + + if (grounded) { + // Fall damage + if (!damageExempt && entity.getIsFlipping() && entity instanceof HasHealth) { + HasHealth damageable = (HasHealth) entity; + damageable.damage(25); + } + + entity.setgetIsFlipping(false); + } + } + + @Override + public boolean isGrounded(Terrain terr, boolean isUpsideDown) { + float terrH = terr.getHeightAt(this); + + return !isUpsideDown ? getBottom().y >= terrH : getTop().y <= terrH; + } + + @Override + public boolean isGrounded(Terrain terr, ArrayList platforms, boolean isUpsideDown) { + boolean isOnFloor = isGrounded(terr, isUpsideDown); + boolean isOnPlatform = false; + + for (Platform p : platforms) { + PVector pPos = p.getPos(); + if (this.pos.x+w >= pPos.x && this.pos.x <= pPos.x+p.getW() && this.pos.y+this.h == pPos.y) { + isOnPlatform = true; + break; + } + } + + return isOnFloor || isOnPlatform; + } + + @Override + public boolean isWithin(float minX, float maxX) { + return pos.x+w >= minX && pos.x <= maxX; + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Roofus.pde b/StudentFolders/A1/29kingstont/side_scroller/Roofus.pde new file mode 100644 index 0000000..f6c1783 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Roofus.pde @@ -0,0 +1,37 @@ +public class Roofus extends BoringZombie { + private int numAttacks = 0; + private final int spawnEvery = 1; + Roofus(PVector pos, boolean isUpsideDown) { + super(pos, 1, 2, 1000, 50, 3, isUpsideDown); + } + + @Override + protected void attack(World world) { + Meeple meeple = world.getMeeple(); + if (meeple == null) return; + + weapon.useAction(this, meeple); + numAttacks++; + + if (numAttacks % spawnEvery == 0) { + float enemyX = random(world.getOffset().x, world.getOffset().x+width); + Enemy enemy = new Minion(new PVector(enemyX, 500), getIsUpsideDown()); + world.createEnemy(enemy); + } + } + + @Override + public void display() { + stroke(0); + strokeWeight(1); + fill(5, 224, 195); + + PVector pos = this.getPos(); + RectBody body = this.getBody(); + rect(pos.x, pos.y, body.getW(), body.getH()); + + this.weapon.handheldDisplay(this); + + displayHp(this); + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/ShootingThing.pde b/StudentFolders/A1/29kingstont/side_scroller/ShootingThing.pde new file mode 100644 index 0000000..76f1386 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/ShootingThing.pde @@ -0,0 +1,143 @@ +public class ShootingThing extends Weapon { + private float w, h; + + private int totalAmmo; + + private int loadedAmmo; + private int maxAmmo; + + private boolean isReloading; + private int reloadSpeed; + private int reloadStart; + + ShootingThing() { + super(200, 15); + this.w = 35; + this.h = 20; + + this.totalAmmo = 128; + this.maxAmmo = 16; + this.loadedAmmo = maxAmmo; + + this.reloadSpeed = 1500; + this.isReloading = false; + } + ShootingThing(int cooldown, float damage, int maxAmmo, int reloadSpeed) { + super(cooldown, damage); + this.w = 35; + this.h = 30; + + this.totalAmmo = 128; + this.maxAmmo = maxAmmo; + this.loadedAmmo = maxAmmo; + + this.reloadSpeed = reloadSpeed; + this.isReloading = false; + } + + public int getMaxAmmo() { + return this.maxAmmo; + } + public int getLoadedAmmo() { + return this.loadedAmmo; + } + public int getTotalAmmo() { + return this.totalAmmo; + } + public boolean getIsReloading() { + return this.isReloading; + } + + @Override + protected boolean conditionSatisfied() { + return loadedAmmo > 0 && !isReloading; + } + + @Override + protected void attack(Entity user, ArrayList targets, World world) { + if (!(user.getBody() instanceof RectBody)) throw new Error("I'm too lazy to make this work for circle bodies so this will only work for rectangles!!!"); + + Entity rectUser = (Entity) user; + + PVector bulletPos = rectUser.getPos().copy(); + bulletPos.add(this.getOffset(user)).add(0, h/2); + + PVector bulletVel = new PVector(900, 0).mult(user.getIsFacingRight() ? 1 : -1); + PVector bulletAcc = new PVector(10, 0).mult(user.getIsFacingRight() ? -1 : 1); + world.createBullet(new Bullet(bulletPos, bulletVel, bulletAcc, this.damage, targets)); + loadedAmmo--; + + if (loadedAmmo <= 0) { + this.reload(); + } + } + + public void reload() { + if (this.loadedAmmo == this.maxAmmo) return; + + this.reloadStart = millis(); + this.isReloading = true; + } + + @Override + public void update() { + if (this.isReloading && millis()-reloadStart >= reloadSpeed) { + int ammoToTake = min(this.totalAmmo, this.maxAmmo-this.loadedAmmo); + this.totalAmmo -= ammoToTake; + this.loadedAmmo += ammoToTake; + this.isReloading = false; + } + } + + @Override + protected ArrayList getTargetsInRange(Entity user, ArrayList targets) { + // FIXME + return targets; + } + + @Override + protected PVector getOffset(Entity user) { + if (!(user.getBody() instanceof RectBody)) throw new Error("I'm too lazy to make this work for circle bodies so this will only work for Meeple!!!"); + + Entity rectUser = (Entity) user; + RectBody rectUserBody = rectUser.getBody(); + + PVector offset = new PVector(); + if (user.getIsFacingRight()) { + offset.x = rectUserBody.getW(); + offset.y = rectUserBody.getH()/2-h/2; + } else { + offset.x = -w; + offset.y = rectUserBody.getH()/2-h/2; + } + + return offset; + } + + @Override + public void handheldDisplay(Entity user) { + if (!this.isInUse) fill(50); + else fill(255, 0, 0); + + PVector offset = this.getOffset(user); + if (this.isInUse) verticalLine(user.getPos().x+offset.x); + + rect(user.getPos().x+offset.x, user.getPos().y+offset.y, w, h); + + this.isInUse = false; + } + + @Override + public void iconDisplay(PVector pos) { + fill(255); + noStroke(); + rect(pos.x, pos.y, Constants.BLOCK_UNIT, Constants.BLOCK_UNIT); + + fill(0); + textSize(16); + textAlign(CENTER, CENTER); + text("gun", pos.x+25, pos.y+25); + + // throw new Error("Knife doesn't have entity display yet"); + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Static.pde b/StudentFolders/A1/29kingstont/side_scroller/Static.pde new file mode 100644 index 0000000..ac92b7d --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Static.pde @@ -0,0 +1,17 @@ +public abstract class Static implements Displayable { + protected PVector pos; + public int w, h; + + Static(PVector pos, int w, int h) { + this.pos = pos; + this.w = w; + this.h = h; + } + + public PVector getPos() { + return this.pos; + } + + @Override + public abstract void display(); +} diff --git a/StudentFolders/A1/29kingstont/side_scroller/Terrain.pde b/StudentFolders/A1/29kingstont/side_scroller/Terrain.pde new file mode 100644 index 0000000..93a06bd --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Terrain.pde @@ -0,0 +1,147 @@ +class TerrainSeg { + private int startX, endX; + private int height; + + TerrainSeg(int startX, int endX, int height) { + if (this.endX < this.startX) { + this.startX = this.endX; + this.endX = this.startX; + } else { + this.startX = startX; + this.endX = endX; + } + + this.height = height; + } + + public int getW() { + return this.endX-this.startX; + } + public int getH() { + return this.height; + } + public int getStartX() { + return this.startX; + } + public int getEndX() { + return this.endX; + } +} + +class Terrain { + ArrayList segments; + private int minX; + private int maxX; + + + + private final int baseHAboveGround = Constants.BLOCK_UNIT*3; + private final int maxVarianceUp = Constants.BLOCK_UNIT*5; + private final int maxVarianceDown = Constants.BLOCK_UNIT*2; + + private boolean isUpsideDown; + + Terrain(int w, boolean isUpsideDown) { + this.segments = new ArrayList<>(); + this.isUpsideDown = isUpsideDown; + + this.minX = 0; + this.maxX = 0; + this.constructRightWithoutVarying(w); + } + + private int getReferencePoint() { + return this.isUpsideDown ? 0 : height; + } + private int getDirection() { + return this.isUpsideDown ? -1 : 1; + } + + public int getHeightAt(float x) { + if (x > this.maxX || x < this.minX) return 9999 * this.getDirection(); + + TerrainSeg targetSegment = null; + for (TerrainSeg segment : segments) { + if (segment.startX <= x && segment.endX > x) { + targetSegment = segment; + } + } + + if (targetSegment == null) return 9999 * this.getDirection(); + + return this.getReferencePoint() - targetSegment.height*this.getDirection(); + } + public int getHeightAt(RectBody box) { + int terrY1 = getHeightAt(box.getX()); + int terrY2 = getHeightAt(box.getX()+box.getW()); + + return !isUpsideDown ? min(terrY1, terrY2) : max(terrY1, terrY2); + } + + public int getMinX() { + return this.minX; + } + public int getMaxX() { + return this.maxX; + } + + public void constructLeft(int len) { + int riseFactor = int(random(0, 3))-1; + TerrainSeg lastSeg = segments.size() > 0 ? segments.get(0) : null; + int lastSegHeight = lastSeg != null ? lastSeg.getH() : this.baseHAboveGround; + + this.segments.add(0, new TerrainSeg(this.minX-len, this.minX, lastSegHeight+riseFactor*Constants.BLOCK_UNIT)); + this.minX -= len; + } + public void constructRight(int len) { + this.construct(len, true); + } + public void constructRightWithoutVarying(int len) { + this.segments.add(new TerrainSeg(this.maxX, this.maxX+len, this.baseHAboveGround)); + this.maxX += len; + } + + private void construct(int len, boolean isRight) { + int riseFactor = int(random(0, 3))-1; + + int lastSegIdx = isRight ? segments.size()-1 : 0; + TerrainSeg lastSeg = (segments.size() > 0) ? segments.get(lastSegIdx) : null; + int lastSegHeight = (lastSeg != null) ? lastSeg.height : baseHAboveGround; + + int newHAboveGround = lastSegHeight + riseFactor * Constants.BLOCK_UNIT; + + float variance = (newHAboveGround - baseHAboveGround); + if (variance < 0 && abs(variance) > this.maxVarianceDown) newHAboveGround = this.baseHAboveGround - this.maxVarianceDown; + else if (variance > 0 && abs(variance) > this.maxVarianceUp) newHAboveGround = this.baseHAboveGround + this.maxVarianceUp; + + int insertIdx = isRight ? segments.size() : 0; + + TerrainSeg newSeg; + if (isRight) newSeg = new TerrainSeg(this.maxX, this.maxX+len, newHAboveGround); + else newSeg = new TerrainSeg(this.minX-len, this.minX, newHAboveGround); + + this.segments.add(insertIdx, newSeg); + this.maxX += len * (isRight ? 1 : -1); + } + + // TODO: take in offset as param and only render till renderPadding + public void display(float minX, float maxX) { + for (TerrainSeg segment : segments) { + RectBody segmentRect; + if (!isUpsideDown) { + segmentRect = new RectBody(new PVector(segment.getStartX(), getHeightAt(segment.getStartX())), segment.getW(), segment.getH()); + } else { + segmentRect = new RectBody(new PVector(segment.getStartX(), getHeightAt(segment.getStartX())-segment.getH()), segment.getW(), segment.getH()); + } + + if (!segmentRect.isWithin(minX, maxX)) continue; + + + PVector boxPos; + if (!isUpsideDown) boxPos = new PVector(segment.getStartX(), getHeightAt(segment.getStartX())); + else boxPos = new PVector(segment.getStartX(), 0); + + drawWavyBox2(segmentRect, 10, PLATFORM_BLUE, PLATFORM_BLUE); + } + } +} diff --git a/StudentFolders/A1/29kingstont/side_scroller/Trampoline.pde b/StudentFolders/A1/29kingstont/side_scroller/Trampoline.pde new file mode 100644 index 0000000..89463b3 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Trampoline.pde @@ -0,0 +1,13 @@ +public class Trampoline extends Platform { + Trampoline(PVector pos, int w, int h) { + super(pos, w, h); + } + + @Override + public void display() { + fill(0, 255, 243); + noStroke(); + + rect(getPos().x, getPos().y, getW(), getH()); + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Tree.pde b/StudentFolders/A1/29kingstont/side_scroller/Tree.pde new file mode 100644 index 0000000..060cd2d --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Tree.pde @@ -0,0 +1,34 @@ +public class Tree extends Static { + Tree(PVector pos, int w, int h) { + super(pos, w, h); + } + + // FIXME + public void display() { + // cachedRender.beginDraw(); + // cachedRender.noStroke(); + // cachedRender.background(255, 0, 0); + + // // Base position: bottom center of the PGraphics + // float centerX = cachedRender.width / 2.0; + // float bottomY = cachedRender.height; + + // // Draw trunk + // int trunkWidth = w-145; + // int trunkHeight = h*3/4; + + // cachedRender.fill(139, 69, 19); // brown + // cachedRender.rect(centerX-w/2.0, bottomY-h, trunkWidth, trunkHeight); + + // // Bush position (top of trunk) + // float bushY = bottomY - h; + + // // Draw bushy top (green foliage) + // cachedRender.fill(34, 139, 34); // forest green + // cachedRender.ellipse(centerX, bushY - 30, 200, 150); + // cachedRender.ellipse(centerX - 70, bushY - 10, 150, 120); + // cachedRender.ellipse(centerX + 70, bushY - 10, 150, 120); + + // cachedRender.endDraw(); + } +} diff --git a/StudentFolders/A1/29kingstont/side_scroller/Vestido.pde b/StudentFolders/A1/29kingstont/side_scroller/Vestido.pde new file mode 100644 index 0000000..55d2016 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Vestido.pde @@ -0,0 +1,110 @@ +public class Vestido extends Enemy { + private float minDamage; + private float maxDamage; + + private float range; + private Integer bombTriggeredTime; + private int detonationDelay; + + // private ShootingThing gun; + + Vestido(PVector pos, boolean isUpsideDown) { + super(new RectBody(pos, 40, 80, 1), 0.5, 80, 0, 100, 5, isUpsideDown); + this.range = 150; + this.detonationDelay = 2000; + + this.minDamage = 20; + this.maxDamage = damage; + + // this.gun = new ShootingThing(100, 1, 9999, 0); + } + + // Vestido(PVector pos, int movementSpeed, float minDamage, float maxDamage, float range, int detonationDelay, int cooldown, int maxHealth, boolean isUpsideDown) { + // super(new RectBody(pos, 40, 80, 1), movementSpeed, maxDamage, cooldown, maxHealth, isUpsideDown); + // this.range = range; + // this.detonationDelay = detonationDelay; + + // this.minDamage = minDamage; + // this.maxDamage = maxDamage; + + // // this.gun = new ShootingThing(0, 1, 9999, 0); + // } + + @Override + protected void move(Meeple meeple) { + float diff = meeple.getPos().x - this.getPos().x; + + if (diff > 0) { + this.getPos().x += movementSpeed; + this.setIsFacingRight(true); + } else if (diff < 0) { + this.getPos().x -= movementSpeed; + this.setIsFacingRight(false); + } + } + + @Override + protected boolean attackConditionSatisfied(Meeple meeple) { + return meeple.getPos().dist(this.getCenter()) <= range; + } + + @Override + protected void attack(World world) { + Meeple meeple = world.getMeeple(); + if (meeple == null) return; + + if (bombTriggeredTime == null) { + bombTriggeredTime = millis(); + setMovementSpeed(2); + } + } + + @Override + public void update(World world) { + super.update(world); + + // Meeple meeple = world.getMeeple(); + // if (meeple == null) return; + // gun.useAction(this, meeple); + + if (bombTriggeredTime != null && millis() - bombTriggeredTime >= detonationDelay) { + Meeple meeple = world.getMeeple(); + if (meeple == null) return; + + explode(meeple); + } + } + + private void explode(Meeple meeple) { + // Calculate damage + if (attackConditionSatisfied(meeple)) { + float dist = meeple.getCenter().dist(this.getCenter()); + float damageToDeal = map(dist, 0, range, maxDamage, minDamage); + + meeple.damage(damageToDeal); + } + + die(); + } + + @Override + public void display() { + stroke(0); + strokeWeight(1); + + if (bombTriggeredTime != null) { + PVector center = getCenter(); + fill(map(millis()-bombTriggeredTime, 0, detonationDelay, 100, 255), 0, 0, 255/2); + circle(center.x, center.y, range*2); + } + + PVector pos = getPos(); + RectBody body = getBody(); + fill(0, 255, 0); + rect(pos.x, pos.y, body.getW(), body.getH()); + + // gun.handheldDisplay(this); + + displayHp(this); + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/Weapon.pde b/StudentFolders/A1/29kingstont/side_scroller/Weapon.pde new file mode 100644 index 0000000..29dec67 --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/Weapon.pde @@ -0,0 +1,66 @@ +public abstract class Weapon extends Item { + private float cooldown; + protected float damage; + private Integer lastUsed; + protected boolean isInUse = false; + + Weapon(float cooldown, float damage) { + super(false); + this.cooldown = cooldown; + this.damage = damage; + } + + public float getCooldown() { + return this.cooldown; + } + public float getDamage() { + return this.damage; + } + + @Override + public void update() {} + + @Override + public void useAction(Entity user, World world) { + throw new Error("Please use the other use(). My code is bad ok shut up."); + } + + public void useAction(Entity user, ArrayList targets) { + if (cooldownSatisfied() && conditionSatisfied()) { + this.lastUsed = millis(); + this.isInUse = true; + attack(user, targets, world); + } + } + public void useAction(Entity user, Entity target) { + if (cooldownSatisfied() && conditionSatisfied()) { + this.lastUsed = millis(); + this.isInUse = true; + + ArrayList targets = new ArrayList(); + targets.add(target); + attack(user, targets, world); + } + } + + protected abstract void attack(Entity user, ArrayList targets, World world); + protected abstract PVector getOffset(Entity user); + protected abstract ArrayList getTargetsInRange(Entity user, ArrayList targets); + + protected ArrayList getTargetsInRange(Entity user, Entity target) { + ArrayList targets = new ArrayList<>(); + targets.add(target); + return getTargetsInRange(user, targets); + } + + private boolean cooldownSatisfied() { + if (this.lastUsed == null) return true; + return millis() - this.lastUsed > this.cooldown; + } + protected abstract boolean conditionSatisfied(); + + @Override + public boolean equals(Object that) { + return false; + } +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/World.pde b/StudentFolders/A1/29kingstont/side_scroller/World.pde new file mode 100644 index 0000000..ff2edaa --- /dev/null +++ b/StudentFolders/A1/29kingstont/side_scroller/World.pde @@ -0,0 +1,479 @@ +public interface Displayable { + public void display(); +} +public interface HasHealth { + public float getHealth(); + public float getMaxHealth(); + public float getSacrificialHealth(); + public void damage(float x); + public void heal(float x); + public void setHealth(float x); +} +public interface Killable { + public boolean getIsDead(); +} + +@FunctionalInterface +public interface Spawner { + void spawn(PVector pos, boolean isUpsideDown); +} + +class World implements Displayable { + private MrKeyboard keyboard; + + private PVector offset; + private float viewportPadding = 500; + private float constructPadding = 100; // Construct the world when viewport is 100 px away from void + private Layer[] layers = new Layer[Constants.MAX_LAYER+1]; // 0 thru 10; 0 is foreground; 10 is background + + private Terrain rightsideUpTerr; + private Terrain upsideDownTerr; + private Environment env; + private float decoDensity = 5/1600f; + + private Meeple meeple; + private ParticleSystem dirtPs; + + private ArrayList items; + private int selectedItemIndex; + + private ArrayList platforms; + private ArrayList enemies; + private ArrayList bullets; + private ArrayList itemEntities; + + private Spawner[] spawnableEnemies = { + (PVector pos, boolean isUpsideDown) -> createEnemy(new BoringZombie(pos, isUpsideDown)), + (PVector pos, boolean isUpsideDown) -> createEnemy(new Roofus(pos, isUpsideDown)), + (PVector pos, boolean isUpsideDown) -> createEnemy(new Vestido(pos, isUpsideDown)) + }; + + World(int worldWidth, MrKeyboard keyboard) { + this.keyboard = keyboard; + + this.offset = new PVector(); + this.rightsideUpTerr = new Terrain(worldWidth, false); + this.upsideDownTerr = new Terrain(worldWidth, true); + this.env = new Environment(); + + this.items = new ArrayList<>(); + this.selectedItemIndex = 0; + + this.platforms = new ArrayList<>(); + this.enemies = new ArrayList<>(); + this.bullets = new ArrayList<>(); + this.itemEntities = new ArrayList<>(); + + Constants.CHUNK_W = width/2; + + for (int i=0; i getItems() { + return this.items; + } + public int getSelectedItemIndex() { + return this.selectedItemIndex; + } + public int itemCapacityLeft() { + return Constants.MAX_ITEMS-this.items.size(); + } + public void addItem(Item item) { + if (itemCapacityLeft() > 0) { + this.items.add(item); + } + } + public void setSelectedItem(int i) { + if (i >= 0 && i < Constants.MAX_ITEMS) { + Item item = (i < items.size()) ? items.get(i) : null; + this.meeple.setCurrentlyHeldItem(item); + this.selectedItemIndex = i; + } + } + + public void attachMeeple(Meeple meeple) { + // Register meeple + this.meeple = meeple; + if (!items.isEmpty()) this.meeple.setCurrentlyHeldItem(items.get(this.selectedItemIndex)); + + this.layers[meeple.getLayer()].register(meeple); + + + // Register dirt particle system + dirtPs = new ParticleSystem(new PVector(), (float x, float y, PVector vel, PVector acc) -> new Dirt(new PVector(x, y), vel, acc)); + RectBody meepleBody = meeple.getBody(); + dirtPs.attachTo(meeple, new PVector(meepleBody.getW()/2, meepleBody.getH())); + + layers[meeple.getLayer()].register(dirtPs); + } + + private void spawnEnemies(float minX, float maxX) { + int maxNumEnemiesDay; + int maxNumEnemiesNight; + int worldW = rightsideUpTerr.getMaxX(); + + if (worldW > Constants.CHUNK_W*10) { + maxNumEnemiesDay = 5; + maxNumEnemiesNight = 8; + + } else if (worldW > Constants.CHUNK_W*5) { + maxNumEnemiesDay = 4; + maxNumEnemiesNight = 6; + } else { + maxNumEnemiesDay = 3; + maxNumEnemiesNight = 5; + } + + int numEnemies = int(random(1, maxNumEnemiesDay)); + float dayPercentage = env.getDayPercentage(); + + if (dayPercentage < 0.25) { + numEnemies = int(random(3, maxNumEnemiesNight)); + } + + // Rightside up + for (int i=0; i= Constants.FLAG_SPAWN_CHANCE) return; + boolean isUpsideDown = random(0, 1) < 0.5; + + float x = random(minX, maxX); + float y = rightsideUpTerr.getHeightAt(x); + createItemEntity(new ItemEntity(new PVector(x, y-Constants.BLOCK_UNIT), new Flag(), isUpsideDown)); + } + + public void update() { + long t = System.nanoTime(); // DEBUGGER ######################## + + // Receive user input + long t_receiveInput = System.nanoTime(); // DEBUGGER ##################################### + if (meeple != null) { + Terrain terrainToUse = getTerrainToUse(meeple.getIsUpsideDown()); + + // User input + if (keyboard.isKeyDown('A')) { + meeple.move(false); + if (!Constants.MINIMIZE_GRAPHICS && meeple.isGrounded(terrainToUse, platforms, meeple.getIsUpsideDown())) { + dirtPs.spawn(2, () -> new PVector(random(2, 5), random(-2, 0)), () -> new PVector()); + } + } + if (keyboard.isKeyDown('D')) { + meeple.move(true); + if (!Constants.MINIMIZE_GRAPHICS && meeple.isGrounded(terrainToUse, platforms, meeple.getIsUpsideDown())) { + dirtPs.spawn(2, () -> new PVector(random(-2, -5), random(-2, 0)), () -> new PVector()); + } + } + if (keyboard.isKeyTapped(' ')) { + meeple.jump(terrainToUse, platforms); + } + if (keyboard.isKeyDown('j')) { + meeple.attack(this); + } + + if (keyboard.isKeyDown('r') && meeple.getCurrentlyHeldItem() instanceof ShootingThing) { + ShootingThing gun = (ShootingThing) meeple.getCurrentlyHeldItem(); + gun.reload(); + } + + for (int i=0; i= width-viewportPadding) { + offset.x += (meeplePos.x+meepleW-offset.x)-(width-viewportPadding); + } else if (meeplePos.x-offset.x <= viewportPadding) { + offset.x -= viewportPadding-(meeplePos.x-offset.x); + } + } + logStats("UPDATE receive user input", (System.nanoTime()-t_receiveInput)/1e6); // DEBUGGER ####### + + long t_updateItems = System.nanoTime(); // DEBUGGER ##################################### + + // Update items + for (Item i : items) { + i.update(); + } + logStats("UPDATE items", (System.nanoTime()-t_updateItems)/1e6); // DEBUGGER ####### + + // Update layers ################################################ + // Get live chunks + + long t_allLayers = System.nanoTime(); // DEBUGGER ##################################### + + int startChunk = getChunkIn(offset)[0] - Constants.LIVE_CHUNK_RADIUS; + int endChunk = getChunkIn(offset)[1] + Constants.LIVE_CHUNK_RADIUS; + + for (int i=layers.length-1; i>=0; i--) { + layers[i].update(this, startChunk * Constants.CHUNK_W, endChunk * Constants.CHUNK_W); + } + + logStats("UPDATE all layers", (System.nanoTime()-t_allLayers)/1e6); // DEBUGGER ####### + + if (meeple != null) { + // Check if player dropped into void + if (!meeple.getIsUpsideDown() && meeple.getTop().y >= height+200 || meeple.getIsUpsideDown() && meeple.getBottom().y <= -200) { + meeple.damage(10); + } + + // Check for death + if (meeple.getIsDead()) { + setGameOver(true); + } + } + + // Check if meeple needs to be healed + for (Enemy e : enemies) { + if (e.getIsDead() && !e.getIsSuicide()) { + meeple.heal(e.getSacrificialHealth()); + + score += e.getSacrificialHealth()*10; + } + } + + // Check for death + for (int i=items.size()-1; i>=0; i--) { + if(items.get(i).getIsDead()) { + items.remove(i); + } + } + for (int i=itemEntities.size()-1; i>=0; i--) { + if(itemEntities.get(i).getIsDead()) { + itemEntities.remove(i); + } + } + for (int i=enemies.size()-1; i>=0; i--) { + if(enemies.get(i).getIsDead()) { + enemies.remove(i); + } + } + for (int i=bullets.size()-1; i>=0; i--) { + if(bullets.get(i).getIsDead()) { + bullets.remove(i); + } + } + + long t_envUpdate = System.nanoTime(); // DEBUGGER ################################ + env.update(); + logStats("UPDATE env", (System.nanoTime()-t_allLayers)/1e6); // DEBUGGER ######### + + long t_glow = System.nanoTime(); // DEBUGGER ##################################### + if (meeple != null) env.glow(meeple.getBody(), 200, offset); + logStats("UPDATE glow-ify meeple", timeDiffFromNano(t_glow)); // DEBUGGER #### + + logGeneralStats("World update fn", timeDiffFromNano(t)); // DEBUGGER ################################ + logGeneralStats("Chunks generated", (rightsideUpTerr.getMaxX()-rightsideUpTerr.getMinX())/(float) Constants.CHUNK_W); + } + + public void display() { + long t = System.nanoTime(); // DEBUGGER ######################## + + int startChunk = getChunkIn(offset)[0]; + int endChunk = getChunkIn(offset)[1]; + + long t_envBg = System.nanoTime(); // DEBUGGER ##################################### + int totalItems = 0; + env.displayBackground(offset); + logStats("DISPLAY env background", timeDiffFromNano(t_envBg)); // DEBUGGER ############### + + + long t_layers = System.nanoTime(); + for (int i=layers.length-1; i>=0; i--) { + totalItems += layers[i].getNumItems(); + layers[i].display(startChunk * Constants.CHUNK_W, endChunk * Constants.CHUNK_W); + } + logStats("DISPLAY all layers", timeDiffFromNano(t_layers)); + + + long t_terrs = System.nanoTime(); + rightsideUpTerr.display(startChunk * Constants.CHUNK_W, endChunk * Constants.CHUNK_W); + upsideDownTerr.display(startChunk * Constants.CHUNK_W, endChunk * Constants.CHUNK_W); + logStats("DISPLAY terrain", timeDiffFromNano(t_terrs)); + + + long t_darknessFilter = System.nanoTime(); + env.displayFilter(offset); + logStats("DISPLAY darkness filter", timeDiffFromNano(t_darknessFilter)); // DEBUGGER ####### + + + logGeneralStats("World display fn", timeDiffFromNano(t)); // DEBUGGER ####### + logGeneralStats("# registered objects", totalItems); + } + + private int[] getChunkIn(PVector offset) { + int startChunk = floor(offset.x / Constants.CHUNK_W); + int endChunk = ceil((offset.x+width) / Constants.CHUNK_W); + + return new int[]{startChunk, endChunk}; + } + public Terrain getTerrainToUse(boolean isUpsideDown) { + return isUpsideDown ? upsideDownTerr : rightsideUpTerr; + } + + + + + + + + + + public void createPlatform(PVector pos, int w, int h) { + Platform p = new Platform(pos, w, h); + layers[2].register(p); + platforms.add(p); + } + public void createTrampoline(PVector pos, int w, int h) { + Trampoline t = new Trampoline(pos, w, h); + layers[2].register(t); + platforms.add(t); + } + public void createEnemy(Enemy e) { + if (!Constants.SPAWN_ENEMIES) return; + + layers[2].register(e); + enemies.add(e); + } + public void createBullet(Bullet b) { + layers[2].register(b); + bullets.add(b); + } + public void createItemEntity(ItemEntity e) { + layers[2].register(e); + itemEntities.add(e); + } + + + // Generate more world as the meeple moves + // "Construct world" just sounded cool, but generateWorld prob is better name no + public void constructWorld() { + // if (offset.x <= rightsideUpTerr.minX + constructPadding) { + // int maxX = rightsideUpTerr.minX; + // int minX = rightsideUpTerr.minX-constructW; + // this.rightsideUpTerr.constructLeft(constructW); + // this.upsideDownTerr.constructLeft(constructW); + // this.generateDeco(minX, maxX); + + // this.spawnEnemies(minX, maxX); + // } + + // Construct right + if (offset.x+width >= rightsideUpTerr.maxX - constructPadding) { + int maxX = rightsideUpTerr.maxX + Constants.CHUNK_W; + int minX = rightsideUpTerr.maxX; + rightsideUpTerr.constructRight(Constants.CHUNK_W); + upsideDownTerr.constructRight(Constants.CHUNK_W); + generateDeco(minX, maxX); + + spawnEnemies(minX, maxX); + spawnFlags(minX, maxX); + + + if (random(0, 1) < Constants.TRAMPOLINE_SPAWN_CHANCE) { + println("Spawned trampoline"); + + float x = random(minX, maxX); + if (random(0, 1) < 0.5) { + float terrY = rightsideUpTerr.getHeightAt(x); + createTrampoline(new PVector(x, terrY - Constants.TRAMPOLINE_H), Constants.TRAMPOLINE_W, Constants.TRAMPOLINE_H); // Rightside up + } else { + float terrY = upsideDownTerr.getHeightAt(x); + createTrampoline(new PVector(x, terrY), Constants.TRAMPOLINE_W, Constants.TRAMPOLINE_H); // Upside down + } + } + + score += 50; // score variable from side_scroller.pde + } + } + + public void addTree(PVector pos, int layer) { + int minW = 145; + // layers[layer].add(new Tree(pos, minW+(int)random(35, 75), (int)random(200, 500))); + } + + public void addBush(PVector pos, float minSize, float maxSize, int layer) { + this.layers[layer].register(new Bush(pos, (int)random(minSize, maxSize), (int)random(minSize, maxSize))); + } + + public void generateDeco(int minX, int maxX) { + int w = maxX - minX; + float step = 1.0 / decoDensity; + // for (float x = minX; x < maxX; x += step) { + // float jitter = random(-step * 0.5, step * 0.5); + // float posX = constrain(x + jitter, minX, maxX); + // world.addTree(new PVector(posX, rightsideUpTerr.getHeightAt(posX)), 3); + // } + // for (float x = minX; x < maxX; x += step) { + // float jitter = random(-step * 0.5, step * 0.5); + // float posX = constrain(x + jitter, minX, maxX); + // world.addTree(new PVector(posX, rightsideUpTerr.getHeightAt(posX)), 5); + // } + + float bushesEvery = 100; + for (int i=0; i log = new HashMap<>(); +// private HashMap generalLog = new HashMap<>(); + +private ArrayList log = new ArrayList<>(); +private ArrayList generalLog = new ArrayList<>(); + +private LongHealthBar meepleHealthBar; + +// DEBUGGING!! +private boolean SHOW_STATS = false; +ArrayList verticals = new ArrayList<>(); +ArrayList horizontals = new ArrayList<>(); + +void setGameOver(boolean g) { + this.isGameOver = g; +} + +void setup() { + size(1400, 900, P2D); + // fullScreen(P2D); + background(255); + pixelDensity(1); + frameRate(60); + + keyboard = new MrKeyboard(this); + lastTime = millis(); + + world = new World(width, keyboard); + + world.attachMeeple(new Meeple(2)); + world.generateDeco(0, world.getWorldWidth()); + + if (world.getMeeple() != null) meepleHealthBar = new LongHealthBar(new PVector(50, 50), world.getMeeple().getMaxHealth()); +} + +void draw() { + if (!isGameOver) { + // Calculate delta time + float newTime = millis(); + float frameTime = (newTime - lastTime) / 1000f; + lastTime = newTime; + + dt = min(frameTime, 1/30f); + // frameTime -= dt; + totalTime += dt; + + + long t = System.nanoTime(); // DEBUGGER ######################## + + // Update + world.constructWorld(); + world.update(); + + if (keyboard.isKeyTapped('t')) SHOW_STATS = !SHOW_STATS; + + // Display + textSize(64); + textAlign(TOP, LEFT); + + // Draw world + pushMatrix(); + PVector offset = world.getOffset(); + translate(-offset.x, offset.y); + world.display(); + popMatrix(); + + + // Draw UI & an assortment of others + long t_UI = System.nanoTime(); + Meeple meeple = world.getMeeple(); + if (meeple != null) { + // Tint screen red the lower the player's health goes + if (meeple.getHealth() <= 40) { + fill(255, 0, 0, map(meeple.getHealth(), 0, 40, 100, 0)); + noStroke(); + rect(0, 0, width, height); + } + + // Health bar + meepleHealthBar.setHealth(meeple.getHealth()); + meepleHealthBar.update(); + meepleHealthBar.display(GREEN); + + // Score + if (!SHOW_STATS) { + textAlign(RIGHT, TOP); + fill(255); + textSize(64); + text(score, width-10, 10); + textAlign(CENTER, CENTER); + } + + // Item bar + ArrayList items = world.getItems(); + Item currentlySelected = meeple.getCurrentlyHeldItem(); + float padding = 20; + PVector nowPos = new PVector(50, height-Constants.BLOCK_UNIT-50); + + for (int i=0; i= topY; i--) { + float wv = noise(i/100f); + vertex(pos.x - map(wv, 0, 1, 0, waveMaxSideLen), i); + } + } else { + for (int i = 0; i <= topY; i++) { + float wv = noise(i/100f); + vertex(pos.x - map(wv, 0, 1, 0, waveMaxSideLen), i); + } + } + + // ---- Top/Bottom surface waviness ---- + for (int x = int(pos.x); x <= int(pos.x + w); x++) { + float nh = noise(x/100f); + vertex(x, topY - map(nh, 0, 1, 0, waveMaxSideLen) * direction); + } + + // ---- Right side waviness ---- + if (!isUpsideDown) { + for (int i = topY; i <= height; i++) { + float wv = noise(i/100f); + vertex(pos.x + w + map(wv, 0, 1, 0, waveMaxSideLen), i); + } + } else { + for (int i = topY; i >= 0; i--) { + float wv = noise(i/100f); + vertex(pos.x + w + map(wv, 0, 1, 0, waveMaxSideLen), i); + } + } + + vertex(pos.x + w, isUpsideDown ? 0 : height); + vertex(pos.x + w, topY); + + endShape(CLOSE); +} + +void drawWavyBox2(RectBody box, int waveMaxSideLen, color rect, color wave) { + PVector pos = box.getPos(); + float w = box.getW(); + float h = box.getH(); + + if (!Constants.MINIMIZE_GRAPHICS) { + // Wavy decoration (clockwise) + fill(wave); + noStroke(); + beginShape(); + + int leftX = int(pos.x); + int rightX = int(pos.x+w); + + int topY = int(pos.y); + int bottomY = int(pos.y+h); + + int increment = 10; + + // ---- Left side waviness ---- + for (int y = bottomY; y >= topY; y-=increment) { + float wv = noise(y/100f); + vertex(leftX - map(wv, 0, 1, 0, waveMaxSideLen), y); + } + + // ---- Top surface waviness ---- + for (int x = leftX; x <= rightX; x+=increment) { + float nh = noise(x/100f); + vertex(x, topY - map(nh, 0, 1, 0, waveMaxSideLen)); + } + + // ---- Right side waviness ---- + for (int y = topY; y <= bottomY; y+=increment) { + float wv = noise(y/100f); + vertex(rightX + map(wv, 0, 1, 0, waveMaxSideLen), y); + } + + // ---- Bottom side waviness ---- + for (int x = rightX; x >= leftX; x-=increment) { + float nh = noise(x/100f); + vertex(x, bottomY + map(nh, 0, 1, 0, waveMaxSideLen)); + } + + endShape(CLOSE); + } + + fill(rect); + noStroke(); + + // Main rectangle + rect(pos.x, pos.y, w, h); +} \ No newline at end of file diff --git a/StudentFolders/A1/29kingstont/side_scroller/~$de scroller plan.docx b/StudentFolders/A1/29kingstont/side_scroller/~$de scroller plan.docx new file mode 100644 index 0000000..db3cb28 Binary files /dev/null and b/StudentFolders/A1/29kingstont/side_scroller/~$de scroller plan.docx differ diff --git a/StudentFolders/A1/29kingstont/upwards_ball/sketch.properties b/StudentFolders/A1/29kingstont/upwards_ball/sketch.properties new file mode 100644 index 0000000..a520eda --- /dev/null +++ b/StudentFolders/A1/29kingstont/upwards_ball/sketch.properties @@ -0,0 +1 @@ +main=sketch.pde diff --git a/StudentFolders/A1/29kingstont/upwards_ball/upwards_ball.pde b/StudentFolders/A1/29kingstont/upwards_ball/upwards_ball.pde new file mode 100644 index 0000000..0eba16f --- /dev/null +++ b/StudentFolders/A1/29kingstont/upwards_ball/upwards_ball.pde @@ -0,0 +1,71 @@ +float x, y, dim; +float dy = 0; +float acc = 0; + +color ballColor; +color bgColor; +color nextBallColor; +color nextBgColor; +boolean spawnBallNext = false; + +int lastBeat = 0; +int beatInt = 500; +int beatNum = 0; + +void setup() { + //size(800, 800); + fullScreen(); + dim = 80; + x = random(dim/2, width-dim/2); + y = height-dim/2-10; + + ballColor = randomColor(); + bgColor = color(0); + lastBeat = millis(); +} + +void draw() { + background(bgColor); + strokeWeight(0); + rect(0, 0, beatNum*(width/4.0), height); + + if (millis()-lastBeat >= beatInt) { + fill(ballColor); + if (beatNum == 3 && spawnBallNext) { + ballColor = nextBallColor; + bgColor = nextBgColor; + } + if (beatNum == 4 && spawnBallNext) { + x = random(dim/2, width-dim/2); + y = height-dim/2-10; + spawnBallNext = false; + } + beatNum = beatNum%4+1; + lastBeat = millis(); + } + + strokeWeight(3); + stroke(255); + circle(x, y, dim); + y += dy; + dy += acc; + + if (y < 0-dim/2) { + dy = 0; + acc = 0; + nextBgColor = ballColor; + nextBallColor = randomColor(); + spawnBallNext = true; + } +} + +color randomColor() { + return color(random(255), random(255), random(255)); +} + +void keyPressed() { + if (key == ' ' && dy == 0) { + dy = -20; + //acc = random(-0.5, -2); + } +}