/* * UniMarket.java * * Copyright (c) 2014 Carl Spain. All rights reserved. * * This file is part of MekHQ. * * MekHQ is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MekHQ is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MekHQ. If not, see <http://www.gnu.org/licenses/>. */ package mekhq.campaign.market; import java.io.PrintWriter; import java.io.Serializable; import java.util.ArrayList; import java.util.Calendar; import java.util.Set; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import megamek.common.Compute; import megamek.common.EntityWeightClass; import megamek.common.MechSummary; import megamek.common.MechSummaryCache; import megamek.common.UnitType; import mekhq.MekHQ; import mekhq.MekHqXmlUtil; import mekhq.Utilities; import mekhq.Version; import mekhq.campaign.Campaign; import mekhq.campaign.mission.AtBContract; import mekhq.campaign.mission.Mission; import mekhq.campaign.rating.IUnitRating; import mekhq.campaign.universe.Faction; import mekhq.campaign.universe.IUnitGenerator; import mekhq.campaign.universe.RandomFactionGenerator; /** * Generates units available for sale. * * @author Neoancient * */ public class UnitMarket implements Serializable { /** * */ private static final long serialVersionUID = -2085002038852079114L; public class MarketOffer { public int market; public int unitType; public int unitWeight; public MechSummary unit; public int pct; public MarketOffer(int m, int t, int w, MechSummary u, int p) { market = m; unitType = t; unitWeight = w; unit = u; pct = p; } public MarketOffer() { } } public static int TYPE_ATBMONTHLY = 0; //TODO: Implement a method that rolls each day and adds or removes units public static int MARKET_OPEN = 0; public static int MARKET_EMPLOYER = 1; public static int MARKET_MERCENARY = 2; public static int MARKET_FACTORY = 3; public static int MARKET_BLACK = 4; public static int MARKET_NUM = 5; public static String [] marketNames = { "Open Market", "Employer Market", "Mercenary Auction", "Factory Line", "Black Market" }; private int method = TYPE_ATBMONTHLY; //master list private ArrayList<MarketOffer> offers; public UnitMarket() { offers = new ArrayList<MarketOffer>(); } public ArrayList<MarketOffer> getOffers() { return offers; } public void removeOffer(MarketOffer o) { offers.remove(o); } public void generateUnitOffers(Campaign campaign) { if (method == TYPE_ATBMONTHLY && campaign.getCalendar().get(Calendar.DAY_OF_MONTH) == 1) { offers.clear(); AtBContract contract = null; for (Mission m : campaign.getMissions()) { if (m.isActive() && m instanceof AtBContract) { contract = (AtBContract)m; break; } } addOffers(campaign, Compute.d6() - 2, MARKET_OPEN, UnitType.MEK, null, IUnitRating.DRAGOON_F, 7); addOffers(campaign, Compute.d6() - 1, MARKET_OPEN, UnitType.TANK, null, IUnitRating.DRAGOON_F, 7); addOffers(campaign, Compute.d6() - 2, MARKET_OPEN, UnitType.AERO, null, IUnitRating.DRAGOON_F, 7); if (contract != null) { addOffers(campaign, Compute.d6() - 3, MARKET_EMPLOYER, UnitType.MEK, contract.getEmployerCode(), IUnitRating.DRAGOON_D, 7); addOffers(campaign, Compute.d6() - 2, MARKET_EMPLOYER, UnitType.TANK, contract.getEmployerCode(), IUnitRating.DRAGOON_D, 7); addOffers(campaign, Compute.d6() - 3, MARKET_EMPLOYER, UnitType.AERO, contract.getEmployerCode(), IUnitRating.DRAGOON_D, 7); } if (!campaign.getFaction().isClan()) { addOffers(campaign, Compute.d6(3) - 9, MARKET_MERCENARY, UnitType.MEK, "MERC", IUnitRating.DRAGOON_C, 5); addOffers(campaign, Compute.d6(3) - 6, MARKET_MERCENARY, UnitType.TANK, "MERC", IUnitRating.DRAGOON_C, 5); addOffers(campaign, Compute.d6(3) - 9, MARKET_MERCENARY, UnitType.AERO, "MERC", IUnitRating.DRAGOON_C, 5); } if (campaign.getUnitRatingMod() >= IUnitRating.DRAGOON_B) { Set<Faction> factions = campaign.getCurrentPlanet().getFactionSet(Utilities.getDateTimeDay(campaign.getCalendar())); String faction = Utilities.getRandomItem(factions).getShortName(); if (campaign.getFaction().isClan() || !Faction.getFaction(faction).isClan()) { addOffers(campaign, Compute.d6() - 3, MARKET_FACTORY, UnitType.MEK, faction, IUnitRating.DRAGOON_A, 6); addOffers(campaign, Compute.d6() - 2, MARKET_FACTORY, UnitType.TANK, faction, IUnitRating.DRAGOON_A, 6); addOffers(campaign, Compute.d6() - 3, MARKET_FACTORY, UnitType.AERO, faction, IUnitRating.DRAGOON_A, 6); } } if (!campaign.getFaction().isClan()) { addOffers(campaign, Compute.d6(2) - 6, MARKET_BLACK, UnitType.MEK, null, IUnitRating.DRAGOON_C, 6); addOffers(campaign, Compute.d6(2) - 4, MARKET_BLACK, UnitType.TANK, null, IUnitRating.DRAGOON_C, 6); addOffers(campaign, Compute.d6(2) - 6, MARKET_BLACK, UnitType.AERO, null, IUnitRating.DRAGOON_C, 6); } if (campaign.getCampaignOptions().getUnitMarketReportRefresh()) { campaign.addReport("<a href='UNIT_MARKET'>Unit market updated</a>"); } } } private void addOffers(Campaign campaign, int num, int market, int unitType, String faction, int quality, int priceTarget) { if (faction == null) { faction = RandomFactionGenerator.getInstance().getEmployer(); } if (faction == null) { faction = campaign.getFactionCode(); market = MARKET_EMPLOYER; } for (int i = 0; i < num; i++) { int weight = getRandomWeight(unitType, faction, campaign.getCampaignOptions().getRegionalMechVariations()); MechSummary ms; if (unitType == UnitType.TANK) { ms = campaign.getUnitGenerator().generate(faction, unitType, weight, campaign.getCalendar().get(Calendar.YEAR), quality, IUnitGenerator.MIXED_TANK_VTOL, null); } else { ms = campaign.getUnitGenerator().generate(faction, unitType, weight, campaign.getCalendar().get(Calendar.YEAR), quality); } if (ms != null) { if (campaign.getCampaignOptions().limitByYear() && campaign.getCalendar().get(Calendar.YEAR) < ms.getYear()) { continue; } if ((campaign.getCampaignOptions().allowClanPurchases() && ms.isClan()) || (campaign.getCampaignOptions().allowISPurchases() && !ms.isClan())) { int pct = 100 - (Compute.d6(2) - priceTarget) * 5; /*Some RATs, particularly ASF, group multiple weight classes together * so we need to get the actual weight class from the generated unit * (-1 because EntityWeightClass starts with ultra-light).*/ offers.add(new MarketOffer(market, unitType, ms.getWeightClass(), ms, pct)); } } } } /* Used by special event */ public String addSingleUnit(Campaign campaign, int market, int unitType, String faction, int quality, int pricePct) { int weight = getRandomWeight(unitType, faction, campaign.getCampaignOptions().getRegionalMechVariations()); MechSummary ms = campaign.getUnitGenerator().generate(faction, unitType, weight, campaign.getCalendar().get(Calendar.YEAR), quality); if (ms == null) { return null; } else { offers.add(new MarketOffer(market, unitType, weight, ms, pricePct)); return ms.getName(); } } public static int getRandomWeight(int unitType, String faction, boolean regionalVariations) { if (unitType == UnitType.AERO) { return getRandomAeroWeight(); } if (unitType == UnitType.MEK && regionalVariations) { return getRegionalMechWeight(faction); } return getRandomMechWeight(); } public static int getRandomMechWeight() { int roll = Compute.randomInt(10); if (roll <= 2) return EntityWeightClass.WEIGHT_LIGHT; if (roll <= 6) return EntityWeightClass.WEIGHT_MEDIUM; if (roll <= 8) return EntityWeightClass.WEIGHT_HEAVY; return EntityWeightClass.WEIGHT_ASSAULT; } public static int getRegionalMechWeight(String faction) { int roll = Compute.randomInt(100); if (faction.equals("DC")) { if (roll < 40) return EntityWeightClass.WEIGHT_LIGHT; if (roll < 60) return EntityWeightClass.WEIGHT_MEDIUM; if (roll < 90) return EntityWeightClass.WEIGHT_HEAVY; return EntityWeightClass.WEIGHT_ASSAULT; } else if (faction.equals("LA")) { if (roll < 20) return EntityWeightClass.WEIGHT_LIGHT; if (roll < 50) return EntityWeightClass.WEIGHT_MEDIUM; if (roll < 85) return EntityWeightClass.WEIGHT_HEAVY; return EntityWeightClass.WEIGHT_ASSAULT; } else if (faction.equals("FWL")) { if (roll < 30) return EntityWeightClass.WEIGHT_LIGHT; if (roll < 70) return EntityWeightClass.WEIGHT_MEDIUM; if (roll < 92) return EntityWeightClass.WEIGHT_HEAVY; return EntityWeightClass.WEIGHT_ASSAULT; } if (roll < 30) return EntityWeightClass.WEIGHT_LIGHT; if (roll < 70) return EntityWeightClass.WEIGHT_MEDIUM; if (roll < 90) return EntityWeightClass.WEIGHT_HEAVY; return EntityWeightClass.WEIGHT_ASSAULT; } public static int getRandomAeroWeight() { int roll = Compute.randomInt(8); if (roll <= 2) return EntityWeightClass.WEIGHT_LIGHT; if (roll <= 6) return EntityWeightClass.WEIGHT_MEDIUM; return EntityWeightClass.WEIGHT_HEAVY; } public void writeToXml(PrintWriter pw1, int indent) { pw1.println(MekHqXmlUtil.indentStr(indent) + "<unitMarket>"); for (MarketOffer o : offers) { pw1.println(MekHqXmlUtil.indentStr(indent + 1) + "<offer>"); pw1.println(MekHqXmlUtil.indentStr(indent + 2) + "<market>" + o.market + "</market>"); pw1.println(MekHqXmlUtil.indentStr(indent + 2) + "<unitType>" + o.unitType + "</unitType>"); pw1.println(MekHqXmlUtil.indentStr(indent + 2) + "<unitWeight>" + o.unitWeight + "</unitWeight>"); pw1.println(MekHqXmlUtil.indentStr(indent + 2) + "<unit>" + o.unit.getName() + "</unit>"); pw1.println(MekHqXmlUtil.indentStr(indent + 2) + "<pct>" + o.pct + "</pct>"); pw1.println(MekHqXmlUtil.indentStr(indent + 1) + "</offer>"); } pw1.println(MekHqXmlUtil.indentStr(indent) + "</unitMarket>"); } public static UnitMarket generateInstanceFromXML(Node wn, Campaign c, Version version) { UnitMarket retVal = null; try { // Instantiate the correct child class, and call its parsing function. retVal = new UnitMarket(); // Okay, now load Part-specific fields! NodeList nl = wn.getChildNodes(); // Loop through the nodes and load our contract offers for (int x = 0; x < nl.getLength(); x++) { Node wn2 = nl.item(x); // If it's not an element node, we ignore it. if (wn2.getNodeType() != Node.ELEMENT_NODE) { continue; } if (!wn2.getNodeName().equalsIgnoreCase("offer")) { // Error condition of sorts! // Errr, what should we do here? MekHQ.logMessage("Unknown node type not loaded in Mission nodes: " + wn2.getNodeName()); continue; } MarketOffer o = retVal.new MarketOffer(); NodeList nl2 = wn2.getChildNodes(); for (int i = 0; i < nl2.getLength(); i++) { Node wn3 = nl2.item(i); if (wn3.getNodeType() != Node.ELEMENT_NODE) { continue; } if (wn3.getNodeName().equalsIgnoreCase("market")) { o.market = Integer.parseInt(wn3.getTextContent().trim()); } else if (wn3.getNodeName().equalsIgnoreCase("unitType")) { o.unitType = Integer.parseInt(wn3.getTextContent().trim()); } else if (wn3.getNodeName().equalsIgnoreCase("unitWeight")) { o.unitWeight = Integer.parseInt(wn3.getTextContent().trim()); } else if (wn3.getNodeName().equalsIgnoreCase("unit")) { o.unit = MechSummaryCache.getInstance().getMech(wn3.getTextContent().trim()); } else if (wn3.getNodeName().equalsIgnoreCase("pct")) { o.pct = Integer.parseInt(wn3.getTextContent().trim()); } } if (o != null) { retVal.offers.add(o); } } } catch (Exception ex) { // Errrr, apparently either the class name was invalid... // Or the listed name doesn't exist. // Doh! MekHQ.logError(ex); } return retVal; } }