/** * Copyright (c) 2001-2017 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 */ 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 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) * @author Pavel Savara (constributor) */ public class BulletPeer { 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; // Do not set to -1 private final int color; protected int explosionImageIndex; // Do not set to -1 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 } private void checkBulletCollision(List<BulletPeer> bullets) { for (BulletPeer b : bullets) { if (b != null && b != this && b.isActive() && intersect(b.boundingLine)) { // Check if one of the bullets belongs to a sentry robot and is within the safe zone if (owner.isSentryRobot() || b.getOwner().isSentryRobot()) { int sentryBorderSize = battleRules.getSentryBorderSize(); if (x > sentryBorderSize && x < (battleRules.getBattlefieldWidth() - sentryBorderSize) && y > sentryBorderSize && y < (battleRules.getBattlefieldHeight() - sentryBorderSize)) { continue; // Continue, as the sentry should not interfere with bullets in the safe zone } } state = BulletState.HIT_BULLET; frame = 0; x = lastX; y = lastY; b.state = BulletState.HIT_BULLET; b.frame = 0; b.x = b.lastX; b.y = b.lastY; // Bugfix #366 owner.addEvent(new BulletHitBulletEvent(createBullet(false), b.createBullet(true))); b.owner.addEvent(new BulletHitBulletEvent(b.createBullet(false), createBullet(true))); break; } } } private Bullet createBullet(boolean hideOwnerName) { String ownerName = (owner == null) ? null : (hideOwnerName ? getNameForEvent(owner) : owner.getName()); String victimName = (victim == null) ? null : (hideOwnerName ? victim.getName() : getNameForEvent(victim)); return new Bullet(heading, x, y, power, ownerName, victimName, isActive(), bulletId); } private BulletStatus createStatus() { return new BulletStatus(bulletId, x, y, victim == null ? null : getNameForEvent(victim), isActive()); } private String getNameForEvent(RobotPeer otherRobot) { if (battleRules.getHideEnemyNames() && !owner.isTeamMate(otherRobot)) { return otherRobot.getAnnonymousName(); } return otherRobot.getName(); } // 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)) { state = BulletState.HIT_VICTIM; frame = 0; victim = otherRobot; double damage = Rules.getBulletDamage(power); if (owner.isSentryRobot()) { if (victim.isSentryRobot()) { damage = 0; } else { int range = battleRules.getSentryBorderSize(); if (x > range && x < (battleRules.getBattlefieldWidth() - range) && y > range && y < (battleRules.getBattlefieldHeight() - range)) { damage = 0; } } } double score = damage; if (score > otherRobot.getEnergy()) { score = otherRobot.getEnergy(); } otherRobot.updateEnergy(-damage); boolean teamFire = (owner.getTeamPeer() != null && owner.getTeamPeer() == otherRobot.getTeamPeer()); if (!teamFire && !otherRobot.isSentryRobot()) { owner.getRobotStatistics().scoreBulletDamage(otherRobot.getName(), score); } if (otherRobot.getEnergy() <= 0 && otherRobot.isAlive()) { otherRobot.kill(); if (!teamFire && !otherRobot.isSentryRobot()) { double bonus = owner.getRobotStatistics().scoreBulletKill(otherRobot.getName()); if (bonus > 0) { owner.println( "SYSTEM: Bonus for killing " + (owner.getNameForEvent(otherRobot) + ": " + (int) (bonus + .5))); } } } if (!victim.isSentryRobot()) { owner.updateEnergy(Rules.getBulletHitBonus(power)); } otherRobot.addEvent( new HitByBulletEvent( robocode.util.Utils.normalRelativeAngle(heading + Math.PI - otherRobot.getBodyHeading()), createBullet(true))); // Bugfix #366 owner.addEvent( new BulletHitEvent(owner.getNameForEvent(otherRobot), otherRobot.getEnergy(), createBullet(false))); // Bugfix #366 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(false))); // Bugfix #366 } } public int getBulletId() { return bulletId; } public int getFrame() { return frame; } public double getHeading() { return heading; } public RobotPeer getOwner() { return owner; } public double getPower() { return power; } public double getVelocity() { return Rules.getBulletSpeed(power); } public RobotPeer getVictim() { return victim; } public double getX() { return x; } public double getY() { return y; } public double getPaintX() { return (state == BulletState.HIT_VICTIM && victim != null) ? victim.getX() + deltaX : x; } public double getPaintY() { return (state == BulletState.HIT_VICTIM && victim != null) ? victim.getY() + deltaY : y; } public boolean isActive() { return state.isActive(); } public BulletState getState() { return state; } public int getColor() { return color; } public void setHeading(double newHeading) { heading = newHeading; } public void setPower(double newPower) { power = newPower; } public void setVictim(RobotPeer newVictim) { victim = newVictim; } public void setX(double newX) { x = lastX = newX; } public void setY(double newY) { y = lastY = newY; } public void setState(BulletState newState) { state = newState; } public void update(List<RobotPeer> robots, List<BulletPeer> bullets) { frame++; if (isActive()) { updateMovement(); checkWallCollision(); if (isActive()) { checkRobotCollision(robots); } if (isActive() && bullets != null) { checkBulletCollision(bullets); } } updateBulletState(); owner.addBulletStatus(createStatus()); } protected void updateBulletState() { switch (state) { case FIRED: // Note that the bullet must be in the FIRED state before it goes to the MOVING state if (frame > 0) { state = BulletState.MOVING; } break; case HIT_BULLET: case HIT_VICTIM: case HIT_WALL: case EXPLODED: // Note that the bullet explosion must be ended before it goes into the INACTIVE state if (frame >= getExplosionLength()) { state = BulletState.INACTIVE; } break; default: } } private void updateMovement() { lastX = x; lastY = y; double v = getVelocity(); x += v * sin(heading); y += v * cos(heading); boundingLine.setLine(lastX, lastY, x, y); } 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(); } }