/* * Copyright (c) 2012. HappyDroids LLC, All rights reserved. */ package com.happydroids.droidtowers.entities; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.utils.Array; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.happydroids.droidtowers.TowerAssetManager; import com.happydroids.droidtowers.TowerConsts; import com.happydroids.droidtowers.controllers.AvatarSteeringManager; import com.happydroids.droidtowers.controllers.PathSearchManager; import com.happydroids.droidtowers.generators.NameGenerator; import com.happydroids.droidtowers.grid.GameGrid; import com.happydroids.droidtowers.grid.GridPosition; import com.happydroids.droidtowers.math.Direction; import com.happydroids.droidtowers.math.GridPoint; import com.happydroids.droidtowers.pathfinding.TransitPathFinder; import com.happydroids.droidtowers.utils.Random; import com.happydroids.error.ErrorUtil; import javax.annotation.Nullable; import java.util.Iterator; import java.util.LinkedList; import java.util.Set; import static com.happydroids.droidtowers.controllers.AvatarState.MOVING; import static com.happydroids.droidtowers.controllers.AvatarState.USING_STAIRS; import static com.happydroids.droidtowers.types.ProviderType.COMMERCIAL; import static com.happydroids.droidtowers.types.ProviderType.FOOD; public class Avatar extends GameObject { public static final float FRAME_DURATION = 0.25f; public static final float WALKING_ANIMATION_DURATION = FRAME_DURATION * 3; private static final Set<Color> colors = Sets.newHashSet(Color.GREEN, Color.RED, Color.ORANGE, Color.MAGENTA, Color.PINK, Color.YELLOW); private static Iterator<Color> colorIterator = Iterables.cycle(colors).iterator(); private final Animation walkAnimation; private float walkAnimationTime; private AvatarSteeringManager steeringManager; protected final GameGrid gameGrid; private boolean isEmployed; private boolean isResident; private float satisfactionShops; private float satisfactionFood; protected GridObject movingTo; private TransitPathFinder pathFinder; private Room home; private float hungerLevel; private LinkedList<Object> lastVisitedPlaces; private float lastSearchedForHome = Float.MAX_VALUE; private boolean wanderingAround; private final String name; private float timeUntilPathSearch; public Avatar(final GameGrid gameGrid) { super(); this.gameGrid = gameGrid; name = NameGenerator.randomMaleName(); setPosition(-Random.randomInt(50, 200), TowerConsts.GROUND_HEIGHT); setColor(colorIterator.next()); TextureAtlas droidAtlas = getTextureAtlas(); TextureAtlas.AtlasRegion stationary = droidAtlas.findRegion(addFramePrefix("stationary")); setSize(stationary.originalWidth, stationary.originalHeight); setRegion(stationary); setOrigin(stationary.originalWidth / 2, 0); // setVisible(false); walkAnimation = new Animation(FRAME_DURATION, droidAtlas.findRegions(addFramePrefix("walk"))); lastVisitedPlaces = Lists.newLinkedList(); satisfactionFood = 1f; satisfactionShops = 1f; pathFinder = new TransitPathFinder(this.gameGrid, this instanceof Janitor); pathFinder.setCompleteCallback(new Runnable() { @Override public void run() { pathFinderComplete(); } }); steeringManager = new AvatarSteeringManager(this, this.gameGrid); } private void pathFinderComplete() { if (pathFinder.isFinished()) { steeringManager.setPath(pathFinder.getDiscoveredPath()); steeringManager.start(); } } protected String addFramePrefix(String frameName) { return "droid/" + frameName; } protected TextureAtlas getTextureAtlas() { return TowerAssetManager.textureAtlas("characters.txt"); } private void beginNextAction() { try { wanderAround(); if (timeUntilPathSearch > 0) { return; } if (!pathFinder.isWorking()) { if (hungerLevel <= 0.5f) { GridObject closestFood = searchForFood(); navigateToGridObject(closestFood); } else { if (home != null && !lastVisitedPlaces.contains(home)) { navigateToGridObject(home); } else { findPlaceToVisit(); } } } } catch (Throwable throwable) { ErrorUtil.sendErrorToServer(throwable); } } protected void findPlaceToVisit() { Array<GridObject> gridObjects = gameGrid.getObjects(); if (gridObjects.size == 1) { navigateToGridObject(gridObjects.get(0)); } else if (gridObjects.size > 0) { int idx = 0; for (GridObject gridObject : gridObjects) { if (gridObject.provides(COMMERCIAL) && gridObject.getDirtLevel() < 1f) { navigateToGridObject(gridObject); break; } } } } protected void wanderAround() { GridPosition start = gameGrid.positionCache().getPosition(gameGrid.closestGridPoint(getX(), getY())); Array<GridPosition> discoveredPath = new Array<GridPosition>(5); GridPoint gridSize = gameGrid.getGridSize(); if (start.y == TowerConsts.LOBBY_FLOOR) { discoveredPath.add(gameGrid.positionCache() .getPosition(Random.randomInt(0, gridSize.x), TowerConsts.LOBBY_FLOOR)); discoveredPath.add(gameGrid.positionCache() .getPosition(Random.randomInt(0, gridSize.x), TowerConsts.LOBBY_FLOOR)); } else { for (int i = 1; i < 5; i++) { GridPosition positionRight = gameGrid.positionCache().getPosition(start.x + i, start.y); if (positionRight != null && !positionRight.isEmpty()) { if (positionRight.elevator == null) { discoveredPath.add(positionRight); } } else { break; } } for (int i = 1; i < 5; i++) { GridPosition positionLeft = gameGrid.positionCache().getPosition(start.x - i, start.y); if (positionLeft != null && !positionLeft.isEmpty()) { if (positionLeft.elevator == null) { discoveredPath.add(positionLeft); } } else { break; } } } steeringManager.setPath(discoveredPath); steeringManager.start(); } protected void navigateToGridObject(GridObject gridObject) { cancelMovement(); if (gridObject == null) { wanderAround(); return; } movingTo = gridObject; if (movingTo != null) { movingTo.addToVisitorQueue(this); } pathFinder.setStart(gameGrid.positionCache().getPosition(gameGrid.closestGridPoint(getX(), getY()))); pathFinder.setGoal(gameGrid.positionCache().getPosition(gridObject.getPosition())); pathFinder.start(); if (this instanceof Janitor) { while (pathFinder.isWorking()) { pathFinder.step(); } pathFinder.runCompleteCallback(); } else { PathSearchManager.instance().queue(pathFinder); } } public void afterReachingTarget() { if (movingTo != null) { movingTo.recordVisitor(this); lastVisitedPlaces.add(movingTo); if (lastVisitedPlaces.size() > 3) { lastVisitedPlaces.poll(); } if (movingTo.provides(FOOD)) { hungerLevel = 1f; } } movingTo = null; timeUntilPathSearch = PathSearchManager.instance().queueLength() > 5 ? 5f + MathUtils.random(1, 5f) : 0f; } @Override public void update(float delta) { super.update(delta); timeUntilPathSearch -= delta; if (home == null) { lastSearchedForHome += delta; if (lastSearchedForHome > 10f) { lastSearchedForHome = 0f; searchForAHome(); } } hungerLevel -= 0.001f * delta; if (!steeringManager.isRunning()) { beginNextAction(); } else { if ((steeringManager.getCurrentState() & MOVING) != 0 || (steeringManager.getCurrentState() & USING_STAIRS) != 0) { walkAnimationTime += delta; if (walkAnimationTime >= WALKING_ANIMATION_DURATION) { walkAnimationTime = 0f; } TextureRegion keyFrame = walkAnimation.getKeyFrame(walkAnimationTime, true); setRegion(keyFrame); flip(steeringManager.horizontalDirection() == Direction.LEFT, false); } } } private GridObject searchForFood() { Array<GridObject> commercialSpaces = gameGrid.getInstancesOf(CommercialSpace.class); if (commercialSpaces.size == 0) { return null; } final int avatarX = (int) Avatar.this.getX(); final int avatarY = (int) Avatar.this.getY(); GridObject closest = null; int closestDist = Integer.MAX_VALUE; for (GridObject gridObject : commercialSpaces) { CommercialSpace commercialSpace = (CommercialSpace) gridObject; if (commercialSpace.getVisitorQueueSize() >= 5 || commercialSpace.getEmploymentLevel() > 0.0f) { continue; } int distanceFromAvatar = commercialSpace.getPosition().dst(avatarX, avatarY); if (distanceFromAvatar < closestDist) { closest = commercialSpace; } } if (lastVisitedPlaces.contains(closest) || closest == null) { satisfactionFood = MathUtils.clamp(satisfactionFood - 0.15f, 0f, 1f); } else { satisfactionFood = MathUtils.clamp(satisfactionFood + 0.15f, 0f, 1f); } return closest; } public void cancelMovement() { if (steeringManager != null) { steeringManager.finished(); } if (pathFinder != null) { PathSearchManager.instance().remove(pathFinder); } if (movingTo != null) { movingTo.removeFromVisitorQueue(this); } timeUntilPathSearch = 5f + MathUtils.random(1, 5f); } public void searchForAHome() { if (home != null) { return; } Array<GridObject> rooms = gameGrid.getInstancesOf(Room.class); if (rooms != null && rooms.size > 0) { GridObject mostDesirable = rooms.get(0); for (GridObject gridObject : rooms) { Room room = (Room) gridObject; if (room.isConnectedToTransport() && room.getNumSupportedResidents() > 0) { if (room.getNumResidents() == 0 || (room.getNumResidents() < room.getNumSupportedResidents() && mostDesirable.getDesirability() < room.getDesirability())) { mostDesirable = room; } } } setHome(mostDesirable); } } public void setHome(GridObject newHome) { home = (Room) newHome; if (home != null && home.addResident(this)) { cancelMovement(); setPosition(home.getWorldCenterBottom()); } } public void recalculateCurrentPath() { cancelMovement(); if (movingTo != null) { navigateToGridObject(movingTo); } else { wanderAround(); } } public static final Predicate<GridObject> AVATAR_HOME_FILTER = new Predicate<GridObject>() { @Override public boolean apply(@Nullable GridObject input) { if (input instanceof Room) { Room room = (Room) input; return room.isConnectedToTransport() && (room.getNumResidents() == 0 || room.getNumResidents() < room.getNumSupportedResidents()); } return false; } }; @Override public void markToRemove(boolean b) { super.markToRemove(b); cancelMovement(); } public String getName() { return name; } public GridObject getMovementTarget() { return movingTo; } public float getHungerLevel() { return hungerLevel; } public float getSatisfactionFood() { return satisfactionFood; } public float getSatisfactionShops() { return satisfactionShops; } public void murderDeathKill187() { cancelMovement(); if (home != null) { home.removeResident(this); } markToRemove(true); } }