import java.awt.Color; import java.awt.geom.Area; import java.awt.geom.Rectangle2D; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Vector; import org.jbox2d.collision.AABB; import org.jbox2d.collision.PolygonDef; import org.jbox2d.collision.Shape; import org.jbox2d.common.Vec2; import org.jbox2d.dynamics.Body; import org.jbox2d.dynamics.Steppable; import pulpcore.image.BlendMode; import pulpcore.image.CoreFont; import pulpcore.image.CoreGraphics; import pulpcore.math.CoreMath; import pulpcore.sprite.Sprite; import pulpfizz.physics.Actor; import pulpfizz.physics.BodyUtils; import pulpfizz.physics.CollisionUtils; import pulpfizz.physics.Material; import pulpfizz.physics.PolygonUtils; import pulpfizz.physics.contact.ContactData; import pulpfizz.physics.contact.ContactEventListener; import pulpfizz.pulp.body.ImageBodySprite; import pulpfizz.pulp.body.PhysicsLayer; public class ToyBox extends Actor implements ContactEventListener, Steppable { private PhysicsLayer physics; private Vector contents; private Vector released; public Body body; private Shape insideSensor; private Shape swallowSensor; private static Body polygonBody; public final static int TOYBOX_GROUP = 1; private boolean infinite = false; public ToyBox(PhysicsLayer physics, float physicsX, float physicsY) { this.physics = physics; physics.getWorld().registerPostStep(this); contents = new Vector(); released = new Vector(); /* * Create the bounding rectangles, and then the polygon body. */ float largeSide = 5f; float smallSide = 3.5f; if (ToyBox.polygonBody == null) { // Make a larger rect, then subtract the inner one. Rectangle2D.Float outerRect = new Rectangle2D.Float(0f, 0f, largeSide, smallSide); Rectangle2D.Float innerRect = new Rectangle2D.Float(0.2f, .2f, largeSide - .4f, smallSide); Area outerA = new Area(outerRect); outerA.subtract(new Area(innerRect)); ToyBox.polygonBody = PolygonUtils.areaToBody(physics, outerA, 0.1f); // Add a sensor rect. Area a = new Area(innerRect); Vec2[] vecs = PolygonUtils.areaToVertexArray(physics, innerRect, .1f); PolygonDef pd = new PolygonDef(); pd.isSensor = true; for (Vec2 v : vecs) { pd.vertices.add(v); } ToyBox.polygonBody.createShape(pd); ToyBox.polygonBody.allowSleeping(false); // Apply the material. Material m = Material.STONE(); m.setFriction(m.woodF); m.applyAll(ToyBox.polygonBody); /* * Save the static box as a local variable. */ body = ToyBox.polygonBody; } else { body = BodyUtils.cloneBody(ToyBox.polygonBody); } body.setBullet(true); /* * Create the central sensor, for detecting shapes inside. */ Vec2[] vecs = PolygonUtils.areaToVertexArray(physics, new Rectangle2D.Float(0.25f, 0f, largeSide - .5f, smallSide), 0.1f); PolygonDef pd = new PolygonDef(); pd.isSensor = true; for (Vec2 v : vecs) pd.addVertex(v); insideSensor = body.createShape(pd); /* * Create the bottom sensor, for detecting swallowing events. */ vecs = PolygonUtils.areaToVertexArray(physics, new Rectangle2D.Float(0.25f, .2f, largeSide - .5f, .5f), 0.1f); pd = new PolygonDef(); pd.isSensor = true; for (Vec2 v : vecs) pd.addVertex(v); swallowSensor = body.createShape(pd); // Register a contact listener between this body and the Blokk group. setName("Toy Box"); addToGroup(TOYBOX_GROUP); addBody(body); physics.getWorld().getContactEventDispatcher().registerActorGroupListener(getID(), Blokk.BLOKK_GROUP, this); // Move the body down a bit. body.setXForm(new Vec2(physicsX,physicsY),0); /* * A sprite for the box image. */ ImageBodySprite sprite1 = new ImageBodySprite(body, physics, GameScene.IMG_LOC + "box.png", new AABB(new Vec2(0, 0), new Vec2( largeSide, smallSide))); physics.add(sprite1); final ImageBodySprite sprite = new ImageBodySprite(body, physics, GameScene.IMG_LOC + "box-front.png", new AABB(new Vec2(0, 0), new Vec2(largeSide, smallSide))) { @Override public void update(int elapsedTime) { super.update(elapsedTime); setDirty(true); } }; physics.add(sprite); physics.moveToTop(sprite); // sprite.alpha.set(100); /* * A label for the blokk count. */ Sprite label = new Sprite(0, 0, 0, 0) { CoreFont stencilFont = CoreFont.load(GameScene.IMG_LOC + "stencil.font.png").tint(new Color(100, 80, 70).getRGB()); @Override protected void drawSprite(CoreGraphics g) { g.setFont(stencilFont); g.drawString(getBoxCount(), -76, 33); g.drawString("BLOKKS", -38, 33); } @Override public void update(int elapsedTime) { super.update(elapsedTime); setDirty(true); } }; label.x.bindTo(sprite.x); label.y.bindTo(sprite.y); label.angle.bindTo(sprite.angle); physics.add(label); physics.moveToTop(label); } private boolean isCrowded() { return getBlokksInside() >= 2; } private int getBlokksInside() { Body[] bodies = CollisionUtils.getContactingBodies(insideSensor); int count = 0; for (Body b : bodies) { Actor a = (Actor) b.getUserData(); if (a.isInGroup(Blokk.BLOKK_GROUP)) { count++; } } return count; } private String getBoxCount() { if (isInfinite()) return new String('\u221E' + ""); else return String.valueOf(getBlokksInside() + getContents().size()); } public boolean isInfinite() { return infinite; } public void setInfinite(boolean infinite) { this.infinite = infinite; } public List getContents() { return contents; } public void setContents(List newContents) { // for (Blokk b : released) // { // physics.getWorld().destroyBody(b.body); // physics.remove(b.sprite); // } // released.clear(); this.contents.clear(); this.contents.addAll(newContents); this.released.clear(); } private void createNextBlokk() { Blokk blokk; if (isInfinite()) { Blokk.BodyShapes[] values = Blokk.BodyShapes.values(); blokk = new Blokk(physics, values[CoreMath.rand(0, values.length - 1)]); } else { synchronized (contents) { if (contents.size() == 0) { // No blokks left! return; } Blokk.BodyShapes shape = contents.remove(0); blokk = new Blokk(physics, shape); } } released.add(blokk); Vec2 physPos = body.getWorldCenter(); blokk.body.setXForm(physPos, 0); blokk.body.synchronizeTransform(); } private Collection killMe = Collections.synchronizedCollection(new HashSet()); private void swallowBlokk(Blokk b) { if (System.currentTimeMillis() - b.creationTime < 1000) { return; } synchronized (killMe) { killMe.add(b); } synchronized (released) { released.remove(b); } } public void trigger(ContactData c) { Shape notMe = c.shape2; Shape me = c.shape1; Body b = notMe.getBody(); Actor a = (Actor) b.getUserData(); if (!(a instanceof Blokk)) return; Blokk blokk = (Blokk) a; if (me == insideSensor) { switch (c.state) { case ContactData.ADD: // shapesInside.add(notMe); break; case ContactData.PERSIST: // shapesInside.add(notMe); break; case ContactData.REMOVE: // shapesInside.remove(notMe); break; } } else if (me == swallowSensor) { switch (c.state) { case ContactData.PERSIST: if (getBlokksInside() > 2) swallowBlokk(blokk); break; default: break; } } } int timeout; public synchronized void step(float dt, int iterations) { synchronized (killMe) { for (Blokk blokk : killMe) { blokk.destroy(); if (!isInfinite()) { contents.add(blokk.bodyShape); } } killMe.clear(); } timeout -= 5; if (!isCrowded() && timeout < 0) { createNextBlokk(); timeout = 100; } } }