package bubolo.world.entity.concrete; import java.util.ArrayList; import java.util.List; import java.util.UUID; import com.badlogic.gdx.math.Intersector; import com.badlogic.gdx.math.Polygon; import bubolo.util.TileUtil; import bubolo.world.Damageable; import bubolo.world.World; import bubolo.world.entity.Effect; import bubolo.world.entity.Entity; import bubolo.audio.Audio; import bubolo.audio.Sfx; /** * Bullets are shot by Tanks and Pillboxes, and can cause damage to StationaryElements and other * Actors. * * @author BU CS673 - Clone Productions */ public class Bullet extends Effect { /** * Used when serializing and de-serializing. */ private static final long serialVersionUID = -9153862417398330898L; // The max distance the bullet can travel, in world units. private static final float MAX_DISTANCE = 600; // The distance the bullet has traveled. private int distanceTraveled; // The x movement per tick. private float movementX; // The y movement per tick. private float movementY; // The bullet's movement speed. private static final float SPEED = 6.f; // The bullet's movement speed. private static final int DAMAGEDONE = 10; // Specifies whether the bullet is initialized. private boolean initialized; private Entity parent = null; /** * Construct a new Bullet with a random UUID. */ public Bullet() { this(false); } /** * Package-private constructor for testing. Allows the sound to be suppressed. * * @param noSound * true if there should be no bullet creation sound, or false otherwise. */ Bullet(boolean noSound) { this(UUID.randomUUID(), noSound); } /** * Construct a new Bullet with the specified UUID. * * @param id * is the existing UUID to be applied to the new Bullet. * @param noSound * should be true if a sound should not be played upon Bullet construction, false * otherwise. */ public Bullet(UUID id, boolean noSound) { super(id); setWidth(4); setHeight(8); updateBounds(); // Play cannon fired sound effect. if (!noSound) { Audio.play(Sfx.CANNON_FIRED); } } /** * Construct a new Bullet with the specified UUID, and play the bullet created sound by default. * * @param id * is the existing UUID to be applied to the new Bullet. */ public Bullet(UUID id) { this(id, false); } @Override public void update(World world) { if (!initialized) { initialize(); } // TODO (cdc - 2014-03-21): This could be made into a controller. However, it's so // simple, what's the point? move(world); } /** * return the Entity that spawned this bullet * @return * the entity that spawned this bullet */ public Entity getParent() { return this.parent; } /** * sets the Parent field of this bullet * @param parent * the entity to set as the parent of this bullet */ public void setParent(Entity parent) { this.parent = parent; } /** * Moves the bullet. Calls dispose() on this entity if the distance travelled has exceeded the * MAX_DISTANCE value. */ private void move(World world) { if (distanceTraveled > MAX_DISTANCE) { dispose(); return; } setX(getX() + movementX); setY(getY() + movementY); distanceTraveled += (Math.abs(movementX) + Math.abs(movementY)); for(Entity collider:getLookaheadEntities(world)) { if (collider instanceof Damageable) { if (Intersector.overlapConvexPolygons(collider.getBounds(), this.getBounds())) { Damageable damageableCollider = (Damageable)collider; damageableCollider.takeHit(DAMAGEDONE); dispose(); return; } } } } /** * Sets the x and y movement values. Should be called once (but not in the constructor, since * the rotation is not yet set). */ private void initialize() { movementX = (float)(Math.cos(getRotation()) * SPEED); movementY = (float)(Math.sin(getRotation()) * SPEED); initialized = true; } /** * Returns a list of all Entities that would overlap with this Tank if it was where it * will be in one game tick, along its current trajectory. */ private List<Entity> getLookaheadEntities(World w) { ArrayList<Entity> intersects = new ArrayList<Entity>(); for (Entity localEntity: TileUtil.getLocalEntities(getX(),getY(), w)) { if (localEntity!=this && localEntity!=this.parent) { if (overlapsEntity(localEntity) ||Intersector.overlapConvexPolygons(lookAheadBounds(), localEntity.getBounds())) { intersects.add(localEntity); } } } return intersects; } private Polygon lookAheadBounds() { Polygon lookAheadBounds = getBounds(); float newX = (float) (getX() + Math.cos(getRotation()) * SPEED); float newY = (float) (getY() + Math.sin(getRotation()) * SPEED); lookAheadBounds.setPosition(newX, newY); return lookAheadBounds; } }