import java.awt.*; /* *---------------------------------------------------------------------- * * Bullet.java */ /** Bullet is the class that contains most hairy code since it does some sort of collision detection. */ /* *---------------------------------------------------------------------- */ public class Bullet extends Moving { /** How may times a bullet can pass the vertical edges. Currently set to 2. */ public static final int maxBorderPass = 2; /** How many pixels a bullet can travel, at most. Used to prevent endless ricochet loops. Measured in Manhattan distance. Currently set to 2000. */ public static final int maxMilage = 2000; private int milage; private float dx, dy; // Bullet speed. private int borderPass; private boolean inObstacle; private int obstacleNo = 0; // Tells in which obstacle the bullet is. private Image texture = Gunfighter.textures[10]; /* *---------------------------------------------------------------------- * * Bullet */ /** All bullets are created by a Player. @see Player */ /* *---------------------------------------------------------------------- */ public Bullet() { dead = true; inObstacle = false; } /** Draws the bullet translated a couple of pixels so coord is the centre of the bullet. */ public void draw(Graphics g) { g.drawImage(texture, coord.x - 2, coord.y - 2, null); } /* *---------------------------------------------------------------------- * * fire */ /** Fires a new bullet. @param x x-coordinate @param y y-coordinate @param s initial horizontal speed */ /* *---------------------------------------------------------------------- */ public void fire(int x, int y, int speed) { int i = 0; dead = false; coord = new Point(x, y); dx = speed; dy = 0; borderPass = 0; milage = 0; /* * Check if the bullet starts in an obstacle. */ while (!inObstacle && (i < Gunfighter.obstacles.length)) { Polygon poly = Gunfighter.obstacles[i].getPolygon(); if (poly.inside(coord.x, coord.y)) { obstacleNo = i; inObstacle = true; } i++; } } /* *---------------------------------------------------------------------- * * update */ /** Updates the bullet. */ /* *---------------------------------------------------------------------- */ public void update() { milage += Math.abs(dx) + Math.abs(dy); if (coord.x > Gunfighter.screenWidth) { borderPass++; coord.x = 0; } else if (coord.x < 0) { borderPass++; coord.x = Gunfighter.screenWidth; } if ((milage > maxMilage) || (borderPass >= maxBorderPass)) { dead = true; } else { action(); } } /* *---------------------------------------------------------------------- * * action * * Moves the bullet and detects collisions with obstacles and actors. * *---------------------------------------------------------------------- */ private void action() { int i = 0; boolean collision = false; int x = Math.round(coord.x + dx); int y = Math.round(coord.y + dy); /* * Test if we hit one of the actors. */ if (Gunfighter.actor[0].getHit().inside(coord.x, coord.y)) { Gunfighter.actor[0].die(); } else if (Gunfighter.actor[1].getHit().inside(coord.x, coord.y)) { Gunfighter.actor[1].die(); /* * Didn't hit an actor */ } else if (!inObstacle) { /* * Not inside an obstacle. * Check the borders. * The status panel is 40 pixels so we have to check * earlier at the lower border. */ if ((coord.y < 0) || (coord.y >= Gunfighter.screenHeight - 42 - dy)) { /* * Play ricochet sound, bounce and accelerate a bit. */ Gunfighter.sfx[1].play(); dy = -((float)1.05 * dy); } else { /* * Check the obstacles. */ while (!collision && (i < Gunfighter.obstacles.length)) { Polygon poly = Gunfighter.obstacles[i].getPolygon(); if (poly.inside(x, y)) { collision=true; // Yes, it's a collision. if (Gunfighter.obstacles[i] instanceof Tree) { deflect(i); } else { bounce(i); } obstacleNo = i; inObstacle = true; } i++; } } } /* * Inside an obstacle. * Check if it still is. */ else if (!Gunfighter.obstacles[obstacleNo].getPolygon().inside(coord.x, coord.y)) { inObstacle = false; } else if (Gunfighter.obstacles[obstacleNo] instanceof Tree) { deflect(obstacleNo); // It's in a tree, continue to deflect. } /* * If the bullet is in an ammo box or rock - do nothing. * This because the actor should be able to stand with his gun * over an obstacle and shoot. */ /* * Really move the bullet. */ coord.translate(Math.round(dx), Math.round(dy)); } /* *---------------------------------------------------------------------- * * bounce * * Tries to bounce the bullet naturally. * The heuristics tries to decide between which points * of the obstacle the bullet passes. * To do this, we make polygons between each pair of adjecent * points and the centre. Then we check in which polygon * the bullet is. * To easier understand this uncomment some lines in * Obstacle.draw(Graphics). * *---------------------------------------------------------------------- */ private void bounce(int obstacleNo) { int closest, closest2; float speed, bulletAngle, normalAngle; int x = Math.round(coord.x + dx); int y = Math.round(coord.y + dy); int i = 0; Obstacle o = Gunfighter.obstacles[obstacleNo]; Polygon p = o.getPolygon(); int n = p.npoints; Polygon helpPoly; Gunfighter.sfx[1].play(); helpPoly = o.helpPoly(i); /* * Accelerate the bullet a bit. */ speed = (float)1.05 * (float)Math.sqrt( (dx * dx + dy * dy)); while (!helpPoly.inside(x, y) && (i < n-2)) { i++; helpPoly = o.helpPoly(i); } /* * A special fix for the most obvious error. * Occurs on one of the big rocks. */ if ( (n == 8) && ((i == 0) || (i + 1 == 7)) ){ if (dx > 0) { closest = 6; closest2 = 7; } else { closest = 0; closest2 = 1; } } else { closest = i; closest2 = i+1; } /* * Bounce the bullet. */ bulletAngle = (float)Math.atan2(dy, dx); normalAngle = (float)Math.atan2(p.ypoints[closest] - p.ypoints[closest2], p.xpoints[closest] - p.xpoints[closest2]); dx = speed * (float)Math.cos(2 * normalAngle - bulletAngle); dy = speed * (float)Math.sin(2 * normalAngle - bulletAngle); } /* *---------------------------------------------------------------------- * * deflect * * Deflect the bullet a few degrees. * The direction depends on the tree's sign. * *---------------------------------------------------------------------- */ private void deflect(int obstacleNo) { float sign = ((Tree)Gunfighter.obstacles[obstacleNo]).getSign(); float bulletAngle = (float)Math.atan2( dy, dx); float speed = (float)Math.sqrt((dx * dx + dy * dy)); dx = speed * (float)Math.cos(bulletAngle + sign * 0.06); dy = speed * (float)Math.sin(bulletAngle + sign * 0.06); } }