package propra2012.gruppe33.bomberman; import java.awt.Point; import java.util.ArrayList; import java.util.List; import java.util.UUID; import propra2012.gruppe33.bomberman.ai.AIControl; import propra2012.gruppe33.bomberman.graphics.rendering.scenegraph.grid.items.CollectableItem; import propra2012.gruppe33.bomberman.graphics.rendering.scenegraph.grid.items.ItemSpawner; import propra2012.gruppe33.bomberman.graphics.rendering.scenegraph.grid.movement.GridMovement; import propra2012.gruppe33.bomberman.graphics.sprite.AnimationRoutines; import com.indyforge.twod.engine.graphics.rendering.scenegraph.Entity; import com.indyforge.twod.engine.graphics.rendering.scenegraph.EntityFilter; import com.indyforge.twod.engine.graphics.rendering.scenegraph.GraphicsEntity; import com.indyforge.twod.engine.graphics.rendering.scenegraph.RenderedImage; import com.indyforge.twod.engine.graphics.rendering.scenegraph.Scene; import com.indyforge.twod.engine.graphics.rendering.scenegraph.animation.RenderedAnimation; import com.indyforge.twod.engine.graphics.rendering.scenegraph.animation.RenderedAnimation.AnimationEvent; import com.indyforge.twod.engine.graphics.rendering.scenegraph.math.Grid; import com.indyforge.twod.engine.graphics.rendering.scenegraph.math.MathExt; import com.indyforge.twod.engine.graphics.rendering.scenegraph.math.Vector2f; import com.indyforge.twod.engine.graphics.rendering.scenegraph.math.Vector2f.Direction; import com.indyforge.twod.engine.graphics.rendering.scenegraph.network.entity.DetachEntityChange; import com.indyforge.twod.engine.graphics.rendering.scenegraph.network.input.InputChange; import com.indyforge.twod.engine.graphics.rendering.scenegraph.timeout.Timeout; import com.indyforge.twod.engine.graphics.sprite.Animation; import com.indyforge.twod.engine.graphics.sprite.AnimationBundle; import com.indyforge.twod.engine.graphics.sprite.Sprite; import com.indyforge.twod.engine.resources.assets.AssetManager; /** * * @author Christopher Probst * @author Malte Schmidt * @author Matthias Hesse * */ public final class GameRoutines implements GameConstants { public static final EntityFilter EXP_RANGE_FILTER = new EntityFilter() { /** * */ private static final long serialVersionUID = 1L; /* * */ @Override public boolean accept(Entity element) { return element.typeProp(Float.class) != null; } }; /** * This filter only accepts free nodes which have a velocity. */ public static final EntityFilter VELOCITY_NODE_FILTER = new EntityFilter() { /** * */ private static final long serialVersionUID = 1L; /* * */ @Override public boolean accept(Entity element) { return isFieldFree(element) && element.typeProp(Float.class) != null; } }; /** * Collects all entity node position which are in range. * * @param node * The node. * @param entityFilter * The entity filter. * @param range * The range. * @return a list with all points. */ public static List<Point> bombRange(Entity node, EntityFilter entityFilter, int range) { if (node == null) { throw new NullPointerException("node"); } else if (!(node.parent() instanceof GraphicsEntity)) { throw new IllegalArgumentException("The node parent is not a " + "graphics entity"); } else if (entityFilter != null && !entityFilter.accept(node)) { throw new IllegalStateException("The given node is not accepted"); } // Convert parent GraphicsEntity gridEntity = (GraphicsEntity) node.parent(); // Convert the node GraphicsEntity graphicsNode = (GraphicsEntity) node; // Calc origin Vector2f origin = graphicsNode.position().round(); // Create new array list List<Point> points = new ArrayList<Point>(range * 4); // The origin is quite valid! points.add(origin.point()); /* * Cast rays into all directions. Enums ftw! */ for (Direction dir : Direction.values()) { // Ignore the undefined direction! if (dir == Direction.Undefined) { continue; } // Calc the dir range int dirRange = (int) GameRoutines.lineOfSight(gridEntity, graphicsNode, null, entityFilter, range, dir); if (dirRange >= 1) { // Add points for (int i = 1; i <= dirRange; i++) { // Calc active point point and add points.add(origin.add(dir.vector().scaleLocal(i)).point()); } } } return points; } /** * Calculates the line-of-sight starting at the given position into the * given direction. If you provide an entity filter you can specify which * nodes are valid. * * @param gridEntity * The grid entity. * @param node * The node. * @param offset * The offset. * @param entityFilter * The entity filter. * @param max * The max distance. * @param direction * The direction. * @return the valid line-of-sight as float. */ public static float lineOfSight(Entity gridEntity, GraphicsEntity node, Vector2f offset, EntityFilter entityFilter, float max, Direction direction) { if (gridEntity == null) { throw new NullPointerException("gridEntity"); } else if (node == null) { throw new NullPointerException("node"); } else if (direction == null) { throw new NullPointerException("direction"); } else if (offset == null) { offset = Vector2f.zero(); } // Get the coords of the nearest vector Vector2f nearest = node.position().round(); // Get position of the child Vector2f position = nearest.add(offset); // Get the grid Grid grid = gridEntity.typeProp(Grid.class); // Position is outside the grid if (!grid.inside(nearest)) { throw new IllegalArgumentException("Position out of grid"); } // The return value float distance; // Used for check int offx, offy; switch (direction) { case North: case South: // Calc distance distance = direction == Direction.North ? position.y - nearest.y : nearest.y - position.y; // Init offy = direction == Direction.North ? -1 : 1; offx = 0; break; case West: case East: // Calc distance distance = direction == Direction.West ? position.x - nearest.x : nearest.x - position.x; // Init offy = 0; offx = direction == Direction.West ? -1 : 1; break; default: throw new IllegalArgumentException("Unknown enum type: " + direction); } // Get the nearest point Point point = nearest.point(); /* * While the next point is inside the grid and the entity filter accepts * the next node entity (if null the filter is ignored) and the distance * has not reached the max yet. */ while (grid.inside(point, offx, offy) && (entityFilter == null || entityFilter.accept(gridEntity .childAt(grid.index(point)))) && distance < max) { // Increase distance distance += 1.0f; } // Limit the result return MathExt.clamp(distance, 0, max); } public static RenderedImage createShield(final Scene scene) { if (scene == null) { throw new NullPointerException("scene"); } // The shield final RenderedImage shield = new RenderedImage() { /** * */ private static final long serialVersionUID = 1L; /* * (non-Javadoc) * * @see * com.indyforge.twod.engine.graphics.rendering.scenegraph.Entity * #onDetached * (com.indyforge.twod.engine.graphics.rendering.scenegraph.Entity, * com.indyforge.twod.engine.graphics.rendering.scenegraph.Entity) */ @Override protected void onDetached(Entity parent, Entity child) { // Deactive sound if (scene.processor().hasSession()) { scene.soundManager().playSound(SHIELD_OFF_SOUND, true); } } }.centered(true).imageResource(scene.imageProp(SHIELD_IMAGE)); // Set index + tag shield.index(SHIELD_INDEX).tag(SHIELD_TAG); // Register delete procedure! if (scene.processor().hasAdminSessionServer()) { shield.attach(new Timeout(10) { /** * */ private static final long serialVersionUID = 1L; @Override protected void onTimeout(Timeout timeout) { super.onTimeout(timeout); DetachEntityChange dec = new DetachEntityChange(); dec.entities().add(shield.registrationKey()); scene.processor().adminSessionServer().composite() .queueChange(dec, true); } }); } return shield; } public static RenderedImage createPalisade(Scene scene, Direction direction) { if (scene == null) { throw new NullPointerException("scene"); } // The new palisade RenderedImage palisade = new RenderedImage().centered(true); // A palisade is breakable! palisade.tag(BREAKABLE_TAG).index(ITEM_INDEX); // Switch direction switch (direction) { case North: case South: palisade.imageResource(scene.imageProp(PALISADE_VERT_IMAGE)); break; case East: case West: palisade.imageResource(scene.imageProp(PALISADE_HORI_IMAGE)); break; default: throw new IllegalArgumentException(direction + " not supported"); } return palisade; } public static RenderedAnimation createExplosion(Sprite explosion, long timePerImage) { // Create new explosion entity Animation animation = explosion .newAnimationFromRange("explosion", timePerImage, 0, 0, 25) .loop(false).paused(false); // Create an entity using the animation RenderedAnimation renderedAnimation = new RenderedAnimation(); renderedAnimation.tag(FREE_TAG).tag(EXPLOSION_TAG) .index(EXPLOSION_INDEX); renderedAnimation.animationBundle().add(animation); renderedAnimation.animationName("explosion"); // Delete this entity when the animation is finished renderedAnimation.attach(new Entity() { /** * */ private static final long serialVersionUID = 1L; @Override protected void onEvent(Entity source, Object event, Object... params) { super.onEvent(source, event, params); if (event instanceof AnimationEvent) { switch ((AnimationEvent) event) { case AnimationFinished: // Detach the rendered animation source.detach(); break; } } } }); return renderedAnimation; } /** * Creates a new remote dwarf player. * * @param assetManager * The asset manager to load the dwarf. * @param name * The name of the player. * @return the created dwarf. * @throws Exception * If an error occurs. */ public static GraphicsEntity createRemoteDwarf(AssetManager assetManager, String name) throws Exception { return createRemoteGridPlayer(name, AnimationRoutines.createDwarf(assetManager, 35, 35)); } /** * Creates a new remote wizard player. * * @param assetManager * The asset manager to load the wizard. * @param name * The name of the player. * @return the created wizard. * @throws Exception * If an error occurs. */ public static GraphicsEntity createRemoteWizard(AssetManager assetManager, String name) throws Exception { return createRemoteGridPlayer(name, AnimationRoutines.createWizard(assetManager, 35, 35)); } /** * Creates a new remote santa player. * * @param assetManager * The asset manager to load the santa. * @param name * The name of the player. * @return the created santa. * @throws Exception * If an error occurs. */ public static GraphicsEntity createRemoteSanta(AssetManager assetManager, String name) throws Exception { return createRemoteGridPlayer(name, AnimationRoutines.createSanta(assetManager, 35, 35)); } /** * Creates a new remote knight player. * * @param assetManager * The asset manager to load the knight. * @param name * The name of the player. * @return the created knight. * @throws Exception * If an error occurs. */ public static GraphicsEntity createRemoteKnight(AssetManager assetManager, String name) throws Exception { return createRemoteGridPlayer(name, AnimationRoutines.createKnight(assetManager, 35, 35)); } /** * Translates the bomb to its range. * * @param bomb * The bomb. * @return the range or -1. */ public static int bombRange(CollectableItem bomb) { if (bomb == null) { throw new NullPointerException("bomb"); } switch (bomb) { case DefaultBomb: return DEFAULT_BOMB_RANGE; case NukeBomb: return NUKE_BOMB_RANGE; case FastBomb: return FAST_BOMB_RANGE; default: return -1; } } /** * Translates the bomb to its delay. * * @param bomb * The bomb. * @return the delay or -1. */ public static float bombDelay(CollectableItem bomb) { if (bomb == null) { throw new NullPointerException("bomb"); } switch (bomb) { case DefaultBomb: return DEFAULT_BOMB_DELAY; case NukeBomb: return NUKE_BOMB_DELAY; case FastBomb: return FAST_BOMB_DELAY; default: return -1; } } /** * Translates the item to an asset path. * * @param item * The item. * @return the asset path or null. */ public static String itemToAsset(CollectableItem item) { if (item == null) { throw new NullPointerException("item"); } switch (item) { case DefaultBomb: return DEFAULT_BOMB_IMAGE; case NukeBomb: return NUKE_BOMB_IMAGE; case FastBomb: return FAST_BOMB_IMAGE; case Palisade: return PALISADE_HORI_IMAGE; case ShieldPotion: return SHIELD_POTION_IMAGE; case Speed: return SPEED_IMAGE; default: return null; } } /** * Translates the item to a bag asset path. * * @param item * The item. * @param count * The item count. * @return the asset path or null. */ public static String itemToBagAsset(CollectableItem item, int count) { if (item == null) { throw new NullPointerException("item"); } switch (item) { case DefaultBomb: return DEFAULT_BOMB_BAG_IMAGE; case NukeBomb: return NUKE_BOMB_BAG_IMAGE; case FastBomb: return FAST_BOMB_BAG_IMAGE; case Palisade: return PALISADE_BAG_IMAGE; case ShieldPotion: return SHIELD_POTION_IMAGE; case Speed: return count >= 0 ? FAST_SHROOM_IMAGE : SLOW_SHROOM_IMAGE; default: return null; } } /** * Hides a bomb behind the first breakable entity at the given node. * * @param node * The node. * @param item * The item. * @param count * The item count. */ public static void createItem(GraphicsEntity node, final CollectableItem item, final int count) { if (node == null) { throw new NullPointerException("node"); } else if (item == null) { throw new NullPointerException("item"); } else if (node.children().size() <= 0) { throw new IllegalStateException("The given node does not " + "contain any children"); } else if (!node.childAt(0).tagged(BREAKABLE_TAG)) { throw new IllegalStateException("The first entity at this " + "point is not tagged as breakable"); } // Lookup bag asset final String bagAsset = itemToBagAsset(item, count); // Check the asset! if (bagAsset == null) { throw new IllegalArgumentException("Unknown bag"); } // There MUST BE a breakable child! node.childAt(0).attach(new Entity() { /** * */ private static final long serialVersionUID = 1L; private final UUID uuid = UUID.randomUUID(); /* * (non-Javadoc) * * @see * com.indyforge.twod.engine.graphics.rendering.scenegraph.Entity # * onParentDetached(com.indyforge.twod.engine.graphics.rendering * .scenegraph.Entity, com.indyforge.twod.engine.graphics.rendering * .scenegraph.Entity) */ @Override protected void onParentDetached(final Entity parent, Entity child) { super.onParentDetached(parent, child); // Create a new item image RenderedImage itemImage = new RenderedImage( ((GraphicsEntity) parent).findScene().imageProp( bagAsset)) { /** * */ private static final long serialVersionUID = 1L; /* * (non-Javadoc) * * @see * com.indyforge.twod.engine.graphics.rendering.scenegraph * .Entity * #onEntityAttached(com.indyforge.twod.engine.graphics * .rendering.scenegraph.Entity, * com.indyforge.twod.engine.graphics * .rendering.scenegraph.Entity) */ @Override protected void onEntityAttached(Entity p, Entity c) { // Find the scene Scene scene = findScene(); /* * Sibling added ? */ if (parent == p && scene.processor().hasAdminSessionServer()) { if (c.tagged(PLAYER_TAG)) { // Get item spawner object ItemSpawner sp = c.typeProp(ItemSpawner.class); // Increase the item count sp.addItems(item, count, true); // Remove the item bag DetachEntityChange dec = new DetachEntityChange(); dec.entities().add(registrationKey()); // Queue the change scene.processor().adminSessionServer() .broadcast().queueChange(dec, true); // Apply direct on server side ! scene.processor().adminSessionServer().local() .applyChange(dec); } } } }.centered(true); // Use the same reg key itemImage.registrationKey(uuid); // Scale a bit itemImage.scale().set(ITEM_SCALE); // Set item order + tag itemImage.index(ITEM_INDEX).tag(FREE_TAG).tag(BREAKABLE_TAG); // Attach to the node parent.attach(itemImage); } }); } /** * Creates a new local grid player. * * @param name * The name of the player. * @param charAniBundle * The char-animation bundle. * @return the created player entity. */ public static GraphicsEntity createRemoteGridPlayer(String name, AnimationBundle charAniBundle) { if (name == null) { throw new NullPointerException("name"); } // Create new player GraphicsEntity player = new GraphicsEntity(); // Register the input change event player.events().put(InputChange.class, player.iterableChildren(true, true)); // Set player name player.name(name); // Give player tag player.tag(PLAYER_TAG); // Create animation RenderedAnimation charAni = new RenderedAnimation(charAniBundle); // Look north by default charAni.animationName(AnimationRoutines.RUN_PREFIX + Direction.North.toString().toLowerCase()); // Create new item spawner ItemSpawner itemSpawner = new ItemSpawner(); // Create grid movement GridMovement movement = new GridMovement(); // Attach remaining stuff player.attach( charAni, movement, AnimationRoutines.createGridControllerAnimationHandler(charAni), itemSpawner); // Add the animation player.addTypeProp(charAni); // Add the movement! player.addTypeProp(movement); // Add the item spawner player.addTypeProp(itemSpawner); // Set scale player.scale().set(PLAYER_SCALE); return player; } /** * Checks a given field for explosions. * * @param nodeEntity * The node. * @return true if the given node contains an explosion-tagged entity, * otherwise false. */ public static boolean hasFieldExplosion(Entity nodeEntity) { if (nodeEntity == null) { throw new NullPointerException("nodeEntity"); } for (Entity child : nodeEntity) { if (child.tagged(EXPLOSION_TAG)) { return true; } } return false; } /** * Checks the child node of the grid entity at the given point to be free. * * @param gridEntity * The grid entity. * @param point * The point of the node. * @return true if the field is free, otherwise false. */ public static boolean isFieldFree(Entity gridEntity, Point point) { if (gridEntity == null) { throw new NullPointerException("gridEntity"); } else if (point == null) { throw new NullPointerException("point"); } // Get the grid Grid grid = gridEntity.typeProp(Grid.class); // Position is outside the grid if (!grid.inside(point)) { throw new IllegalArgumentException("Position out of grid"); } return isFieldFree(gridEntity.childAt(grid.index(point))); } /** * Checks a single node entity to be bombable. * * @param nodeEntity * The node entity you want to check. * @return true if the field is bombable, otherwise false. */ public static boolean isFieldBombable(Entity nodeEntity) { if (nodeEntity == null) { throw new NullPointerException("nodeEntity"); } for (Entity child : nodeEntity) { if (!child.tagged(FREE_TAG) && !child.tagged(PLAYER_TAG)) { return false; } } return true; } /** * Checks a single node entity to be free. * * @param nodeEntity * The node entity you want to check. * @return true if the field is free, otherwise false. */ public static boolean isFieldFree(Entity nodeEntity) { if (nodeEntity == null) { throw new NullPointerException("nodeEntity"); } for (Entity child : nodeEntity) { if (!child.tagged(FREE_TAG)) { return false; } } return true; } /** * Rearranges the children of the given node entity. * <p> * Please note that this method is NOT called automatically so you should * call this method when the position of the node-children have been * changed. * * @param node * The node entity. * @return the node for chaining. */ public static GraphicsEntity rearrangeGridNode(GraphicsEntity node) { if (node == null) { throw new NullPointerException("node"); } else if (!(node.parent() instanceof GraphicsEntity)) { throw new IllegalArgumentException("node does not have " + "a graphics entity parent"); } // Lookup parent GraphicsEntity graphicsParent = (GraphicsEntity) node.parent(); // Lookup grip Grid grid = graphicsParent.typeProp(Grid.class); // Check the grid if (grid == null) { throw new IllegalStateException("Parent does not have a " + "grid property"); } /* * The following code is very important. It reattaches children when * they leave the nodes. */ // Iterate over all children for (Entity child : node) { if (child instanceof GraphicsEntity) { // Convert GraphicsEntity graphicsChild = (GraphicsEntity) child; // Get points Point absolute = node.position().point(), relative = graphicsChild .position().round().point(), newAbsolute = new Point( relative); // Translate the new absolute point newAbsolute.translate(absolute.x, absolute.y); // Check coords if (!absolute.equals(newAbsolute)) { // New point valid ?? if (grid.inside(newAbsolute)) { // Lookup other child Entity otherChild = graphicsParent.childAt(grid .index(newAbsolute)); // Attach to it otherChild.attach(child); // Change position graphicsChild.position().x -= relative.x; graphicsChild.position().y -= relative.y; } } } } return node; } public static float edgeWeight(GraphicsEntity ge, Point a, Point b) { if (a.distance(b) > 1.5) { throw new IllegalArgumentException("Point b is too far"); } // Lookup grid Grid grid = ge.typeProp(Grid.class); if (!grid.inside(a)) { throw new IllegalArgumentException("a out of field"); } else if (!grid.inside(b)) { throw new IllegalArgumentException("b out of field"); } Entity ea = ge.childAt(grid.index(a)); Entity eb = ge.childAt(grid.index(b)); Float fa = ea.typeProp(Float.class); Float fb = eb.typeProp(Float.class); if (fa == null || fb == null) { return Float.MAX_VALUE; } float avg = (fa + fb) / 2; return 1f / avg; } /** * Creates a new grid entity which contains all nodes. * * @param gridWidth * The width of the grid. * @param gridHeight * The height of the grid. * @return the new grid entity. */ public static GraphicsEntity createGridEntity(final int gridWidth, final int gridHeight) { // Create grid final Grid grid = new Grid(gridWidth, gridHeight); /* * The AI-Field used by the AI. Updated every frame! */ final int[][][] aiField = new int[gridHeight][gridWidth][0]; // Create a new grid entity GraphicsEntity gridEntity = new GraphicsEntity() { /** * */ private static final long serialVersionUID = 1L; /* * (non-Javadoc) * * @see * com.indyforge.twod.engine.graphics.rendering.scenegraph.Entity * #onUpdate(float) */ @Override protected void onUpdate(float tpf) { super.onUpdate(tpf); if (findSceneProcessor().hasAdminSessionServer()) { // Create nodes for each coords for (int y = 0; y < gridHeight; y++) { for (int x = 0; x < gridWidth; x++) { // System.out.print(aiField[y][x].length + ", "); if (aiField[y][x] == null) { continue; } // Get the node GraphicsEntity node = (GraphicsEntity) childAt(grid .index(x, y)); // Create new array to hold the children array aiField[y][x] = new int[node.children().size()]; // The index int i = 0; // Check all children! for (Entity child : node) { if (child.tagged(PLAYER_TAG)) { aiField[y][x][i++] = AIControl.PLAYER; } else if (child.tagged(BREAKABLE_TAG)) { aiField[y][x][i++] = AIControl.BREAKABLE; } else if (child.tagged(BOMB_TAG)) { aiField[y][x][i++] = AIControl.BOMB; } else { aiField[y][x][i++] = AIControl.GOOD_ITEM; } } } // System.out.println(); } // / System.out.println(); // System.out.println(); // System.out.println(); // System.out.println(); } } }; // Simply put into props gridEntity.addTypeProp(aiField); // Add as type property gridEntity.addTypeProp(grid); // Create nodes for each coords for (int y = 0; y < gridHeight; y++) { for (int x = 0; x < gridWidth; x++) { // Create a new node GraphicsEntity node = new GraphicsEntity(); // Set position on grid node.position().set(x, y); // Not visible... node.visible(false); // Attach the node to the grid gridEntity.attach(node); } } return gridEntity; } // Should not be instantiated... private GameRoutines() { } }