package vooga.rts.gamedesign.sprite.gamesprites.interactive; import java.awt.Color; import java.awt.Dimension; import java.awt.GradientPaint; import java.awt.Graphics2D; import java.awt.Image; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import vooga.rts.action.Action; import vooga.rts.action.IActOn; import vooga.rts.ai.AstarFinder; import vooga.rts.ai.Path; import vooga.rts.ai.PathFinder; import vooga.rts.commands.Command; import vooga.rts.commands.InformationCommand; import vooga.rts.gamedesign.sprite.gamesprites.GameEntity; import vooga.rts.gamedesign.sprite.gamesprites.IAttackable; import vooga.rts.gamedesign.sprite.gamesprites.Projectile; import vooga.rts.gamedesign.sprite.gamesprites.Resource; import vooga.rts.gamedesign.sprite.gamesprites.interactive.buildings.Building; import vooga.rts.gamedesign.sprite.gamesprites.interactive.units.Unit; import vooga.rts.gamedesign.state.UnitState; import vooga.rts.gamedesign.strategy.Strategy; import vooga.rts.gamedesign.strategy.attackstrategy.AttackStrategy; import vooga.rts.gamedesign.strategy.attackstrategy.CannotAttack; import vooga.rts.gamedesign.strategy.gatherstrategy.CanGather; import vooga.rts.gamedesign.strategy.gatherstrategy.CannotGather; import vooga.rts.gamedesign.strategy.gatherstrategy.GatherStrategy; import vooga.rts.gamedesign.strategy.occupystrategy.CanBeOccupied; import vooga.rts.gamedesign.strategy.occupystrategy.CannotBeOccupied; import vooga.rts.gamedesign.strategy.occupystrategy.OccupyStrategy; import vooga.rts.gamedesign.strategy.production.CannotProduce; import vooga.rts.gamedesign.strategy.production.ProductionStrategy; import vooga.rts.gamedesign.strategy.upgradestrategy.CannotUpgrade; import vooga.rts.gamedesign.strategy.upgradestrategy.UpgradeStrategy; import vooga.rts.gamedesign.upgrades.UpgradeTree; import vooga.rts.gamedesign.weapon.Weapon; import vooga.rts.state.GameState; import vooga.rts.util.Camera; import vooga.rts.util.DelayedTask; import vooga.rts.util.FilterImageColor; import vooga.rts.util.Information; import vooga.rts.util.Location3D; import vooga.rts.util.Pixmap; import vooga.rts.util.Sound; /** * This class is the extension of GameEntity. It contains the strategies for * attacking upgrading gathering and producing. * * @author Ryan Fishel * @author Kevin Oh * @author Francesco Agosti * @author Wenshun Liu * */ public abstract class InteractiveEntity extends GameEntity implements IAttackable, IActOn { public static final Location3D DEFAULT_LOCATION = new Location3D(0, 0, 0); public static final int DEFAULT_PLAYERID = 0; private static final int LOCATION_OFFSET = 20; private static int DEFAULT_INTERACTIVEENTITY_SPEED = 150; public static final double DEFAULT_BUILD_TIME = 5; public static final int DEFAULT_ARMOR = 10; public static final int MAX_HEALTHBAR_SIZE = 80; private boolean isSelected; private Sound mySound; private AttackStrategy myAttackStrategy; private ProductionStrategy myProductionStrategy; private UpgradeStrategy myUpgradeStrategy; private OccupyStrategy myOccupyStrategy; private GatherStrategy myGatherStrategy; private int myArmor; private Map<String, Action> myActions; private Map<String, Information> myActionInfos; private List<DelayedTask> myTasks; private double myBuildTime; private Information myInfo; private PathFinder myFinder; private Path myPath; private Queue<DelayedTask> myQueueableTasks; private DelayedTask myCurQueueTask; private GameEntity myTargetEntity; private Location3D myRallyPoint; /** * Creates a new interactive entity. * * @param image * is the image of the interactive entity * @param center * is the location of the interactive entity * @param size * is the dimension of the interactive entity * @param sound * is the sound the interactive entity makes * @param teamID * is the ID of the team that the interactive entity is on * @param health * is the health of the interactive entity */ public InteractiveEntity (Pixmap image, Location3D center, Dimension size, Sound sound, int playerID, int health, double buildTime) { super(image, center, size, playerID, health); mySound = sound; myAttackStrategy = new CannotAttack(); myProductionStrategy = new CannotProduce(); myUpgradeStrategy = new CannotUpgrade(); myGatherStrategy = new CannotGather(); myActions = new HashMap<String, Action>(); myActionInfos = new HashMap<String, Information>(); isSelected = false; myTasks = new ArrayList<DelayedTask>(); myBuildTime = buildTime; myOccupyStrategy = new CannotBeOccupied(); myPath = new Path(); myQueueableTasks = new LinkedList<DelayedTask>(); myCurQueueTask = new DelayedTask(0, null); myFinder = new AstarFinder(); myTargetEntity = this; myArmor = DEFAULT_ARMOR; setSpeed(DEFAULT_INTERACTIVEENTITY_SPEED); myRallyPoint = new Location3D(0, 0, 0); } public void changeImageColor () { Image image = getImage().getImage(); if (image != null) { setImage(FilterImageColor.colorImage(image, getPlayerID())); } } /** * Adds an action to myActions with the passed in command and Action */ public void addAction (String command, Action action) { myActions.put(command, action); } /** * removes the action associated with the command * */ public void removeAction (String command) { myActions.remove(command); } /** * Returns the map of all actions of this interactive entity * * @return Map of String to Action */ public Map<String, Action> getActions () { return myActions; } /** * Sets actions to the given map of string to action * * @param actions * , the map to set myActions to */ public void setActions (Map<String, Action> actions) { myActions = actions; } /** * Initialize default actions. will be overriden in subclasses */ public abstract void addDefaultActions (); /** * Adds a new Delayedtask to the list of tasks * * @param dt * the delayed task to be added */ public void addTask (DelayedTask dt) { myTasks.add(dt); } /** * Adds a new Queuabletask like upgrades and production to the linked list * of queueable tasks * * @param dt * the queueable task to be added */ public void addQueueableTask (DelayedTask dt) { myQueueableTasks.add(dt); } /** * Sets the information class of the interactive entity to the passed in * information * * @param info * the Information to be set as the interactive entity's * information */ public void setInfo (Information info) { myInfo = info; } /** * Returns the information class of this interactive entity * * @return the information class */ public Information getInfo () { return myInfo; } @Override public void setPlayerID (int playerID) { this.changeImageColor(); super.setPlayerID(playerID); } /** * Sets the upgradeTree to the passed in upgrade tree * * @param upgradeTree * the upgrade tree to be used */ public void setUpgradeTree (UpgradeTree upgradeTree) { myUpgradeStrategy.setUpgradeTree(upgradeTree, this); } /** * Returns the upgrade tree associated with the interactive entity * * @return the upgradeTree */ public UpgradeTree getUpgradeTree () { return myUpgradeStrategy.getUpgradeTree(); } /** * Returns all the strategies of this interactive entity * * @return an array of all Strategies */ public Strategy[] getStrategies () { Strategy[] all = new Strategy[5]; all[0] = myAttackStrategy; all[1] = myOccupyStrategy; all[2] = myGatherStrategy; all[3] = myProductionStrategy; all[4] = myUpgradeStrategy; return all; } /** * adds passed in command and info into map * * @param command * @param info */ public void addActionInfo (String command, Information info) { myActionInfos.put(command, info); } public void removeActionInfo (String command) { myActionInfos.remove(command); } /** * This method specifies that the interactive entity is attacking an * IAttackable. It checks to see if the IAttackable is in its range, it sets * the state of the interactive entity to attacking, and then it attacks the * IAttackable if the state of the interactive entity lets it attack. * * @param attackable * is the IAttackable that is being attacked. */ public void attack (IAttackable attackable) { double distance = distance(attackable); if (!this.isDead()) { if (!getEntityState().isAttacking()) { if (attackInRange(attackable, distance)) { getEntityState().stop(); this.stopMoving(); } getEntityState().attack(); } if (getEntityState().canAttack()) { myAttackStrategy.attack(attackable, distance); } } } /** * Checks to see if an entity is in attack state and comes in range of * another entity. * * @param attackable * is an enemy entity * @param distance * is the distance an enemy entity is away * @return true if the entity should stop and attack the enemy and false if * it should not */ private boolean attackInRange (IAttackable attackable, double distance) { return getEntityState().inAttackMode() && this.getAttackStrategy().getCurrentWeapon() .inRange((InteractiveEntity) attackable, distance); } /** * Calculates the distance that an enemy is away from this entity. * * @param attackable * is an enemy entity * @return the distance that the enemy is away */ private double distance (IAttackable attackable) { return Math.sqrt(Math.pow(getWorldLocation().getX() - ((InteractiveEntity) attackable).getWorldLocation().getX(), 2) + Math.pow(getWorldLocation().getY() - ((InteractiveEntity) attackable).getWorldLocation().getY(), 2)); } public void calculateDamage (int damage) { int healthLost = damage * (1 - (myArmor / (myArmor + 100))); changeHealth(healthLost); } /** * checks to see if the passed in command is a valid input for an action in * this interactive entity * * @param command */ public boolean containsInput (Command command) { return myActions.containsKey(command.getMethodName()); } /** * Creates a copy of an interactive entity. **/ public abstract InteractiveEntity copy (); /** * Gives "toOther" all the information and strategies attributed to this * class. * * @param toOther */ public void transmitProperties (InteractiveEntity toOther) { toOther.setInfo(getInfo()); for (Strategy s : getStrategies()) { s.copyStrategy(toOther); } } /** * Returns the action that corresponds to a command. * * @param command * is a command that was entered by the player * @return the action the is mapped to the command */ public Action getAction (Command command) { return myActions.get(command.getMethodName()); } /** * returns all the actions this interactive entity is capable of doing * */ public Set<InformationCommand> getCommands () { Set<InformationCommand> infoCommands = new HashSet<InformationCommand>(); if (myActions.isEmpty()) return null; // this needs to be fixed for (String s : myActions.keySet()) { // need to check what type it is...eg it cant be a left click String isMake = s.split(" ")[0]; if (isMake.equals("make")) { // very buggy infoCommands.add(new InformationCommand(s, myActionInfos.get(s))); } else if (isMake.equals("upgrade")) { infoCommands.add(new InformationCommand(s, myActionInfos.get(s))); } else if (isMake.equals("deoccupy")) { infoCommands.add(new InformationCommand(s, myActionInfos.get(s))); } } if (infoCommands.isEmpty()) { return null; } return infoCommands; } public void getOccupied (Unit occupier) { if (occupier.collidesWith(this)) { myOccupyStrategy.getOccupied(this, occupier); } } /** * This method specifies that the interactive entity is getting attacked so * it calls the attack method of the interactive entity on itself. * * @param interactiveEntity * is the interactive entity that is attacking this interactive * entity */ public void getAttacked (InteractiveEntity interactiveEntity) { interactiveEntity.attack(this); } /** * Returns the current attack strategy of the interactive * * @return the current attack strategy */ public AttackStrategy getAttackStrategy () { return myAttackStrategy; } /** * Returns the sound that the interactive entity makes. * * @return the sound of the interactive entity */ public Sound getSound () { return mySound; } /** * Returns the strategy the entity has for producing (CanProduce or * CannotProduce). * * @return the production strategy of the entity */ public ProductionStrategy getProductionStrategy () { return myProductionStrategy; } /** * Returns the upgrade strategy of this interactive entity * * @return the upgrade strategy of the entity */ public UpgradeStrategy getUpgradeStrategy () { return myUpgradeStrategy; } /** * Sets the amount that the worker can gather at a time. * * @param gatherAmount * is the amount that the worker can gather */ public void setGatherAmount (int gatherAmount) { myGatherStrategy.setGatherAmount(gatherAmount); myGatherStrategy = new CanGather(CanGather.DEFAULTCOOL, myGatherStrategy.getGatherAmount()); } /** * The worker gathers the resource if it can and then resets its gather * cooldown. * * @param gatherable * is the resource being gathered. */ public void gather (IGatherable gatherable) { if (this.collidesWith((GameEntity) gatherable)) { myGatherStrategy.gatherResource(getPlayerID(), gatherable); } } /** * Sets the gather strategy of this entity to the passed in gatherStrategy * * @param gatherStrategy */ public void setGatherStrategy (GatherStrategy gatherStrategy) { myGatherStrategy = gatherStrategy; } /** * Returns the gather strategy of this entity */ public GatherStrategy getGatherStrategy () { return myGatherStrategy; } /** * Sets the production strategy of the entity to CanProduce or * CannotProduce. * * @param productionStrategy * is the production strategy the entity will have */ public void setProductionStrategy (ProductionStrategy productionStrategy) { myProductionStrategy = productionStrategy; } /** * Sees whether the passed in InteractiveEntity is an enemy by checking if * player IDs do not match * * @param InteractiveEntity * other - the other InteractiveEntity to compare * @return whether the other InteractiveEntity is an enemy */ public boolean isEnemy (InteractiveEntity other) { return getPlayerID() != other.getPlayerID(); } @Override public void paint (Graphics2D pen) { if (!isVisible()) { return; } Point2D selectLocation = Camera.instance().worldToView(getWorldLocation()); paintHealthBar(pen, selectLocation); if (isSelected) { Ellipse2D.Double selectedCircle = new Ellipse2D.Double(selectLocation.getX() - LOCATION_OFFSET, selectLocation.getY() + LOCATION_OFFSET, 50, 30); pen.fill(selectedCircle); } super.paint(pen); paintProjectiles(pen); } private void paintProjectiles (Graphics2D pen) { if (myAttackStrategy.hasWeapon()) { for (Projectile p : myAttackStrategy.getCurrentWeapon().getProjectiles()) { p.paint(pen); } } } private void paintHealthBar(Graphics2D pen, Point2D selectLocation) { int healthbarSize = (getMaxHealth() / 5 > MAX_HEALTHBAR_SIZE) ? MAX_HEALTHBAR_SIZE : getMaxHealth() / 5; pen.drawRect((int) (selectLocation.getX() - getSize().width * Camera.ISO_HEIGHT), (int) (selectLocation.getY() - getSize().height * Camera.ISO_HEIGHT - LOCATION_OFFSET), healthbarSize, 5); Rectangle2D healthBar = new Rectangle2D.Double( (int) selectLocation.getX() - getSize().width * Camera.ISO_HEIGHT, (int) (selectLocation.getY() - getSize().height * Camera.ISO_HEIGHT - LOCATION_OFFSET), healthbarSize * getHealth() / getMaxHealth(), 5); float width = calculateHealthBarGradient(healthBar); pen.setPaint(new GradientPaint((float) healthBar.getX() - width, (float) healthBar.getMaxY(), Color.RED, (float) healthBar .getMaxX(), (float) healthBar.getMaxY(), Color.GREEN)); pen.fill(healthBar); pen.setColor(Color.black); } private float calculateHealthBarGradient (Rectangle2D healthBar) { float width = (float) (healthBar.getWidth() * (getHealth() / getMaxHealth())); return width; } /** * If the passed in parameter is another GameEntity, checks to see if it is * a Building and can be occupied, checks to see if it is an enemy, and if * so, switches to attack state. Defaults to move to the center of the other * GameEntity * * @param other * - the other GameEntity */ public void recognize (GameEntity other) { myTargetEntity = other; if (other instanceof Resource) { getEntityState().setUnitState(UnitState.GATHER); } if (isEnemy((InteractiveEntity) other)) { getEntityState().setUnitState(UnitState.ATTACK); } else if (other instanceof Building) { getEntityState().setUnitState(UnitState.OCCUPY); } else { getEntityState().setUnitState(UnitState.NO_STATE); } move(other.getWorldLocation()); } /** * If the passed in parameter is type Location3D, moves the * InteractiveEntity to that location * * @param location * - the location to move to */ public void recognize (Location3D location) { move(location); } /*** * Sets the isSelected boolean to the passed in bool value. */ public boolean select (boolean selected) { if (selected && getEntityState().canSelect()) { isSelected = selected; } if (!selected) { isSelected = selected; } return isSelected; } /** * Sets the attack strategy for an interactive. Can set the interactive to * CanAttack or to CannotAttack and then can specify how it would attack. * Also updates the weapons of the strategy to be at the same location of * this entity. * * @param newStrategy * is the new attack strategy that the interactive will have */ public void setAttackStrategy (AttackStrategy newStrategy) { newStrategy.setWeaponLocation(getWorldLocation()); myAttackStrategy = newStrategy; } @Override public void update (double elapsedTime) { updatePath(); super.update(elapsedTime); updateTasks(elapsedTime); updateQueueableTasks(elapsedTime); updateAttack(elapsedTime); if (this instanceof Unit) { updateOccupy(elapsedTime); } getEntityState().update(elapsedTime); getGatherStrategy().update(elapsedTime); setChanged(); notifyObservers(); } /** * Updates the InteractiveEntity by checking if it is able to occupy * another InteractiveEntity. * * @param elapsedTime */ private void updateOccupy (double elapsedTime) { List<InteractiveEntity> shelters = findShelters(); if (!shelters.isEmpty()) { shelters.get(0).getOccupied((Unit) this); } } /** * Finds the InteractiveEntity that are in range of this InteractiveEntity * which also can be occupied. * * @return the list of InteractiveEntity that can be occupied */ private List<InteractiveEntity> findShelters () { List<InteractiveEntity> possibleShelters = GameState.getMap().<InteractiveEntity>getInArea(getWorldLocation(), 10, InteractiveEntity.class, GameState.getPlayers() .getTeamID(getPlayerID()), true); List<InteractiveEntity> actualShelters = new ArrayList<InteractiveEntity>(); for (InteractiveEntity shelter: possibleShelters) { if (shelter.getOccupyStrategy() instanceof CanBeOccupied) { actualShelters.add(shelter); } } return actualShelters; } private void updateAttack (double elapsedTime) { if (myAttackStrategy.hasWeapon()) { Weapon weapon = myAttackStrategy.getCurrentWeapon(); if (getEntityState().inAttackMode()) { ((InteractiveEntity) myTargetEntity).getAttacked(this); removeState(); } else { List<InteractiveEntity> enemies = findEnemies(weapon); if (!enemies.isEmpty()) { enemies.get(0).getAttacked(this); } weapon.update(elapsedTime); } } } private void updateQueueableTasks (double elapsedTime) { if (myCurQueueTask != null) { if (!myCurQueueTask.isActive() && myQueueableTasks.peek() != null) { myCurQueueTask = myQueueableTasks.poll(); } myCurQueueTask.update(elapsedTime); } } private void updateTasks (double elapsedTime) { Iterator<DelayedTask> it = myTasks.iterator(); while (it.hasNext()) { DelayedTask dt = it.next(); dt.update(elapsedTime); if (!dt.isActive()) { it.remove(); } } } private void updatePath () { if (myPath != null) { if (myPath.size() == 0) { setVelocity(getVelocity().getAngle(), 0); getEntityState().stop(); } else { super.move(myPath.getNext()); } } } /** * If the target entity is dead then the unit is set to have no state. For * example if the entity that is being attacked dies, then this entity * switches out of targeting attack mode so that it can find and attack * other units. */ public void removeState () { if (myTargetEntity.isDead()) { getEntityState().setUnitState(UnitState.NO_STATE); } } /** * Finds a list of enemies that are in range of the entity. * * @param weapon * is the weapon that the entity has * @return a list of enemies */ private List<InteractiveEntity> findEnemies (Weapon weapon) { List<InteractiveEntity> enemies = GameState.getMap().<InteractiveEntity> getInArea(getWorldLocation(), weapon.getRange(), InteractiveEntity.class, GameState.getPlayers() .getTeamID(getPlayerID()), false); return enemies; } /* * Test method to add an interactive entity to */ public void addProducable (InteractiveEntity producable) { myProductionStrategy.addProducable(producable); } @Override public void updateAction (Command command) { if (myActions.containsKey(command.getMethodName())) { Action action = myActions.get(command.getMethodName()); action.update(command); } } /** * Sets the object to be in the changed state for the observer pattern. */ public void setChanged () { super.setChanged(); } /** * Gets the occupy strategy of the entity (either CanBeOccupied or * CannotBeOccupied). * * @return */ public OccupyStrategy getOccupyStrategy () { return myOccupyStrategy; } /** * Sets the occupy strategy for the entity to be CanBeOccupied or * CannotBeOccupied. * * @param occupyStrategy * is the occupy strategy that the entity is being set to */ public void setOccupyStrategy (OccupyStrategy occupyStrategy) { myOccupyStrategy = occupyStrategy; } /** * Returns the time it takes to create the entity. * * @return how long it takes to make the entity */ public double getBuildTime () { return myBuildTime; } /** * Sets how long the build time is for the entity. * * @param time * is the amount of time it will take to create the entity */ public void setBuildTime (double time) { myBuildTime = time; } @Override public void move (Location3D loc) { final Location3D temp = new Location3D(loc); myPath = null; findpath(temp); } private void findpath (Location3D destination) { myPath = GameState.getMap().getPath(myFinder, getWorldLocation(), destination); if (myPath != null) { setRallyPoint(); } } /** * Adds a weapon to the list of weapons in this entity * * @param toAdd * the weapon to be added */ public void addWeapon (Weapon toAdd) { toAdd.setCenter(getWorldLocation()); myAttackStrategy.addWeapon(toAdd); } /** * Sets the upgrade strategy to the passed in strategy */ public void setUpgradeStrategy (UpgradeStrategy upgradeStrategy) { myUpgradeStrategy = upgradeStrategy; } /** * Returns the target entity of this entity. In other words, if an entity is * right clicked on, that entity becomes the target entity which is returned * from this method. * * @return the target interactive entity */ public GameEntity getTargetEntity () { return myTargetEntity; } /** * Sets the target entity that this entity should act on. * * @param entity * is the target entity */ public void setTargetEntity (GameEntity entity) { myTargetEntity = entity; } /** * Sets the armor of the entity. * * @param armor * is the amount of armor the entity will have */ public void setArmor (int armor) { myArmor = armor; } /** * Returns the rally point of the production building. Will be used to move * the newly created units to * * @return myRallyPoint, the rally point of the production building */ public Location3D getRallyPoint () { return myRallyPoint; } /** * Sets the rally point of the production building * * @param rallyPoint * the location of the new rally point */ public void setRallyPoint (Location3D rallyPoint) { myRallyPoint = rallyPoint; } /** * Sets the rally point of the entity that can produce so that units will * move to that point after they are created by the entity. * * @param rallyPoint * is the location where the units will go when they are created */ public void setRallyPoint () { myRallyPoint = new Location3D(getWorldLocation().getX(), getWorldLocation().getY() + getHeight(), 0); } }