BuildingsMap.java
package com.vikingz.unitycoon.building;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.vikingz.unitycoon.achievements.IndecisiveAchievement;
import com.vikingz.unitycoon.building.buildings.AcademicBuilding;
import com.vikingz.unitycoon.building.buildings.AccommodationBuilding;
import com.vikingz.unitycoon.building.buildings.FoodBuilding;
import com.vikingz.unitycoon.building.buildings.RecreationalBuilding;
import com.vikingz.unitycoon.global.GameGlobals;
import com.vikingz.unitycoon.render.BackgroundRenderer;
/**
* This new class is used to store and manage the placed buildings.
* This has been added to help with organisation of code.
*/
public class BuildingsMap {
final List<Building> placedBuildings;
// Used by check collision routine to check collision with background
final BackgroundRenderer backgroundRenderer;
public BuildingsMap(BackgroundRenderer backgroundRenderer) {
placedBuildings = new ArrayList<>();
this.backgroundRenderer = backgroundRenderer;
}
public List<Building> getPlacedBuildings() {
return placedBuildings;
}
/**
* Returns a random building from the list of placed buildings.
* This new method was added to help complete UR_EVENTS.
* @return Building chosen.
*/
public Building chooseRandomBuilding() {
if (placedBuildings.size() == 0) {
return null;
}
Random rand = new Random();
int buildingIndex = rand.nextInt(0,placedBuildings.size());
return placedBuildings.get(buildingIndex);
}
/**
* Attempt to add a new building to the map. This method handles checking collision information
* and funds.
* @param buildingInfo info of building to add.
* @param buildingTexture texture of building to add.
* @param x x-coordinate of position to add building to.
* @param y y-coordinate of position to add building to.
* @return List<Building> Empty if unsuccessful. Contains placed building otherwise.
*/
public List<Building> attemptAddBuilding(BuildingInfo buildingInfo, TextureRegion buildingTexture,
float x, float y) {
return attemptAddBuilding(buildingInfo, buildingTexture, x, y, false);
}
/**
* Attempt to add a new building to the map. This method handles checking collision information
* and funds.
* This new function was added to allow buildings to be placed and pricing to be ignored for
* testing.
* The original function was also refactored to allow new satisfaction and money handlers to be
* used. This helps with FR_SATISFACTION and FR_STUDENT_FINANCE.
* @param buildingInfo info of building to add.
* @param buildingTexture texture of building to add.
* @param x x-coordinate of position to add building to.
* @param y y-coordinate of position to add building to.
* @param ignoreCost Used for testing to ignore any tests related to cost.
* @return List<Building> Empty if unsuccessful. Contains placed building otherwise.
*/
public List<Building> attemptAddBuilding(BuildingInfo buildingInfo, TextureRegion buildingTexture,
float x, float y, boolean ignoreCost) {
List<Building> addedBuildings = new LinkedList<>();
if (checkCollisions(x, y)) {
// Check if the user has enough money to buy that building
if (!ignoreCost && !BuildingStats.nextBuildingFree) {
// Check that user is able to withdraw funds to build building
if (!GameGlobals.MONEY.withdraw(buildingInfo.getBuildingCost())) {
return addedBuildings; // Added buildings will simply be empty at this point
}
}
// Ensures next building is not free.
BuildingStats.nextBuildingFree = false;
// Adds a building of the correct type to the list of buildings that are to be drawn
// to the screen.
switch (buildingInfo.getBuildingType()) {
case ACADEMIC:
addedBuildings.add(addPlacedBuilding(new AcademicBuilding(buildingTexture, x, y,
buildingInfo, buildingInfo.getCoinsPerSecond())));
break;
case ACCOMODATION:
addedBuildings.add(addPlacedBuilding(new AccommodationBuilding(buildingTexture, x, y,
buildingInfo, buildingInfo.getCoinsPerSecond())));
break;
case RECREATIONAL:
addedBuildings.add(addPlacedBuilding(new RecreationalBuilding(buildingTexture, x, y,
buildingInfo, buildingInfo.getCoinsPerSecond())));
break;
case FOOD:
addedBuildings.add(addPlacedBuilding(new FoodBuilding(buildingTexture, x, y, buildingInfo,
buildingInfo.getCoinsPerSecond())));
break;
default:
break;
}
}
return addedBuildings;
}
/**
* Updates stats to reflect building being built.
* This new function was added to allow a delay between the user buying a building and that building
* havingh affects on the game. This was to help with FR_BUILD_TIME.
* @param building Building which has finished building.
*/
public void builtBuilding(Building building) {
GameGlobals.STUDENTS += building.getBuildingInfo().getNumberOfStudents();
incrementBuildingsCount(building.getBuildingInfo().getBuildingType());
GameGlobals.SATISFACTION.recalculateSatisfaction(getPlacedBuildings());
}
/**
* Adds a new building to the list of placed buildings by y coordinate rendered in the correct order.
* This new method ensures buildings placed infront are displayed infront.
*/
public Building addPlacedBuilding(Building newBuilding) {
boolean isadded = false;
for (int i = 0; i < placedBuildings.size(); i++) {
if (placedBuildings.get(i).getY() < newBuilding.getY()) {
placedBuildings.add(i, newBuilding);
isadded = true;
break;
}
}
if (!isadded) {
placedBuildings.add(newBuilding);
}
return newBuilding;
}
/**
* Attempt to remove a building from the list of placed buildings.
* This method was refactored to allow new satisfaction and money handlers to be used.
* This helped to complete FR_SATISFACTION and FR_STUDENT_FINANCE.
* @param toRemove Building object to remove
* @return List<Building>. Empty if unsuccessful, otherwise contains the removed building
*/
public List<Building> attemptBuildingDelete(Building toRemove) {
List<Building> removed = new LinkedList<>();
if (toRemove != null) {
BuildingInfo buildingInfo = toRemove.getBuildingInfo();
placedBuildings.remove(toRemove);
GameGlobals.MONEY.deposit(Math.round(buildingInfo.getBuildingCost()*0.75f));
if (!toRemove.getConstructing()) {
GameGlobals.STUDENTS -= buildingInfo.getNumberOfStudents();
decrementBuildingsCount(buildingInfo.getBuildingType());
}
removed.add(toRemove);
GameGlobals.SATISFACTION.recalculateSatisfaction(getPlacedBuildings());
}
return removed;
}
/**
* Attempt to delete a building from the map at the specified coordinates.
* It is important coordinates have been translated into game coordinates.
* @param gameX
* @param gameY
* @return List<Building>. Empty if uncessful, otherwise contains the removed buidling.
*/
public List<Building> attemptBuildingDeleteAt(float gameX, float gameY) {
Building toRemove = getBuildingAtPoint(gameX, gameY);
return attemptBuildingDelete(toRemove);
}
/**
* Gets the building at the specified point
* @param x Mouse X
* @param y Mouse Y
* @return Building that was at the coords
*/
public Building getBuildingAtPoint(float x, float y){
for (Building building: this.placedBuildings) {
float bx = building.getX();
float by = building.getY();
if( (x > bx && x < (bx + building.getWidth())) &&
(y > by && y < (by + building.getHeight())) ){
return building;
}
}
return null;
}
/**
* Checks whether the user is trying to place a building on another building or not on grass.
* @param x X
* @param y Y
* @return false if there exists a building at the spot the user is trying to place the building
* at or if non grass is present in the buildings spot.
*/
private boolean checkCollisions(float x, float y){
//Checks building exists in spot
float RoundedX = Math.round(x);
float RoundedY = Math.round(y);
if (!checkCollisionBuildings(RoundedX, RoundedY)) { return false; }
if (!checkCollisionBackground(RoundedX, RoundedY)) { return false; }
return true;
}
/**
* Checks placement is permitted based purely on other buildings.
* This method was refactored to prevent buildings colliding unnecessarily with each other.
* @param roundedX
* @param roundedY
* @return true if placement allowed, false otherwise
*/
private boolean checkCollisionBuildings(float roundedX, float roundedY) {
for (Building building: this.placedBuildings) {
//Only check collision for base of building(3/4 of the way up)
if ((roundedX > (building.getX() - GameGlobals.SCREEN_BUILDING_SIZE) &&
roundedX < (building.getX() + GameGlobals.SCREEN_BUILDING_SIZE)) &&
(roundedY > (building.getY() - GameGlobals.SCREEN_BUILDING_SIZE * 3/4) &&
roundedY < (building.getY() + GameGlobals.SCREEN_BUILDING_SIZE * 3/4))) {
return false;
}
}
return true;
}
/**
* Checks whether a placement is allowed based on the background texture.
* This method was refactored to prevent buildings colliding unnecessarily with obstacles.
* @param roundedX
* @param roundedY
* @return true if placement is allowed, false otherwise.
*/
private boolean checkCollisionBackground(float roundedX, float roundedY) {
String hold = backgroundRenderer.getMap();
//CheckTiles on the ground are grassBlocks
int yIndexLow = Math.round(((roundedY-64)/32)) + 3;
int xIndexLow = Math.round((roundedX-64)/32) + 2;
int lengthTiles = hold.split("\n").length;
char[][] TileSet = new char[3][4]; //Only check collision for base of building(3/4 of the way up)
for (int yCord=0;yCord<3;yCord++){
for (int xCord=0;xCord<4;xCord++){
try {
TileSet[yCord][xCord] = hold.split("\n")[lengthTiles - (yIndexLow + yCord)].charAt(
xIndexLow + xCord);
}catch (Exception ignored){}
}
}
for (char[] itemI: TileSet) {
for (char itemJ: itemI) {
if (itemJ != backgroundRenderer.getGRASS() && itemJ != backgroundRenderer.getGRASS2()) {
return false;
}
}
}
return true;
}
/**
* Increments the counter on the screen for the corresponding building that has been placed down.
* @param type Type of the building that has been added
*/
private void incrementBuildingsCount(BuildingStats.BuildingType type){
switch (type) {
case ACADEMIC -> GameGlobals.ACADEMIC_BUILDINGS_COUNT++;
case ACCOMODATION -> GameGlobals.ACCOMODATION_BUILDINGS_COUNT++;
case RECREATIONAL -> GameGlobals.RECREATIONAL_BUILDINGS_COUNT++;
case FOOD -> GameGlobals.FOOD_BUILDINGS_COUNT++;
default -> throw new IllegalArgumentException("Unexpected value: " + type);
}
}
/**
* Increments the counter on the screen for the corresponding building that has been placed down.
* @param type Type of the building that has been added
*/
private void decrementBuildingsCount(BuildingStats.BuildingType type){
switch (type) {
case ACADEMIC -> GameGlobals.ACADEMIC_BUILDINGS_COUNT--;
case ACCOMODATION -> GameGlobals.ACCOMODATION_BUILDINGS_COUNT--;
case RECREATIONAL -> GameGlobals.RECREATIONAL_BUILDINGS_COUNT--;
case FOOD -> GameGlobals.FOOD_BUILDINGS_COUNT--;
default -> throw new IllegalArgumentException("Unexpected value: " + type);
}
IndecisiveAchievement indecisiveAchievement = (IndecisiveAchievement)(
GameGlobals.ACHIEVEMENTS.getAchievement(IndecisiveAchievement.NAME));
indecisiveAchievement.incrementRemovedBuildings();
}
}