package com.lucasdnd.ags.gameplay.market; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashSet; import java.util.Random; import org.newdawn.slick.SlickException; import org.newdawn.slick.SpriteSheet; import com.lucasdnd.ags.gameplay.Business; import com.lucasdnd.ags.gameplay.Console; import com.lucasdnd.ags.gameplay.Customer; import com.lucasdnd.ags.gameplay.Game; import com.lucasdnd.ags.gameplay.actions.Action; import com.lucasdnd.ags.gameplay.actions.BuyGameAction; import com.lucasdnd.ags.gameplay.actions.PlayGameAction; import com.lucasdnd.ags.gameplay.actions.RentGameAction; import com.lucasdnd.ags.system.GameSystem; import com.lucasdnd.ags.system.MainState; import com.lucasdnd.ags.system.ResourceLoader; import com.lucasdnd.ags.system.TimeController; import com.lucasdnd.ags.ui.BuyConsolesList; import com.lucasdnd.ags.ui.BuyGamesList; import com.lucasdnd.ags.ui.popup.FloatingText; /** * Will control how the market works and influences the business. * @author tulio * */ public class Market { protected NameSystem nameSystem; // Generates names for stuff private Random r; // Products available on the Market protected ArrayList<MarketConsole> marketConsoles; // List of available Consoles on the market protected ArrayList<MarketTable> marketTables; // List of available Tables on the market protected ArrayList<MarketGame> marketGames; // List of available Games on the market protected ArrayList<MarketShelf> marketShelves; // List of available Furniture on the market protected ArrayList<MarketTv> marketTvs; // List of available Tvs on the market protected ArrayList<MarketMisc> marketMiscs; // List of available Miscs on the market // Demand and Spawner protected ArrayList<Customer> customersToGenerate; // The list of Customers that will be spawned the next day protected int spawnTimer; // A new Character will spawn each time this timer reaches the limit protected int spawnTimerDemandModifier; // As demand increases, Characters will show up in the store faster protected int currentSpawnTimer; // Current spawner timer // Prices the customers find fair to pay for each activity, in each quality category protected int[] fairPlayPrice; protected int[] fairRentPrice; protected int[] fairSellPrice; // Prices per demand private int[] gamePrices; private int[] gameRentPrices; private int[] minPlayStock; private int[] minRentStock; private int[] minSaleStock; private boolean[] playAvailable; private boolean[] rentAvailable; private boolean[] saleAvailable; private boolean[] autoRestock; // Current generation of consoles private int currentGeneration = 0; private long generationStartDay, generationEndDay; private final int generationNotificationDaysInAdvance = 30; // How many days before a new gen starts the player will be notified /** * Initializes the Market * @throws SlickException */ public Market(int difficulty) throws SlickException { nameSystem = new NameSystem(); // Creates the Array Lists marketConsoles = new ArrayList<MarketConsole>(); marketTables = new ArrayList<MarketTable>(); marketGames = new ArrayList<MarketGame>(); marketShelves = new ArrayList<MarketShelf>(); marketTvs = new ArrayList<MarketTv>(); marketMiscs = new ArrayList<MarketMisc>(); // Initialize the fair prices setupInitialFairPrices(); Calendar cal = Calendar.getInstance(); r = new Random(); // Add the game objects cal.set(1980, 0, 1); // Shelves marketShelves.add(new MarketShelf(0, ResourceLoader.getInstance().shelf1SpriteSheet, "Simple shelf", 7000, 0L, 1, 1, new int[]{1}, new int[]{1}, 5, 6)); marketShelves.add(new MarketShelf(1, ResourceLoader.getInstance().shelf2SpriteSheet, "Glass shelf", 9500, 0L, 1, 1, new int[]{2}, new int[]{1}, 8, 10)); marketTables.add(new MarketTable(0, ResourceLoader.getInstance().tableSpriteSheet, "Table and chairs", 10000, cal.getTimeInMillis())); // Tvs marketTvs.add(new MarketTv(0, ResourceLoader.getInstance().tv1SpriteSheet, "Old Tv", 10000, 0L, 1)); marketTvs.add(new MarketTv(1, ResourceLoader.getInstance().tv2SpriteSheet, "Black Tv", 20000, 0L, 1)); marketTvs.add(new MarketTv(2, ResourceLoader.getInstance().tv3SpriteSheet, "Gray Tv", 45000, 0L, 1)); // Miscs marketMiscs.add(new MarketMisc(0, ResourceLoader.getInstance().plantSpriteSheet, "Plant", 1500, 0L, 1, 1, new int[]{1}, new int[]{1})); // Spawn Controller currentSpawnTimer = 0; spawnTimer = 10000; customersToGenerate = new ArrayList<Customer>(); // Prices management gamePrices = new int[5]; gamePrices[0] = 1500; gamePrices[1] = 2500; gamePrices[2] = 3500; gamePrices[3] = 5000; gamePrices[4] = 7000; gameRentPrices = new int[5]; gameRentPrices[0] = 20; gameRentPrices[1] = 100; gameRentPrices[2] = 150; gameRentPrices[3] = 200; gameRentPrices[4] = 300; // Inventory management minPlayStock = new int[5]; minRentStock = new int[5]; minSaleStock = new int[5]; // Play, rent and sale available playAvailable = new boolean[5]; rentAvailable = new boolean[5]; saleAvailable = new boolean[5]; autoRestock = new boolean[5]; for (int i = 0; i < playAvailable.length; i++) { playAvailable[i] = true; rentAvailable[i] = true; saleAvailable[i] = true; autoRestock[i] = true; } } /** * Update that runs every cycle */ public void update(Business business, int delta) { // Check if we have characters to spawn if(customersToGenerate != null) { if(customersToGenerate.size() > 0) { // Update spawn Timer with a random add to the spawn timer to make it less predictable currentSpawnTimer += delta + new Random().nextInt(100); // Reached the limit, spawn! if(currentSpawnTimer >= spawnTimer) { // Add it to the Store business.getStore().getCharacters().add(customersToGenerate.get(0)); // Places the Character on the map business.getStore().getMap().placeObject(customersToGenerate.get(0), business.getStore().getMap().getSpawnPoint().x, business.getStore().getMap().getSpawnPoint().y); // Remove that Character from the spawn list customersToGenerate.remove(0); } } } } /** * Every hour, the Market generates characters based on the demand * @throws SlickException */ public void hourUpdate(Business business, TimeController timeController, FloatingText notification, BuyConsolesList buyConsolesList, BuyGamesList buyGamesList, MainState mainState, int difficulty, int simulationSpeed, long currentTime) throws SlickException { // Game just started? Start the first gen! if (currentGeneration == 0) { startNewGeneration(business, timeController, notification, buyConsolesList, buyGamesList, mainState, difficulty); } // Generates customers. Check MarketGame and MakertConsole generation rules and the balance sheet // Customer generation by Games: take into consideration unique instances of games HashSet<MarketGame> uniqueGames = new HashSet<MarketGame>(); for(Game ownedGame : business.getStore().getGames()) { uniqueGames.add(ownedGame.getReferredMarketGame()); } for (MarketGame game : uniqueGames) { game.currentGenerationHour++; if(game.currentGenerationHour % game.everyXHours == 0) { generateRandomCustomer(business, game, simulationSpeed); } } // Customer generation by Consoles for(Console ownedConsole : business.getStore().getConsoles()) { MarketConsole console = ownedConsole.getReferringMarketConsole(); console.currentGenerationHour++; if(console.currentGenerationHour % console.everyXHours == 0) { generateRandomCustomer(business, console, simulationSpeed); } } // Customer generation by business rating } /** * Every day, the Market releases new products and updates prices and demand * @throws SlickException */ public void dayUpdate(Business business, TimeController timeController, FloatingText notification, BuyConsolesList buyConsolesList, BuyGamesList buyGamesList, MainState mainState, int difficulty) throws SlickException { // Console Generation Update long currentDay = timeController.getTime() / 1000 / 60 / 60 / 24; if(currentDay == generationEndDay - generationNotificationDaysInAdvance) { // Notify that a new Generation of Consoles is coming notification.show("A new generation of consoles will start in " + generationNotificationDaysInAdvance + " days!", "Current games and consoles will be removed from the market by then."); } else if (currentDay == generationEndDay + 1) { startNewGeneration(business, timeController, notification, buyConsolesList, buyGamesList, mainState, difficulty); } // Games updates boolean gamesListChanged = false; for(MarketGame game : marketGames) { // Update Game demand if(game.getQuality() > 0 && game.isAvailable) { game.decreaseLifespan(); double percentOfLifeSpan = (double)game.getLifespan() / (double)game.getTotalLifespan(); int oldDemand = game.getQuality(); int newDemand = (int)Math.round(game.getQuality() * percentOfLifeSpan); if(newDemand <= 0) { game.setQuality(0); } else { game.setQuality(newDemand); } if (newDemand != oldDemand) { gamesListChanged = true; game.setPrice(getGameBuyPrice(newDemand)); } } // Update Game's availability long gameReleaseDay = game.getReleaseDate() / 1000 / 60 / 60 / 24; if (gameReleaseDay <= currentDay && game.isAvailable == false) { game.isAvailable = true; gamesListChanged = true; } } if (gamesListChanged) { buyGamesList.updateDataSource(business, marketGames); } } /** * Starts a new generation of video game consoles */ public void startNewGeneration(Business business, TimeController timeController, FloatingText notification, BuyConsolesList buyConsolesList, BuyGamesList buyGamesList, MainState mainState, int difficulty) throws SlickException { // Remove all of the current Consoles and Games from the Market int numGamesToRemove = marketGames.size(); for (int i = 0; i < numGamesToRemove; i++) { marketGames.remove(0); } int numConsolesToRemove = marketConsoles.size(); for (int i = 0; i < numConsolesToRemove; i++) { marketConsoles.remove(0); } // Start a new Generation of Consoles Generation gen = new Generation(currentGeneration); // randomly creates a generation currentGeneration = gen.currentGeneration; generationStartDay = timeController.getTime() / 1000 / 60 / 60 / 24; generationEndDay = generationStartDay + gen.duration; // Create new Consoles (their games will be created in here) for (int i = 0; i < gen.numConsoles; i++) { this.createRandomConsole(difficulty, timeController.getTime(), gen.consoleDemands[i]); } // Update the UI buy lists buyConsolesList.updateDataSource(business, marketConsoles); buyGamesList.updateDataSource(business, marketGames); // Notify! int numGames = 0; for (MarketConsole c : marketConsoles) { numGames += c.getNumberOfGames(); } notification.show("A new generation of consoles has started!", "It has " + gen.numConsoles + " consoles, " + numGames + " games and will last " + gen.duration + " days."); } private class Generation { protected int currentGeneration; protected int duration; protected int numConsoles; protected int[] consoleDemands; protected Generation(int currentGen) { currentGeneration = currentGen; currentGeneration++; if (currentGeneration == 1) { // The first gen has special characteristics duration = 120; numConsoles = 3; consoleDemands = new int[]{2, 3, 4}; } else { Random r = new Random(); int result = r.nextInt(5); switch (result) { case 0: // Full random duration = (r.nextInt(5) + 1) * 40 + 80; numConsoles = r.nextInt(5) + 2; consoleDemands = new int[numConsoles]; for (int i = 0; i < consoleDemands.length; i++) { consoleDemands[i] = r.nextInt(4); } break; case 1: // Two rival, high demand consoles duration = 240; numConsoles = 2; consoleDemands = new int[]{4, 4}; break; case 2: // 3 consoles with balanced demand duration = 200; numConsoles = 3; consoleDemands = new int[]{1, 2, 4}; break; case 3: // Crash: n consoles with low demand duration = (r.nextInt(3) + 1) * 40 + 80; numConsoles = r.nextInt(5) + 2; consoleDemands = new int[numConsoles]; for (int i = 0; i < consoleDemands.length; i++) { consoleDemands[i] = r.nextInt(1); } break; case 4: // Three high demand consoles duration = 280; numConsoles = 3; consoleDemands = new int[]{4, 4, 4}; break; } } } } private int getFairPlayPriceModifier(int month) { switch(month) { case 0: return 50; case 1: return -20; case 2: return -20; case 3: return 0; case 4: return 0; case 5: return 0; case 6: return 50; case 7: return 0; case 8: return 0; case 9: return 20; case 10: return 50; case 11: return 100; } return 0; } private int getFairRentPriceModifier(int month) { switch(month) { case 0: return 50; case 1: return -20; case 2: return -20; case 3: return 0; case 4: return 0; case 5: return 0; case 6: return 100; case 7: return 0; case 8: return 0; case 9: return 50; case 10: return 100; case 11: return 150; } return 0; } private int getFairSellPriceModifier(int month) { switch(month) { case 0: return 1000; case 1: return -1000; case 2: return -500; case 3: return 0; case 4: return 0; case 5: return 0; case 6: return 1500; case 7: return 0; case 8: return 0; case 9: return 500; case 10: return 1000; case 11: return 2000; } return 0; } private void setupInitialFairPrices() { fairPlayPrice = new int[5]; fairRentPrice = new int[5]; fairSellPrice = new int[5]; fairPlayPrice[0] = 50; fairPlayPrice[1] = 100; fairPlayPrice[2] = 150; fairPlayPrice[3] = 200; fairPlayPrice[4] = 300; fairRentPrice[0] = 50; fairRentPrice[1] = 150; fairRentPrice[2] = 200; fairRentPrice[3] = 300; fairRentPrice[4] = 500; fairSellPrice[0] = 3000; fairSellPrice[1] = 4000; fairSellPrice[2] = 5000; fairSellPrice[3] = 6500; fairSellPrice[4] = 8500; } public int getCurrentFairSellPrice(int gameQuality, int month) { return fairSellPrice[gameQuality] + getFairSellPriceModifier(month); } public int getCurrentFairRentPrice(int gameQuality, int month) { return fairRentPrice[gameQuality] + getFairRentPriceModifier(month); } public int getCurrentFairPlayPrice(int gameQuality, int month) { return fairPlayPrice[gameQuality] + getFairPlayPriceModifier(month); } /** * Generates a random Console with its games * @param cal * @throws SlickException */ public void createRandomConsole(int difficulty, long currentTime, int consoleDemand) throws SlickException { int year = 0, month = 0, day = 0; SimpleDateFormat dayFormat = new SimpleDateFormat("dd"); SimpleDateFormat monthFormat = new SimpleDateFormat("MM"); SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy"); Date currentDate = new Date(); currentDate.setTime(currentTime); day = Integer.parseInt(dayFormat.format(currentTime)); month = Integer.parseInt(monthFormat.format(currentTime)); year = Integer.parseInt(yearFormat.format(currentTime)); Calendar cal = Calendar.getInstance(); cal.set(year, month - 1, day, 0, 0, 0); int quality = consoleDemand; int price = 0; switch (quality) { case 0: price = 10000; break; case 1: price = 15000; break; case 2: price = 20000; break; case 3: price = 30000; break; case 4: price = 40000; break; } MarketConsole newConsole = new MarketConsole(marketConsoles.size(), com.lucasdnd.ags.system.ResourceLoader.getInstance().getRandomConsoleSpriteSheet(), nameSystem.generateConsoleName(), price, quality, cal.getTimeInMillis()); marketConsoles.add(newConsole); int[] gamesPerCategory = getNumberOfGamesPerCategory(quality, newConsole.getNumberOfGames()); long generationDuration = generationEndDay - generationStartDay; long daysBetweenGames = generationDuration / newConsole.getNumberOfGames(); // Adds the Games for(int j = 0; j < newConsole.getNumberOfGames(); j++) { int gameQuality = getAvailableGameQuality(gamesPerCategory); int gamePrice = getGameBuyPrice(gameQuality); if (j > 0) { cal.add(Calendar.DATE, (int) daysBetweenGames); } MarketGame newGame = new MarketGame(marketGames.size(), nameSystem.generateGameName(), newConsole, difficulty, gamePrice, gameQuality, cal.getTimeInMillis()); if (j == 0) { newGame.setUnlocked(true); newGame.isAvailable = true; } int gameLifespan = (int) (generationDuration * 1.5); newGame.setTotalLifespan(gameLifespan); newGame.setLifespan(gameLifespan); marketGames.add(newGame); } } private int getGameBuyPrice(int demand) { return demand * 1000 + 1000 + r.nextInt(2) * 500; } /** * Number of games in each demand/quality category. Refer to the balance sheet * @param consoleQuality * @param numberOfGames * @return */ private int[] getNumberOfGamesPerCategory(int consoleQuality, int numberOfGames) { int[] result = new int[5]; float p1, p2, p3, p4, p5; p1 = p2 = p3 = p4 = p5 = 0f; switch(consoleQuality) { case 0: p1 = 0.8f; p2 = 0.05f; p3 = 0.05f; p4 = 0.05f; p5 = 0.05f; break; case 1: p1 = 0.4f; p2 = 0.2f; p3 = 0.2f; p4 = 0.1f; p5 = 0.1f; break; case 2: p1 = 0.3f; p2 = 0.2f; p3 = 0.3f; p4 = 0.1f; p5 = 0.1f; break; case 3: p1 = 0.2f; p2 = 0.2f; p3 = 0.4f; p4 = 0.1f; p5 = 0.1f; break; case 4: p1 = 0.1f; p2 = 0.1f; p3 = 0.45f; p4 = 0.2f; p5 = 0.15f; break; } result[0] = (int)(numberOfGames * p1); result[1] = (int)(numberOfGames * p2); result[2] = (int)(numberOfGames * p3); result[3] = (int)(numberOfGames * p4); result[4] = (int)(numberOfGames * p5); return result; } /** * Generates games qualities based on the balance sheet. Used when the games are being created, so we make each of the 5 in their correct ratios * @param gamesPerCategory * @return */ private int getAvailableGameQuality(int[] gamesPerCategory) { int randomQuality = r.nextInt(5); int steps = 0; while(steps < 5) { if(gamesPerCategory[randomQuality] > 0) { return randomQuality; } randomQuality++; if(randomQuality >= 5) { randomQuality = 0; } steps++; } return -1; } /** * Generates a customer based on a game the Player has bought. Refer to the balance sheet * @param game * @throws SlickException */ private void generateRandomCustomer(Business business, MarketGame game, int simulationSpeed) throws SlickException { // Balance sheet: orange area that adds actions to customers depending on the game the player bought ArrayList<Action> listOfActions = new ArrayList<Action>(); int result = 0; switch(game.getQuality()) { case 0: result = GameSystem.getRandomFrom100(10, 85, 5, 0); break; case 1: result = GameSystem.getRandomFrom100(20, 70, 10, 0); break; case 2: result = GameSystem.getRandomFrom100(30, 55, 15, 0); break; case 3: result = GameSystem.getRandomFrom100(40, 40, 20, 0); break; case 4: result = GameSystem.getRandomFrom100(50, 25, 25, 0); break; default: break; } if(result == 0) { listOfActions.add(new BuyGameAction(game)); } else if(result == 1) { listOfActions.add(new RentGameAction(game)); } else if(result == 2) { listOfActions.add(new BuyGameAction(game)); listOfActions.add(new RentGameAction(game)); } listOfActions.add(new PlayGameAction(game.getMarketConsole())); generateRandomCustomer(business, listOfActions, simulationSpeed); } /** * Generates a customer based on a console the Player has bought. Refer to the balance sheet * @param console * @throws SlickException */ private void generateRandomCustomer(Business business, MarketConsole console, int simulationSpeed) throws SlickException { // Balance sheet: orange area that adds actions to customers depending on the console the player bought // Always 100% play chance ArrayList<Action> listOfActions = new ArrayList<Action>(); listOfActions.add(new PlayGameAction(console)); generateRandomCustomer(business, listOfActions, simulationSpeed); } /** * Creates the Customer * @param business * @param listOfActions * @param simulationSpeed * @throws SlickException */ private void generateRandomCustomer(Business business, ArrayList<Action> listOfActions, int simulationSpeed) throws SlickException { SpriteSheet[] charSpriteSheets = ResourceLoader.getInstance().getRandomCustomerSpriteSheet(); Customer c = new Customer(business.getStore().getCharacters().size(), com.lucasdnd.ags.system.ResourceLoader.getInstance().tinyBlackFont, com.lucasdnd.ags.system.ResourceLoader.getInstance().tinyLightGrayFont, charSpriteSheets[0], charSpriteSheets[1], listOfActions, simulationSpeed, business.getStore()); customersToGenerate.add(c); } /** * Changes the Rent Price */ public void changeRentPrice(int demand, int priceChange) { if (priceChange == 10) { // Check if we reached the price limit if(gameRentPrices[demand] < 2000) { gameRentPrices[demand] += priceChange; } } else if(priceChange == 50) { // Check if we reached the price limit if(gameRentPrices[demand] < 1960) { gameRentPrices[demand] += priceChange; } } else if(priceChange == -10) { // Check if we reached the price limit if(gameRentPrices[demand] > 10) { gameRentPrices[demand] += priceChange; } } else if(priceChange == -50) { // Check if we reached the price limit if(gameRentPrices[demand] > 50) { gameRentPrices[demand] += priceChange; } } } /** * Change the Game Sell Price */ public void changeSellPrice(int demand, int priceChange) { if (priceChange == 100) { // Check if we reached the price limit if(gamePrices[demand] < 20000) { gamePrices[demand] += priceChange; } } else if(priceChange == 500) { // Check if we reached the price limit if(gamePrices[demand] < 19600) { gamePrices[demand] += priceChange; } } else if(priceChange == -100) { // Check if we reached the price limit if(gamePrices[demand] > 100) { gamePrices[demand] += priceChange; } } else if(priceChange == -500) { // Check if we reached the price limit if(gamePrices[demand] > 500) { gamePrices[demand] += priceChange; } } } public void setGamePriceByDemand(int demand, int price) { gamePrices[demand] = price; } public void setGameRentPriceByDemand(int demand, int price) { gameRentPrices[demand] = price; } public int setPlayStockByDemand(int demand, int amount) { return minPlayStock[demand] = amount; } public int setRentStockByDemand(int demand, int amount) { return minRentStock[demand] = amount; } public int setSaleStockByDemand(int demand, int amount) { return minSaleStock[demand] = amount; } public int getGamePriceByDemand(int demand) { return gamePrices[demand]; } public int getGameRentPriceByDemand(int demand) { return gameRentPrices[demand]; } public int getPlayStockByDemand(int demand) { return minPlayStock[demand]; } public int getRentStockByDemand(int demand) { return minRentStock[demand]; } public int getSaleStockByDemand(int demand) { return minSaleStock[demand]; } public boolean setPlayAvailableByDemand(int demand, boolean available) { return playAvailable[demand] = available; } public boolean setRentAvailableByDemand(int demand, boolean available) { return rentAvailable[demand] = available; } public boolean setSaleAvailableByDemand(int demand, boolean available) { return saleAvailable[demand] = available; } public boolean setAutoRestockByDemand(int demand, boolean available) { return autoRestock[demand] = available; } public boolean isPlayAvailableByDemand(int demand) { return playAvailable[demand]; } public boolean isRentAvailableByDemand(int demand) { return rentAvailable[demand]; } public boolean isSaleAvailableByDemand(int demand) { return saleAvailable[demand]; } public boolean isAutoRestockAvailableByDemand(int demand) { return autoRestock[demand]; } public ArrayList<MarketTable> getMarketTables() { return marketTables; } public void setMarketTables(ArrayList<MarketTable> marketTables) { this.marketTables = marketTables; } public ArrayList<MarketGame> getMarketGames() { return marketGames; } public void setMarketGames(ArrayList<MarketGame> marketGames) { this.marketGames = marketGames; } public ArrayList<MarketShelf> getMarketShelves() { return marketShelves; } public void setMarketShelves(ArrayList<MarketShelf> marketShelves) { this.marketShelves = marketShelves; } public ArrayList<MarketTv> getMarketTvs() { return marketTvs; } public void setMarketTvs(ArrayList<MarketTv> marketTvs) { this.marketTvs = marketTvs; } public ArrayList<MarketConsole> getMarketConsoles() { return marketConsoles; } public void setMarketConsoles(ArrayList<MarketConsole> marketConsoles) { this.marketConsoles = marketConsoles; } public ArrayList<MarketMisc> getMarketMiscs() { return marketMiscs; } public void setMarketMiscs(ArrayList<MarketMisc> marketMiscs) { this.marketMiscs = marketMiscs; } public int getCurrentGeneration() { return currentGeneration; } public void setCurrentGeneration(int currentGeneration) { this.currentGeneration = currentGeneration; } }