/******************************************************************************* * Copyright (c) 2001, 2010 Mathew A. Nelson and Robocode contributors * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://robocode.sourceforge.net/license/epl-v10.html * * Contributors: * Mathew A. Nelson * - Initial API and implementation * Flemming N. Larsen * - Code cleanup & optimizations * - Bugfix: checkBulletCollision() now uses a workaround for the Java 5 bug * #6457965 with Line2D.intersectsLine via intersect(Line2D.Double line) * - Integration of robocode.Rules * - Replaced width and height with radius * - Added constructor for the BulletRecord to support the replay feature * - Fixed synchonization issues on member fields and methods * - Some private methods were declared public, and have therefore been * redeclared as private * - Replaced getting the number of explosion frames from image manager with * integer constant * - Removed hitTime and resetHitTime(), which is handled thru frame instead * - Added getExplosionLength() to get the exact number of explosion frames * for this class and sub classes * - The update() method is now removing the bullet from the battle field, * when the bullet reaches the inactive state (i.e. is finished) * - Bugfix: Changed the delta coordinates of a bullet explosion on a robot, * so that it will be on the true bullet line for all bullet events * - The coordinates of the bullet when it hits, and the coordinates for the * explosion rendering on a robot has been split. So now the bullet is * painted using the new getPaintX() and getPaintY() methods * Luis Crespo * - Added states * Robert D. Maupin * - Replaced old collection types like Vector and Hashtable with * synchronized List and HashMap * Titus Chen * - Bugfix: Added Battle parameter to the constructor that takes a * BulletRecord as parameter due to a NullPointerException that was raised * as the battleField variable was not intialized * Pavel Savara * - disconnected from Bullet, now we rather send BulletStatus to proxy side *******************************************************************************/ package net.sf.robocode.battle.peer; import net.sf.robocode.peer.BulletStatus; import robocode.*; import robocode.control.snapshot.BulletState; import java.awt.geom.Line2D; import java.awt.geom.Line2D.Double; import static java.lang.Math.cos; import static java.lang.Math.sin; import java.util.List; /** * @author Mathew A. Nelson (original) * @author Flemming N. Larsen (contributor) * @author Luis Crespo (contributor) * @author Robert D. Maupin (contributor) * @author Titus Chen (constributor) */ public class BulletPeer implements ProjectilePeer { private static final int EXPLOSION_LENGTH = 17; private static final int RADIUS = 3; protected final RobotPeer owner; private final BattleRules battleRules; private final int bulletId; protected RobotPeer victim; protected BulletState state; private double heading; protected double x; protected double y; private double lastX; private double lastY; protected double power; private double deltaX; private double deltaY; private final Line2D.Double boundingLine = new Line2D.Double(); protected int frame = -1; private final int color; protected int explosionImageIndex; public BulletPeer(RobotPeer owner, BattleRules battleRules, int bulletId) { super(); this.owner = owner; this.battleRules = battleRules; this.bulletId = bulletId; state = BulletState.FIRED; color = owner.getBulletColor(); // Store current bullet color set on robot } public void checkCollision(List<? extends ProjectilePeer> projectiles) { for (ProjectilePeer b : projectiles) { if (b != null && b != this && b.isActive() && intersect(b.getBoundingLine())) { state = BulletState.HIT_BULLET; b.setState(BulletState.HIT_BULLET); b.setFrame(0); frame = 0; x = lastX; y = lastY; b.setX(b.getLastX()); b.setY(b.getLastY()); Bullet thisBullet = createBullet(); Bullet otherBullet = null; if (b instanceof BulletPeer) { otherBullet = ((BulletPeer) b).createBullet(); owner.addEvent(new BulletHitBulletEvent(thisBullet, otherBullet)); b.getOwner().addEvent(new BulletHitBulletEvent(otherBullet, thisBullet)); } // When introducing new projectiles, add those here! break; } } } public double getLastX() { return lastX; } public double getLastY() { return lastY; } public void setFrame(int frame) { this.frame = frame; } private Bullet createBullet() { return new Bullet(heading, x, y, power, owner == null ? null : owner.getName(), victim == null ? null : victim.getName(), isActive(), bulletId); } private BulletStatus createStatus() { return new BulletStatus(bulletId, x, y, victim == null ? null : victim.getName(), isActive()); } // Workaround for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6457965 private boolean intersect(Line2D.Double line) { double x1 = line.x1, x2 = line.x2, x3 = boundingLine.x1, x4 = boundingLine.x2; double y1 = line.y1, y2 = line.y2, y3 = boundingLine.y1, y4 = boundingLine.y2; double dx13 = (x1 - x3), dx21 = (x2 - x1), dx43 = (x4 - x3); double dy13 = (y1 - y3), dy21 = (y2 - y1), dy43 = (y4 - y3); double dn = dy43 * dx21 - dx43 * dy21; double ua = (dx43 * dy13 - dy43 * dx13) / dn; double ub = (dx21 * dy13 - dy21 * dx13) / dn; return (ua >= 0 && ua <= 1) && (ub >= 0 && ub <= 1); } private void checkRobotCollision(List<RobotPeer> robots) { for (RobotPeer otherRobot : robots) { if (!(otherRobot == null || otherRobot == owner || otherRobot.isDead()) && otherRobot.getBoundingBox().intersectsLine(boundingLine)) { double damage = Rules.getBulletDamage(power); double score = damage; if (score > otherRobot.getEnergy()) { score = otherRobot.getEnergy(); } otherRobot.updateEnergy(-damage); boolean teamFire = (owner.getTeamPeer() != null && owner.getTeamPeer() == otherRobot.getTeamPeer()); if (!teamFire) { owner.getRobotStatistics().scoreBulletDamage(otherRobot.getName(), score); } if (otherRobot.getEnergy() <= 0) { if (otherRobot.isAlive()) { otherRobot.kill(); if (!teamFire) { final double bonus = owner.getRobotStatistics().scoreBulletKill(otherRobot.getName()); if (bonus > 0) { owner.println( "SYSTEM: Bonus for killing " + (owner.getNameForEvent(otherRobot) + ": " + (int) (bonus + .5))); } } } } owner.updateEnergy(Rules.getBulletHitBonus(power)); Bullet bullet = createBullet(); otherRobot.addEvent( new HitByBulletEvent( robocode.util.Utils.normalRelativeAngle(heading + Math.PI - otherRobot.getBodyHeading()), bullet)); state = BulletState.HIT_VICTIM; owner.addEvent(new BulletHitEvent(otherRobot.getName(), otherRobot.getEnergy(), bullet)); frame = 0; victim = otherRobot; double newX, newY; if (otherRobot.getBoundingBox().contains(lastX, lastY)) { newX = lastX; newY = lastY; setX(newX); setY(newY); } else { newX = x; newY = y; } deltaX = newX - otherRobot.getX(); deltaY = newY - otherRobot.getY(); break; } } } private void checkWallCollision() { if ((x - RADIUS <= 0) || (y - RADIUS <= 0) || (x + RADIUS >= battleRules.getBattlefieldWidth()) || (y + RADIUS >= battleRules.getBattlefieldHeight())) { state = BulletState.HIT_WALL; frame = 0; owner.addEvent(new BulletMissedEvent(createBullet())); } } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#getId() */ public int getId() { return bulletId; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#getFrame() */ public int getFrame() { return frame; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#getHeading() */ public double getHeading() { return heading; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#getOwner() */ public RobotPeer getOwner() { return owner; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#getPower() */ public double getPower() { return power; } public double getVelocity() { return Rules.getBulletSpeed(power); } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#getVictim() */ public RobotPeer getVictim() { return victim; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#getX() */ public double getX() { return x; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#getY() */ public double getY() { return y; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#getPaintX() */ public double getPaintX() { return (state == BulletState.HIT_VICTIM && victim != null) ? victim.getX() + deltaX : x; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#getPaintY() */ public double getPaintY() { return (state == BulletState.HIT_VICTIM && victim != null) ? victim.getY() + deltaY : y; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#isActive() */ public boolean isActive() { return state.getValue() <= BulletState.MOVING.getValue(); } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#getState() */ public BulletState getState() { return state; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#getColor() */ public int getColor() { return color; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#setHeading(double) */ public void setHeading(double newHeading) { heading = newHeading; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#setPower(double) */ public void setPower(double newPower) { power = newPower; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#setVictim(net.sf.robocode.battle.peer.RobotPeer) */ public void setVictim(RobotPeer newVictim) { victim = newVictim; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#setX(double) */ public void setX(double newX) { x = lastX = newX; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#setY(double) */ public void setY(double newY) { y = lastY = newY; } public void setState(BulletState newState) { state = newState; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#update(java.util.List, java.util.List) */ public void update(List<RobotPeer> robots, List<? extends ProjectilePeer> bullets) { if (isActive()) { frame++; updateMovement(); if (bullets != null) { checkCollision(bullets); } if (isActive()) { checkRobotCollision(robots); } if (isActive()) { checkWallCollision(); } } else if (state == BulletState.HIT_VICTIM || state == BulletState.HIT_BULLET) { frame++; } updateBulletState(); owner.addBulletStatus(createStatus()); } protected void updateBulletState() { switch (state) { case FIRED: if (frame == 1) { state = BulletState.MOVING; } break; case HIT_BULLET: case HIT_VICTIM: case EXPLODED: if (frame >= getExplosionLength()) { state = BulletState.INACTIVE; } break; case HIT_WALL: state = BulletState.INACTIVE; break; } } private void updateMovement() { lastX = x; lastY = y; double v = getVelocity(); x += v * sin(heading); y += v * cos(heading); boundingLine.setLine(lastX, lastY, x, y); } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#nextFrame() */ public void nextFrame() { frame++; } /* (non-Javadoc) * @see net.sf.robocode.battle.peer.ProjectilePeer#getExplosionImageIndex() */ public int getExplosionImageIndex() { return explosionImageIndex; } protected int getExplosionLength() { return EXPLOSION_LENGTH; } @Override public String toString() { return getOwner().getName() + " V" + getVelocity() + " *" + (int) power + " X" + (int) x + " Y" + (int) y + " H" + heading + " " + state.toString(); } public Double getBoundingLine() { return boundingLine; } }