/*
* Copyright (C) 2010-2012 Klaus Reimer <k@ailis.de>
* See LICENSE.TXT for licensing information.
*/
package de.ailis.xadrian.data;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedSet;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import de.ailis.xadrian.data.factories.FactoryFactory;
import de.ailis.xadrian.data.factories.GameFactory;
import de.ailis.xadrian.data.factories.RaceFactory;
import de.ailis.xadrian.data.factories.SectorFactory;
import de.ailis.xadrian.data.factories.SunFactory;
import de.ailis.xadrian.data.factories.WareFactory;
import de.ailis.xadrian.dialogs.SetYieldsDialog;
import de.ailis.xadrian.exceptions.DataException;
import de.ailis.xadrian.exceptions.GameNotFoundException;
import de.ailis.xadrian.exceptions.TemplateCodeException;
import de.ailis.xadrian.interfaces.GameProvider;
import de.ailis.xadrian.support.Config;
import de.ailis.xadrian.support.DynaByteInputStream;
import de.ailis.xadrian.support.DynaByteOutputStream;
import de.ailis.xadrian.support.I18N;
import de.ailis.xadrian.support.ModalDialog.Result;
import de.ailis.xadrian.support.MultiCollection;
/**
* A complex
*
* @author Klaus Reimer (k@ailis.de)
*/
public class Complex implements Serializable, GameProvider
{
/** Serial version UID */
private static final long serialVersionUID = 2128684141345704703L;
/** The logger */
private static final Log log = LogFactory.getLog(Complex.class);
/** The single price of a complex construction kit */
public static final int KIT_PRICE = 259696;
/** The volume of a complex construction kit */
public static final int KIT_VOLUME = 4250;
/** The complex counter for the complex name generator */
private static int complexCounter = 0;
/** The game this complex belongs to. */
private final Game game;
/** The complex name */
private String name;
/** The factories in this complex */
private final List<ComplexFactory> factories;
/** Automatically added factories in this complex */
private final List<ComplexFactory> autoFactories;
/** The sun power in percent */
private Sun suns;
/** The sector where this complex is build */
private Sector sector = null;
/** If base complex should be calculated or not */
private boolean addBaseComplex = false;
/** Custom buy/sell prices in this complex */
private final Map<Ware, Integer> customPrices;
/** If complex setup should be displayed */
private boolean showingComplexSetup = true;
/** If production statistics should be displayed */
private boolean showingProductionStats = false;
/** If storage capacities should be displayed */
private boolean showingStorageCapacities = false;
/** If shopping list should be displayed */
private boolean showingShoppingList = false;
/** The built factories */
private final Map<String, Integer> builtFactories;
/** The number of built kits */
private int builtKits;
/** The cached shopping list */
private ShoppingList shoppingList;
/**
* Constructor
*
* @param game
* The game this complex belongs to.
*/
public Complex(final Game game)
{
this(game, createComplexName());
}
/**
* Constructor
*
* @param game
* The game this complex belongs to.
* @param name
* The complex name
*/
public Complex(final Game game, final String name)
{
this.game = game;
this.suns = game.getSunFactory().getDefaultSun();
this.name = name;
this.factories = new ArrayList<ComplexFactory>();
this.autoFactories = new ArrayList<ComplexFactory>();
this.customPrices = new HashMap<Ware, Integer>();
this.builtFactories = new HashMap<String, Integer>();
}
/**
* Returns a new name for a complex.
*
* @return A new complex name
*/
private static String createComplexName()
{
complexCounter++;
return I18N.getString("complex.nameTemplate", complexCounter);
}
/**
* Returns the name.
*
* @return The name
*/
public String getName()
{
return this.name;
}
/**
* Sets the complex name.
*
* @param name
* The complex name to set
*/
public void setName(final String name)
{
this.name = name;
}
/**
* Returns the total number of factories in the complex
*
* @return The total number of factories in the complex
*/
public int getTotalQuantity()
{
int quantity = 0;
for (final ComplexFactory factory: this.factories)
quantity += factory.getQuantity();
for (final ComplexFactory factory: this.autoFactories)
quantity += factory.getQuantity();
return quantity;
}
/**
* Returns the total complex price.
*
* @return The total complex price
*/
public long getTotalPrice()
{
long price = 0;
for (final ComplexFactory complexFactory: this.factories)
price += ((long) complexFactory.getQuantity())
* complexFactory.getFactory().getPrice();
for (final ComplexFactory complexFactory: this.autoFactories)
price += ((long) complexFactory.getQuantity())
* complexFactory.getFactory().getPrice();
return price + getTotalKitPrice();
}
/**
* A immutable copy of the factories in this complex.
*
* @return The factories in this complex
*/
public List<ComplexFactory> getFactories()
{
return Collections.unmodifiableList(this.factories);
}
/**
* A immutable copy of the automatically added factories in this complex.
*
* @return The automatically added factories in this complex
*/
public List<ComplexFactory> getAutoFactories()
{
return Collections.unmodifiableList(this.autoFactories);
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
return new HashCodeBuilder().append(this.name).append(this.factories)
.append(this.autoFactories).hashCode();
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(final Object obj)
{
if (obj == null) return false;
if (obj == this) return true;
if (obj.getClass() != getClass()) return false;
final Complex other = (Complex) obj;
return new EqualsBuilder().append(this.name, other.name).append(
this.factories, other.factories).append(this.suns, other.suns)
.append(this.sector, other.sector).isEquals();
}
/**
* Removes the factory with the given index.
*
* @param index
* The factory index
*/
public void removeFactory(final int index)
{
this.factories.remove(index);
calculateBaseComplex();
updateShoppingList();
}
/**
* Disables the factory with the given index.
*
* @param index
* The factory index
*/
public void disableFactory(final int index)
{
this.factories.get(index).disable();
calculateBaseComplex();
}
/**
* Disables the factory with the given index.
*
* @param index
* The factory index
*/
public void enableFactory(final int index)
{
this.factories.get(index).enable();
calculateBaseComplex();
}
/**
* Accepts the automatically added factory with the given index.
*
* @param index
* The factory index
*/
public void acceptFactory(final int index)
{
addFactory(this.autoFactories.get(index));
calculateBaseComplex();
}
/**
* Returns the quantity of the factory with the given index.
*
* @param index
* The factory index
* @return The quantity
*/
public int getQuantity(final int index)
{
return this.factories.get(index).getQuantity();
}
/**
* Increases the quantity of the factory with the given index.
*
* @param index
* The factory index
* @return True if quantity was changed, false if not.
*/
public boolean increaseQuantity(final int index)
{
if (this.factories.get(index).increaseQuantity())
{
calculateBaseComplex();
updateShoppingList();
return true;
}
return false;
}
/**
* Decreases the quantity of the factory with the given index.
*
* @param index
* The factory index
* @return True if quantity was changed, false if not.
*/
public boolean decreaseQuantity(final int index)
{
if (this.factories.get(index).decreaseQuantity())
{
calculateBaseComplex();
updateShoppingList();
return true;
}
return false;
}
/**
* Sets the quantity of the factory with the given index to the specified
* quantity.
*
* @param index
* The factory index
* @param quantity
* The quantity to set
*/
public void setQuantity(final int index, final int quantity)
{
final ComplexFactory factory = this.factories.get(index);
if (factory.getQuantity() != quantity)
{
factory.setQuantity(quantity);
calculateBaseComplex();
updateShoppingList();
}
}
/**
* Returns the yield of the factory with the given index.
*
* @param index
* The factory index
* @return The yield
*/
public List<Integer> getYields(final int index)
{
return this.factories.get(index).getYields();
}
/**
* Sets the yields of the factory with the given index.
*
* @param index
* The factory index
* @param yields
* The yields to set
*/
public void setYields(final int index, final List<Integer> yields)
{
final ComplexFactory factory = this.factories.get(index);
factory.setYields(yields);
calculateBaseComplex();
updateShoppingList();
}
/**
* Returns the factory type of factory with the given index.
*
* @param index
* The factory index
* @return The factory
*/
public Factory getFactory(final int index)
{
return this.factories.get(index).getFactory();
}
/**
* Sets the suns in percent.
*
* @param suns
* The suns in percent to set
*/
public void setSuns(final Sun suns)
{
this.suns = suns;
calculateBaseComplex();
}
/**
* Returns the suns in percent.
*
* @return The suns in percent
*/
public Sun getSuns()
{
if (this.sector != null) return this.sector.getSuns();
return this.suns;
}
/**
* Adds a factory to the complex.
*
* @param factory
* The factory to add
*/
public void addFactory(final Factory factory)
{
if (factory.isMine())
{
final SetYieldsDialog dialog = new SetYieldsDialog(factory);
dialog.setYields(null);
dialog.setSector(getSector());
if (dialog.open() == Result.OK)
{
setSector(dialog.getSector());
addFactory(new ComplexFactory(this.game, factory,
dialog.getYields()));
calculateBaseComplex();
updateShoppingList();
}
}
else
{
addFactory(new ComplexFactory(this.game, factory, 1, 0));
calculateBaseComplex();
updateShoppingList();
}
}
/**
* Adds the specified factory/factories to the complex.
*
* @param complexFactory
* The factory/factories to add
*/
private void addFactory(final ComplexFactory complexFactory)
{
if (!complexFactory.getFactory().isMine())
{
for (final ComplexFactory current: this.factories)
{
if (current.getFactory().equals(complexFactory.getFactory())
&& current.getYield() == complexFactory.getYield())
{
current.addQuantity(complexFactory.getQuantity());
return;
}
}
}
this.factories.add(complexFactory);
Collections.sort(this.factories);
updateShoppingList();
}
/**
* Converts the complex into XML and returns it.
*
* @return The complex as XML
*/
public Document toXML()
{
final Document document = DocumentHelper.createDocument();
final Element root = document.addElement("complex");
root.addAttribute("version", "4");
root.addAttribute("game", this.game.getId());
root.addAttribute("suns", Integer.toString(getSuns().getPercent()));
if (this.sector != null)
root.addAttribute("sector", this.sector.getId());
root.addAttribute("addBaseComplex", Boolean
.toString(this.addBaseComplex));
root.addAttribute("showingProductionStats", Boolean
.toString(this.showingProductionStats));
root.addAttribute("showingShoppingList", Boolean
.toString(this.showingShoppingList));
root.addAttribute("showingStorageCapacities", Boolean
.toString(this.showingStorageCapacities));
root.addAttribute("showingComplexSetup", Boolean
.toString(this.showingComplexSetup));
if (!this.factories.isEmpty())
{
final Element factoriesE = root.addElement("complexFactories");
for (final ComplexFactory factory: this.factories)
{
final Element factoryE = factoriesE
.addElement("complexFactory");
factoryE.addAttribute("factory", factory.getFactory().getId());
factoryE.addAttribute("disabled", Boolean.toString(factory
.isDisabled()));
if (factory.getFactory().isMine())
{
final Element yieldsE = factoryE.addElement("yields");
for (final Integer yield: factory.getYields())
{
final Element yieldE = yieldsE.addElement("yield");
yieldE.setText(Integer.toString(yield));
}
}
else
{
factoryE.addAttribute("quantity", Integer.toString(factory
.getQuantity()));
}
}
}
if (!this.customPrices.isEmpty())
{
final Element waresE = root.addElement("complexWares");
for (final Map.Entry<Ware, Integer> entry: this.customPrices
.entrySet())
{
final Ware ware = entry.getKey();
final int price = entry.getValue();
final Element wareE = waresE.addElement("complexWare");
wareE.addAttribute("ware", ware.getId());
wareE
.addAttribute("use", Boolean.valueOf(price > 0)
.toString());
wareE.addAttribute("price", Integer.toString(Math.abs(price)));
}
}
final Element shoppingListE = root.addElement("built");
shoppingListE.addAttribute("kits", Integer.toString(this.builtKits));
for (final Entry<String, Integer> entry: this.builtFactories
.entrySet())
{
final String id = entry.getKey();
final int quantity = entry.getValue();
final Element factoryE = shoppingListE.addElement("factory");
factoryE.addAttribute("id", id);
factoryE.addAttribute("quantity", Integer.toString(quantity));
}
return document;
}
/**
* Loads a complex from the specified XML document and returns it.
*
* @param document
* The XML document
* @return The complex
* @throws DocumentException
* If XML file could not be read
*/
public static Complex fromXML(final Document document)
throws DocumentException
{
final Element root = document.getRootElement();
// Check the version
final String versionStr = root.attributeValue("version");
int version = 1;
if (versionStr != null) version = Integer.parseInt(versionStr);
if (version > 4)
throw new DocumentException(
I18N.getString("error.fileFormatTooNew"));
// Determine the game for this complex.
String gameId = "x3tc";
if (version == 4) gameId = root.attributeValue("game");
final Game game = GameFactory.getInstance().getGame(gameId);
final Complex complex = new Complex(game);
final FactoryFactory factoryFactory = game.getFactoryFactory();
final SectorFactory sectorFactory = game.getSectorFactory();
final WareFactory wareFactory = game.getWareFactory();
final SunFactory sunsFactory = game.getSunFactory();
complex.setSuns(sunsFactory.getSun(Integer.parseInt(root
.attributeValue("suns"))));
complex.setSector(sectorFactory
.getSector(root.attributeValue("sector")));
complex.setAddBaseComplex(Boolean.parseBoolean(root.attributeValue(
"addBaseComplex", "false")));
complex.showingProductionStats = Boolean.parseBoolean(root
.attributeValue("showingProductionStats", "false"));
complex.showingShoppingList = Boolean.parseBoolean(root.attributeValue(
"showingShoppingList", "false"));
complex.showingStorageCapacities = Boolean.parseBoolean(root
.attributeValue("showingStorageCapacities", "false"));
complex.showingComplexSetup = Boolean.parseBoolean(root.attributeValue(
"showingComplexSetup", "true"));
// Get the factories parent element (In older version this was the root
// node)
Element factoriesE = root.element("complexFactories");
if (factoriesE == null) factoriesE = root;
// Read the complex factories
for (final Object item: factoriesE.elements("complexFactory"))
{
final Element element = (Element) item;
final Factory factory = factoryFactory.getFactory(element
.attributeValue("factory"));
final ComplexFactory complexFactory;
final Element yieldsE = element.element("yields");
if (yieldsE == null)
{
final int yield =
Integer.parseInt(element.attributeValue("yield", "0"));
final int quantity = Integer.parseInt(element
.attributeValue("quantity"));
complexFactory =
new ComplexFactory(game, factory, quantity, yield);
}
else
{
final List<Integer> yields = new ArrayList<Integer>();
for (final Object yieldItem: yieldsE.elements("yield"))
{
final Element yieldE = (Element) yieldItem;
yields.add(Integer.parseInt(yieldE.getText()));
}
complexFactory = new ComplexFactory(game, factory, yields);
}
if (Boolean.parseBoolean(element
.attributeValue("disabled", "false")))
complexFactory.disable();
complex.addFactory(complexFactory);
}
// Read the complex wares
final Element waresE = root.element("complexWares");
if (waresE != null)
{
complex.customPrices.clear();
for (final Object item: waresE.elements("complexWare"))
{
final Element element = (Element) item;
final Ware ware = wareFactory.getWare(element
.attributeValue("ware"));
final boolean use = Boolean.parseBoolean(element
.attributeValue("use"));
final int price = Integer.parseInt(element
.attributeValue("price"));
complex.customPrices.put(ware, use ? price : -price);
}
}
final Element builtE = root.element("built");
if (builtE != null)
{
complex.builtKits = Integer.parseInt(builtE.attributeValue("kits",
"0"));
for (final Object item: builtE.elements("factory"))
{
final Element element = (Element) item;
final String id = element.attributeValue("id");
final int quantity = Integer.parseInt(element
.attributeValue("quantity"));
complex.builtFactories.put(id, quantity);
}
}
complex.calculateBaseComplex();
return complex;
}
/**
* Returns all factories (Manually and automatically added ones):
*
* @return All factories
*/
@SuppressWarnings("unchecked")
private Collection<ComplexFactory> getAllFactories()
{
return new MultiCollection<ComplexFactory>(this.factories,
this.autoFactories);
}
/**
* Returns the products this complex produces in one hour.
*
* @return The products per hour.
*/
public Collection<Product> getProductsPerHour()
{
final Map<String, Product> products = new HashMap<String, Product>();
for (final ComplexFactory factory: getAllFactories())
{
final Product product = factory.getProductPerHour(getSuns());
final Ware ware = product.getWare();
final Product mapProduct = products.get(ware.getId());
if (mapProduct == null)
products.put(ware.getId(), product);
else
products.put(ware.getId(), new Product(ware, mapProduct
.getQuantity()
+ product.getQuantity()));
}
return products.values();
}
/**
* Returns the resources this complex needs in one hour.
*
* @return The needed resources per hour.
*/
public Collection<Product> getResourcesPerHour()
{
final Map<String, Product> resources = new HashMap<String, Product>();
for (final ComplexFactory factory: getAllFactories())
{
for (final Product resource: factory
.getResourcesPerHour(getSuns()))
{
final Ware ware = resource.getWare();
final Product mapResource = resources.get(ware.getId());
if (mapResource == null)
resources.put(ware.getId(), resource);
else
resources.put(ware.getId(), new Product(ware, mapResource
.getQuantity()
+ resource.getQuantity()));
}
}
return resources.values();
}
/**
* Returns the list of complex wares (Produced and needed).
*
* @return The list of complex wares
*/
public Collection<ComplexWare> getWares()
{
final Map<String, ComplexWare> wares =
new HashMap<String, ComplexWare>();
// Add the products
for (final Product product: getProductsPerHour())
{
final Ware ware = product.getWare();
final String wareId = ware.getId();
wares.put(wareId, new ComplexWare(ware, product.getQuantity(), 0,
getWarePrice(ware)));
}
// Add the resources
for (final Product resource: getResourcesPerHour())
{
final Ware ware = resource.getWare();
final String wareId = ware.getId();
ComplexWare complexWare = wares.get(wareId);
if (complexWare == null)
complexWare = new ComplexWare(ware, 0, resource.getQuantity(),
getWarePrice(ware));
else
complexWare = new ComplexWare(ware, complexWare.getProduced(),
resource.getQuantity(), getWarePrice(ware));
wares.put(wareId, complexWare);
}
final List<ComplexWare> result = new ArrayList<ComplexWare>(wares
.values());
Collections.sort(result);
return result;
}
/**
* Returns the profit of this complex.
*
* @return The profit
*/
public double getProfit()
{
double profit;
profit = 0;
for (final ComplexWare complexWare: getWares())
{
profit += complexWare.getProfit();
}
return profit;
}
/**
* Returns the number of needed complex construction kits in this complex.
*
* @return The number of needed complex construction kits.
*/
public int getKitQuantity()
{
return Math.max(0, getTotalQuantity() - 1);
}
/**
* Returns the price of a single complex construction kit.
*
* @return The price of a single complex construction kit
*/
public int getKitPrice()
{
return KIT_PRICE;
}
/**
* Returns the total price of all needed complex construction kits.
*
* @return The total price of all needed complex construction kits
*/
public int getTotalKitPrice()
{
return getKitQuantity() * getKitPrice();
}
/**
* Calculates and adds the factories needed to keep the factories of this
* complex running stable.
*/
private void calculateBaseComplex()
{
final FactoryFactory factoryFactory = this.game.getFactoryFactory();
final RaceFactory raceFactory = this.game.getRaceFactory();
final Ware crystals = this.game.getWareFactory().getWare("crystals");
final Config config = Config.getInstance();
long currentPrice;
long price;
final List<ComplexFactory> backup = new ArrayList<ComplexFactory>();
// First of all remove all automatically added factories
this.autoFactories.clear();
updateShoppingList();
if (!this.addBaseComplex) return;
// First of all we build a base complex without specific crystal fab
// race and remember the price
while (true)
if (!addBaseComplex(null)) break;
currentPrice = getTotalPrice();
// Now cycle over all races and check if the complex gets cheaper if
// the crystal fabs are bought from them
for (final Race race: raceFactory.getRaces())
{
// If race is ignored then don't use it
if (config.isRaceIgnored(race)) continue;
// If race has no crystal fabs then don't use it
if (!factoryFactory.hasFactories(race, crystals)) continue;
// Backup current automatically added factories, clear the
// calculated factories and then calculate the complex again with
// a specific "crystal race"
backup.addAll(this.autoFactories);
this.autoFactories.clear();
while (true)
if (!addBaseComplex(race)) break;
// Check if new price is cheaper then the old one. If cheaper
// then the new complex is used (and checked against the next
// available race). If not cheaper then the old complex is restored
price = getTotalPrice();
if (price < currentPrice)
{
currentPrice = price;
}
else
{
this.autoFactories.clear();
this.autoFactories.addAll(backup);
}
backup.clear();
}
updateShoppingList();
}
/**
* Updates the base complex.
*/
public void updateBaseComplex()
{
calculateBaseComplex();
}
/**
* Updates the shopping list.
*/
public void updateShoppingList()
{
this.shoppingList = null;
this.builtKits = Math.max(0, Math.min(this.builtKits,
getTotalQuantity() - 1));
for (final Map.Entry<String, Integer> entry: this.builtFactories
.entrySet())
{
final String id = entry.getKey();
final int max = getMaxFactories(id);
final int quantity = Math.min(entry.getValue(), max);
this.builtFactories.put(id, quantity);
}
this.shoppingList = null;
}
/**
* Searches for the first unfulfilled resource need (which is not a mineral)
* and adds the necessary factories for this. If a need was found (and
* fixed) then this method returns true. If all needs are already fulfilled
* then it returns false.
*
* @param crystalRace
* Optional race from which crystal fabs should be bought. If
* null then the cheapest fab is searched.
* @return True if a need was found and fixed, false if everything is
* finished
*/
private boolean addBaseComplex(final Race crystalRace)
{
for (final ComplexWare ware: getWares())
{
// We are not going to add mines
if (ware.getWare().isMineral()) continue;
// If the current ware has missing units then add the necessary
// factories for this ware and then restart the adding of factories
if (ware.getMissing() > 0)
{
final Race race = ware.getWare().getId().equals("crystals")
? crystalRace : null;
if (!addBaseComplexForWare(ware, race)) continue;
return true;
}
}
return false;
}
/**
* Adds the factories needed to fulfill the need of the specified complex
* ware.
*
* @param complexWare
* The complex ware for which factories must be added
* @param race
* The race from which factories should be bought. If null then
* the cheapest factory is used.
* @return True if a new factories were added, false if this was not
* possible
*/
private boolean addBaseComplexForWare(final ComplexWare complexWare,
final Race race)
{
final Ware ware = complexWare.getWare();
final FactoryFactory factoryFactory = this.game.getFactoryFactory();
// Remove all automatically added factories which produces the
// specified ware and calculate the real need which must be
// fulfilled.
double need = complexWare.getMissing();
final double oldNeed = need;
for (final ComplexFactory complexFactory: new ArrayList<ComplexFactory>(
this.autoFactories))
{
if (complexFactory.getFactory().getProduct().getWare().equals(ware))
{
need += complexFactory.getProductPerHour(getSuns())
.getQuantity();
this.autoFactories.remove(complexFactory);
}
}
// Determine the available factory sizes
final SortedSet<FactorySize> sizesSet =
factoryFactory.getFactorySizes(ware, race);
final FactorySize[] sizes =
sizesSet.toArray(new FactorySize[sizesSet.size()]);
// Abort if no factories were found
if (sizes.length == 0) return false;
// Get the cheapest factories for the sizes
final Map<FactorySize, Factory> factories =
new HashMap<FactorySize, Factory>();
for (final FactorySize size: sizes)
{
if (race == null)
factories.put(size, factoryFactory.getCheapestFactory(ware,
size));
else
factories
.put(size, factoryFactory.getFactory(ware, size, race));
}
// Get the smallest possible production quantity
final double minProduction = factories.get(sizes[0]).getProductPerHour(
getSuns(), 0).getQuantity();
// Iterate the available sizes (from largest to smallest) and add
// the factories producing an adequate number of products
for (int i = sizes.length - 1; i >= 0; i--)
{
final FactorySize size = sizes[i];
final Factory factory = factories.get(size);
final double product = factory.getProductPerHour(getSuns(), 0)
.getQuantity();
// Calculate the number of factories of the current size needed
log.debug("Need " + need + " units of " + ware + ". Considering "
+ factory + " which produces " + product + " units");
final int quantity = (int) Math.floor((need + minProduction - 0.1)
/ product);
// Add the number of factories and decrease the need
if (quantity > 0)
{
log.debug("Adding " + quantity + "x " + factory);
this.autoFactories
.add(new ComplexFactory(this.game, factory, quantity, 0));
need -= quantity * product;
}
else
log.debug("Not adding any " + factory);
}
if (Math.abs(need - oldNeed) < .0000001)
{
log.debug("Unable to calculate best matching factory. Aborting");
return false;
}
return true;
}
/**
* Toggles the addition of automatically calculated base complex.
*/
public void toggleAddBaseComplex()
{
this.addBaseComplex = !this.addBaseComplex;
calculateBaseComplex();
}
/**
* Enables or disabled base complex addition.
*
* @param addBaseComplex
* True if base complex should be added, false if not
*/
public void setAddBaseComplex(final boolean addBaseComplex)
{
this.addBaseComplex = addBaseComplex;
}
/**
* Checks whether a base complex was added or not.
*
* @return True if a base complex was added. False if not.
*/
public boolean isAddBaseComplex()
{
return this.addBaseComplex;
}
/**
* Returns the storage capacities.
*
* @return The storage capacities.
*/
public Collection<Capacity> getCapacities()
{
final Map<String, Capacity> capacities =
new HashMap<String, Capacity>();
for (final ComplexFactory factory: getAllFactories())
{
for (final Capacity capacity: factory.getCapacities())
{
final Ware ware = capacity.getWare();
final Capacity mapCapacity = capacities.get(ware.getId());
if (mapCapacity == null)
capacities.put(ware.getId(), capacity);
else
capacities.put(ware.getId(), new Capacity(ware, mapCapacity
.getQuantity()
+ capacity.getQuantity()));
}
}
final List<Capacity> result = new ArrayList<Capacity>(capacities
.values());
Collections.sort(result);
return result;
}
/**
* Returns the total storage capacity
*
* @return The total storage capacity;
*/
public long getTotalCapacity()
{
long total = 0;
for (final Capacity capacity: getCapacities())
total += capacity.getQuantity();
return total;
}
/**
* Returns the total storage volume
*
* @return The total storage volume;
*/
public long getTotalStorageVolume()
{
long total = 0;
for (final Capacity capacity: getCapacities())
total += capacity.getVolume();
return total;
}
/**
* Sets the sector in which to build this complex
*
* @param sector
* The sector to set
*/
public void setSector(final Sector sector)
{
if ((sector != null && !sector.equals(this.sector))
|| (sector == null && this.sector != null))
{
this.sector = sector;
calculateBaseComplex();
updateShoppingList();
}
}
/**
* Returns the sector in which this complex is build.
*
* @return The sector
*/
public Sector getSector()
{
return this.sector;
}
/**
* Returns the factory shopping list.
*
* @return The factory shopping list
*/
public ShoppingList getShoppingList()
{
// Return cached shopping list if present
if (this.shoppingList != null) return this.shoppingList;
final ShoppingList list = new ShoppingList(this.sector == null ? null
: this.sector.getNearestKitSellingSector(), this.builtKits);
for (final ComplexFactory factory: this.factories)
{
list.addItem(new ShoppingListItem(factory.getFactory(), factory
.getQuantity(), this.sector == null ? null : factory
.getFactory().getNearestManufacturer(this.sector),
getFactoriesBuilt(factory.getFactory())));
}
for (final ComplexFactory factory: this.autoFactories)
{
list.addItem(new ShoppingListItem(factory.getFactory(), factory
.getQuantity(), this.sector == null ? null : factory
.getFactory().getNearestManufacturer(this.sector),
getFactoriesBuilt(factory.getFactory())));
}
this.shoppingList = list;
return list;
}
/**
* Returns the price for the specified ware. If the price has a custom price
* then this one is returned. If not then the standard average price of the
* ware is returned.
*
* @param ware
* The ware
* @return The price
*/
public int getWarePrice(final Ware ware)
{
final Integer price = this.customPrices.get(ware);
if (price == null) return ware.getAvgPrice();
if (price < 0) return 0;
return price;
}
/**
* Returns the map with custom prices.
*
* @return The map with custom prices
*/
public Map<Ware, Integer> getCustomPrices()
{
return Collections.unmodifiableMap(this.customPrices);
}
/**
* Sets a new map with custom prices.
*
* @param customPrices
* The new map with custom prices
*/
public void setCustomPrices(final Map<Ware, Integer> customPrices)
{
this.customPrices.clear();
this.customPrices.putAll(customPrices);
}
/**
* Checks if complex setup should be displayed.
*
* @return True if complex setup should be displayed, false if not
*/
public boolean isShowingComplexSetup()
{
return this.showingComplexSetup;
}
/**
* Toggles the display of the complex setup.
*/
public void toggleShowingComplexSetup()
{
this.showingComplexSetup = !this.showingComplexSetup;
}
/**
* Checks if production stats should be displayed.
*
* @return True if production stats should be displayed, false if not
*/
public boolean isShowingProductionStats()
{
return this.showingProductionStats;
}
/**
* Toggles the display of production statistics.
*/
public void toggleShowingProductionStats()
{
this.showingProductionStats = !this.showingProductionStats;
}
/**
* Checks if storage capacities should be displayed.
*
* @return True if storage capacities should be displayed, false if not
*/
public boolean isShowingStorageCapacities()
{
return this.showingStorageCapacities;
}
/**
* Toggles the display of storage capacities.
*/
public void toggleShowingStorageCapacities()
{
this.showingStorageCapacities = !this.showingStorageCapacities;
}
/**
* Checks if the shopping list should be displayed.
*
* @return True if the shopping list should be displayed, false if not
*/
public boolean isShowingShoppingList()
{
return this.showingShoppingList;
}
/**
* Toggles the display of the shopping list.
*/
public void toggleShowingShoppingList()
{
this.showingShoppingList = !this.showingShoppingList;
}
/**
* Returns the maximum number of factories in the shopping list for the
* specified factory id.
*
* @param id
* The id of the factory
* @return The number of factories in the shopping list
*/
private int getMaxFactories(final String id)
{
final ShoppingList list = getShoppingList();
for (final ShoppingListItem item: list.getItems())
{
if (item.getFactory().getId().equals(id))
return item.getQuantity();
}
return 0;
}
/**
* Builds a factory with the specified id
*
* @param id
* The id of the factory to build
*/
public void buildFactory(final String id)
{
Integer oldCount = this.builtFactories.get(id);
if (oldCount == null) oldCount = 0;
if (oldCount == getMaxFactories(id)) return;
this.builtFactories.put(id, oldCount + 1);
updateShoppingList();
}
/**
* Destroys a factory with the specified id.
*
* @param id
* The id of the factory to destroy
*/
public void destroyFactory(final String id)
{
final Integer oldCount = this.builtFactories.get(id);
if (oldCount == null) return;
if (oldCount == 0) return;
this.builtFactories.put(id, oldCount - 1);
updateShoppingList();
}
/**
* Build a kit.
*/
public void buildKit()
{
if (this.builtKits >= getTotalQuantity() - 1) return;
this.builtKits++;
updateShoppingList();
}
/**
* Destroys a kit.
*/
public void destroyKit()
{
if (this.builtKits == 0) return;
this.builtKits--;
updateShoppingList();
}
/**
* Returns the number of built factories of the specified type.
*
* @param factory
* The factory type
* @return The number of built factores of the specified type
*/
private int getFactoriesBuilt(final Factory factory)
{
final Integer count = this.builtFactories.get(factory.getId());
return count == null ? 0 : count;
}
/**
* @see de.ailis.xadrian.interfaces.GameProvider#getGame()
*/
@Override
public Game getGame()
{
return this.game;
}
/**
* Checks if the complex uses the specified ware as product or resource.
*
* @param ware
* The ware to check.
* @return True if ware is used, false if not.
*/
public boolean usesWare(final Ware ware)
{
for (final ComplexFactory complexFactory: getAllFactories())
{
final Factory factory = complexFactory.getFactory();
if (factory.getProduct().getWare().equals(ware)) return true;
for (final Product product: factory.getResources())
if (product.getWare().equals(ware)) return true;
}
return false;
}
/**
* Checks if the complex is empty.
*
* @return True when the complex is empty, false if not.
*/
public boolean isEmpty()
{
return this.factories.isEmpty();
}
/**
* Checks if the specified code is a valid template code.
*
* @param templateCode The template code to check.
* @return True if code is valid, false if not.
*/
public static boolean isValidTemplateCode(final String templateCode)
{
try
{
// Decode base 64
final byte[] data =
DatatypeConverter.parseBase64Binary(templateCode.trim());
final InputStream stream = new DynaByteInputStream(
new ByteArrayInputStream(data));
try
{
// Read complex settings
final int settings = stream.read();
final boolean hasSector = (settings & 1) == 1;
final int gameNid = (settings >> 1) & 7;
final Game game;
try
{
game = GameFactory.getInstance().getGame(gameNid);
}
catch (final GameNotFoundException e)
{
return false;
}
// Read sector coordinates or sun power
if (hasSector)
{
final int x = stream.read();
final int y = stream.read();
if (game.getSectorFactory().getSector(x, y) == null) return false;
}
else
{
final int percent = stream.read();
try
{
game.getSunFactory().getSun(percent);
}
catch (final DataException e)
{
return false;
}
}
int factoryId;
while ((factoryId = stream.read()) != 0)
{
final Factory factory =
game.getFactoryFactory().getFactory(factoryId);
if (factory == null) return false;
if (factory.isMine())
{
int yield;
while ((yield = stream.read()) != 0)
if (yield > 256) return false;
}
else
{
stream.read();
}
}
return true;
}
finally
{
stream.close();
}
}
catch (final Exception e)
{
return false;
}
}
/**
* Creates a complex from the specified template code.
*
* @param templateCode
* The template code
* @return The complex.
*/
public static Complex fromTemplateCode(final String templateCode)
{
try
{
// Decode base 64
final byte[] data =
DatatypeConverter.parseBase64Binary(templateCode.trim());
final InputStream stream = new DynaByteInputStream(
new ByteArrayInputStream(data));
try
{
// Read complex settings
final int settings = stream.read();
final boolean hasSector = (settings & 1) == 1;
final int gameNid = (settings >> 1) & 7;
final Game game = GameFactory.getInstance().getGame(gameNid);
final Complex complex = new Complex(game);
// Read sector coordinates or sun power
if (hasSector)
{
final int x = stream.read();
final int y = stream.read();
complex.setSector(game.getSectorFactory().getSector(x, y));
}
else
{
final int percent = stream.read();
complex.setSuns(game.getSunFactory().getSun(percent));
}
int factoryId;
while ((factoryId = stream.read()) != 0)
{
final Factory factory =
game.getFactoryFactory().getFactory(factoryId);
if (factory.isMine())
{
final List<Integer> yields = new ArrayList<Integer>();
int yield;
while ((yield = stream.read()) != 0)
yields.add(yield - 1);
complex
.addFactory(new ComplexFactory(game, factory, yields));
}
else
{
final int quantity = stream.read();
complex.addFactory(new ComplexFactory(game, factory,
quantity, 0));
}
}
return complex;
}
finally
{
stream.close();
}
}
catch (final IOException e)
{
throw new TemplateCodeException(e.toString(), e);
}
}
/**
* Returns the template code.
*
* @return The template code.
*/
public String getTemplateCode()
{
try
{
final ByteArrayOutputStream arrayStream =
new ByteArrayOutputStream();
final OutputStream stream = new DynaByteOutputStream(arrayStream);
// Write the template settings bit mask.
int settings = this.sector == null ? 0 : 1;
settings |= this.game.getNid() << 1;
stream.write(settings);
// Write the sector coordinates
if (this.sector != null)
{
stream.write(this.sector.getX());
stream.write(this.sector.getY());
}
// Or else write the sun power
else
{
stream.write(this.suns.getPercent());
}
// Write the factories
for (final ComplexFactory complexFactory: getAllFactories())
{
if (complexFactory.isDisabled()) continue;
final Factory factory = complexFactory.getFactory();
stream.write(factory.getNid());
if (factory.isMine())
{
for (final int yield: complexFactory.getYields())
stream.write(yield + 1);
stream.write(0);
}
else
stream.write(complexFactory.getQuantity());
}
// Write end marker
stream.write(0);
stream.close();
// Get byte array from stream
final byte[] data = arrayStream.toByteArray();
// Return base 64 encoded bytes
return DatatypeConverter.printBase64Binary(data);
}
catch (final IOException e)
{
throw new TemplateCodeException(e.toString(), e);
}
}
/**
* Checks if this complex has mines.
*
* @return True if complex has mines, false if not.
*/
public boolean hasMines()
{
for (final ComplexFactory factory: this.factories)
if (factory.getFactory().isMine()) return true;
return false;
}
/**
* Returns the complex data as ASCII.
*
* @return The complex data as ASCII.
*/
public String getASCII()
{
final StringWriter writer = new StringWriter();
final PrintWriter out = new PrintWriter(writer);
// Print the game name.
out.print(I18N.getString("complex.game"));
out.print(": ");
out.println(getGame().getName());
// Print the sector name if chosen
final Sector sector = getSector();
if (sector != null)
{
out.print(I18N.getString("complex.sector"));
out.print(": ");
out.println(sector.getName());
}
// Print the sun power
out.print(I18N.getString("complex.suns"));
out.print(": ");
out.println(getSuns().toString());
// Print the template code
out.print(I18N.getString("complex.templateCode"));
out.print(": ");
out.println(getTemplateCode());
out.println();
// Print factories
out.print(I18N.getString("complex.factories"));
out.println(":");
for (final ComplexFactory complexFactory: getAllFactories())
{
if (complexFactory.isDisabled()) continue;
final Factory factory = complexFactory.getFactory();
out.print(complexFactory.getQuantity());
out.print("x ");
out.print(factory.getName());
out.print(" (");
out.print(factory.getRace().getName());
if (factory.isMine())
{
out.print(", ");
out.print(I18N.getString("complex.yield"));
out.print(": ");
boolean first = true;
for (final int yield: complexFactory.getYields())
{
if (!first)
out.print(", ");
out.print(yield);
first = false;
}
}
out.println(")");
}
out.println();
// Print total complex price
out.print(I18N.getString("complex.totalPrice"));
out.print(": ");
out.print(new DecimalFormat().format(getTotalPrice()));
out.println(" Cr");
// Print total complex price
out.print(I18N.getString("complex.profitPerHour"));
out.print(": ");
out.print(new DecimalFormat().format(Math.round(getProfit())));
out.println(" Cr");
return writer.toString();
}
}