/*
* RandomFactionGenerator.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.universe;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileInputStream;
import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.TreeSet;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import megamek.common.Compute;
import mekhq.MekHQ;
import mekhq.Utilities;
import mekhq.campaign.CampaignOptions;
/**
* @author Neoancient
*
* Uses Factions and Planets to weighted lists of potential employers
* and enemies for contract generation. Also finds a suitable planet
* for the action.
*
*/
/* TODO: Redesign the system for tracking factions that do not control any planets
* of their own to make it easier to follow.
* TODO: Account for the de facto alliance of the invading Clans and the
* Fortress Republic in a way that doesn't involve hard-coding them here.
*/
public class RandomFactionGenerator implements Serializable {
/**
*
*/
private static final long serialVersionUID = -7346225681238948390L;
/* When checking for potential enemies, count the planets controlled
* by potentially hostile factions within a certain number of jumps of
* friendly worlds; the number is based on the region of space.
*/
private static final int BORDER_RANGE_IS = 60;
private static final int BORDER_RANGE_CLAN = 90;
private static final int BORDER_RANGE_NEAR_PERIPHERY = 90;
private static final int BORDER_RANGE_DEEP_PERIPHERY = 210; //a bit more than this distance between HL and NC
private static RandomFactionGenerator rfg = null;
private TreeSet<String> currentFactions;
/* all factions that control at least one system at the current date
*/
private ArrayList<String> employers;
/* list of potential employers weighted by number of systems controlled
*/
private HashMap<String, HashMap<String, HashSet<Planet>>> borders;
/*List of systems controlled by faction key1 that are within one
* jump of faction key2
*/
private HashSet<String> deepPeriphery;
private HashSet<String> neutralFactions;
private HashSet<String> majorPowers;
private HashMap<String, HashMap<String, ArrayList<FactionHint>>> wars;
private HashMap<String, HashMap<String, ArrayList<FactionHint>>> alliances;
private HashMap<String, HashMap<String, ArrayList<FactionHint>>> rivals;
private HashMap<String, HashMap<String, ArrayList<FactionHint>>> neutralExceptions;
private HashMap<String, HashMap<String, ArrayList<AltLocation>>> containedFactions;
private ArrayList<ActionListener> listeners;
private Thread loader;
private static boolean initialized = false;
private static boolean initializing = false;
/* Track time and location; only update if one of them has changed */
private Date lastUpdate = null;
private Planet lastLocation = null;
private RandomFactionGenerator() {
currentFactions = new TreeSet<String>();
employers = new ArrayList<String>();
deepPeriphery = new HashSet<String>();
neutralFactions = new HashSet<String>();
majorPowers = new HashSet<String>();
borders = new HashMap<String,HashMap<String,HashSet<Planet>>>();
wars = new HashMap<String, HashMap<String, ArrayList<FactionHint>>>();
alliances = new HashMap<String, HashMap<String, ArrayList<FactionHint>>>();
rivals = new HashMap<String, HashMap<String, ArrayList<FactionHint>>>();
neutralExceptions = new HashMap<String, HashMap<String, ArrayList<FactionHint>>>();
containedFactions = new HashMap<String, HashMap<String, ArrayList<AltLocation>>>();
listeners = new ArrayList<ActionListener>();
lastUpdate = new Date(0); //initialize 1 Jan 1970
lastLocation = Planets.getInstance().getPlanets().get("Terra");
}
public static RandomFactionGenerator getInstance() {
if (rfg == null) {
rfg = new RandomFactionGenerator();
}
if (!initialized && !initializing) {
initializing = true;
rfg.loader = new Thread(new Runnable() {
public void run() {
Planets p = Planets.getInstance();
while (!p.isInitialized()) {
try {
Thread.sleep(50);
} catch (InterruptedException ignore) {
}
}
rfg.initialize();
}
}, "FactionGeography loader");
rfg.loader.setPriority(Thread.NORM_PRIORITY - 1);
rfg.loader.start();
}
return rfg;
}
public boolean isInitialized() {
return initialized;
}
private void initialize() {
try {
loadFactionHints();
} catch (DOMException e) {
MekHQ.logError(e);
} catch (ParseException e) {
MekHQ.logError(e);
}
initialized = true;
initializing = false;
for (ActionListener l : listeners) {
l.actionPerformed(new ActionEvent(this, 0, "FactionGeography initialized"));
}
}
public void dispose() {
if (initialized){
clear();
}
}
public void clear() {
rfg = null;
currentFactions.clear();
employers.clear();
borders.clear();
deepPeriphery.clear();
neutralFactions.clear();
majorPowers.clear();
wars.clear();
alliances.clear();
rivals.clear();
neutralExceptions.clear();
containedFactions.clear();
initialized = false;
initializing = false;
}
private void loadFactionHints() throws DOMException, ParseException {
MekHQ.logMessage("Starting load of faction hint data from XML...");
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
Document xmlDoc = null;
try {
FileInputStream fis = new FileInputStream("data/universe/factionhints.xml");
DocumentBuilder db = dbf.newDocumentBuilder();
xmlDoc = db.parse(fis);
} catch (Exception ex) {
MekHQ.logError(ex);
}
Element rootElement = xmlDoc.getDocumentElement();
NodeList nl = rootElement.getChildNodes();
rootElement.normalize();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < nl.getLength(); i++) {
final Date epoch_start = df.parse("0001-01-01");
final Date epoch_end = df.parse("9999-12-31");
Node wn = nl.item(i);
if (wn.getParentNode() != rootElement)
continue;
if (wn.getNodeType() == Node.ELEMENT_NODE) {
String nodeName = wn.getNodeName();
if (nodeName.equalsIgnoreCase("neutral")) {
String f = wn.getAttributes().getNamedItem("faction").getTextContent().trim();
if (Faction.getFaction(f) != null) {
neutralFactions.add(f);
addNeutralExceptions(f, wn);
} else {
MekHQ.logError("Invalid faction code in factionhints.xml: " + f);
}
} else if (nodeName.equalsIgnoreCase("deepPeriphery")) {
for (String f : wn.getTextContent().trim().split(",")) {
if (Faction.getFaction(f) != null) {
deepPeriphery.add(f);
} else {
MekHQ.logError("Invalid faction code in factionhints.xml: " + f);
}
}
} else if (nodeName.equalsIgnoreCase("majorPowers")) {
for (String f : wn.getTextContent().trim().split(",")) {
if (Faction.getFaction(f) != null) {
majorPowers.add(f);
} else {
MekHQ.logError("Invalid faction code in factionhints.xml: " + f);
}
}
} else if (nodeName.equalsIgnoreCase("rivals")) {
setFactionHint(rivals, wn);
} else if (nodeName.equalsIgnoreCase("war")) {
setFactionHint(wars, wn);
} else if (nodeName.equalsIgnoreCase("alliance")) {
setFactionHint(alliances, wn);
} else if (nodeName.equalsIgnoreCase("location")) {
Date start = epoch_start;
Date end = epoch_end;
double fraction = 0.0;
String outer = "UND";
String inner = "UND";
ArrayList<String> opponents = null;
if (wn.getAttributes().getNamedItem("start") != null) {
start = df.parse(wn.getAttributes().getNamedItem("start").getTextContent().trim());
}
if (wn.getAttributes().getNamedItem("end") != null) {
end = df.parse(wn.getAttributes().getNamedItem("end").getTextContent().trim());
}
for (int j = 0; j < wn.getChildNodes().getLength(); j++) {
Node wn2 = wn.getChildNodes().item(j);
if (wn2.getNodeName().equalsIgnoreCase("outer")) {
outer = wn2.getTextContent().trim();
} else if (wn2.getNodeName().equalsIgnoreCase("inner")) {
inner = wn2.getTextContent().trim();
} else if (wn2.getNodeName().equalsIgnoreCase("fraction")) {
fraction = Double.parseDouble(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("opponents")) {
opponents = new ArrayList<String>();
for (String faction : wn2.getTextContent().trim().split(",")) {
if (Faction.getFaction(faction) != null) {
opponents.add(faction);
}
}
}
}
if (Faction.getFaction(outer) != null && Faction.getFaction(inner) != null) {
if (containedFactions.get(outer) == null) {
containedFactions.put(outer, new HashMap<String, ArrayList<AltLocation>>());
}
if (containedFactions.get(outer).get(inner) == null) {
containedFactions.get(outer).put(inner, new ArrayList<AltLocation>());
}
containedFactions.get(outer).get(inner).add(new AltLocation(start, end, fraction, opponents));
} else {
MekHQ.logError("Invalid faction code in factionhints.xml: " + outer + "/" + inner);
}
}
}
}
}
private void setFactionHint(HashMap<String, HashMap<String, ArrayList<FactionHint>>> hint,
Node node) throws DOMException, ParseException {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
final Date epoch_start = df.parse("0001-01-01");
final Date epoch_end = df.parse("9999-12-31");
String name = null;
Date start = epoch_start;
Date end = epoch_end;
if (node.getAttributes().getNamedItem("name") != null) {
name = node.getAttributes().getNamedItem("name").getTextContent().trim();
}
if (node.getAttributes().getNamedItem("start") != null) {
start = df.parse(node.getAttributes().getNamedItem("start").getTextContent().trim());
}
if (node.getAttributes().getNamedItem("end") != null) {
end = df.parse(node.getAttributes().getNamedItem("end").getTextContent().trim());
}
for (int n = 0; n < node.getChildNodes().getLength(); n++) {
Node wn = node.getChildNodes().item(n);
if (wn.getNodeName().equalsIgnoreCase("parties")) {
Date localStart = start;
Date localEnd = end;
if (wn.getAttributes().getNamedItem("start") != null) {
localStart = df.parse(wn.getAttributes().getNamedItem("start").getTextContent().trim());
}
if (wn.getAttributes().getNamedItem("end") != null) {
localEnd = df.parse(wn.getAttributes().getNamedItem("end").getTextContent().trim());
}
String[] parties = wn.getTextContent().trim().split(",");
for (int i = 0; i < parties.length - 1; i++) {
if (Faction.getFaction(parties[i]) == null) {
MekHQ.logError("Invalid faction code in factionhints.xml: " + parties[i]);
continue;
}
for (int j = i + 1; j < parties.length; j++) {
if (Faction.getFaction(parties[j]) == null) {
MekHQ.logError("Invalid faction code in factionhints.xml: " + parties[j]);
continue;
}
if (hint.get(parties[i]) == null) {
hint.put(parties[i], new HashMap<String, ArrayList<FactionHint>>());
}
if (hint.get(parties[i]).get(parties[j]) == null) {
hint.get(parties[i]).put(parties[j], new ArrayList<FactionHint>());
}
hint.get(parties[i]).get(parties[j]).add(new FactionHint(name, localStart, localEnd));
}
}
}
}
}
private void addNeutralExceptions(String faction, Node node) throws DOMException, ParseException {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
final Date epoch_start = df.parse("0001-01-01");
final Date epoch_end = df.parse("9999-12-31");
Date start = epoch_start;
Date end = epoch_end;
if (node.getAttributes().getNamedItem("end") != null) {
end = df.parse(node.getAttributes().getNamedItem("end").getTextContent().trim());
}
for (int n = 0; n < node.getChildNodes().getLength(); n++) {
Node wn = node.getChildNodes().item(n);
if (wn.getNodeName().equalsIgnoreCase("exceptions")) {
Date localStart = start;
Date localEnd = end;
if (wn.getAttributes().getNamedItem("start") != null) {
localStart = df.parse(wn.getAttributes().getNamedItem("start").getTextContent().trim());
}
if (wn.getAttributes().getNamedItem("end") != null) {
localEnd = df.parse(wn.getAttributes().getNamedItem("end").getTextContent().trim());
}
String[] parties = wn.getTextContent().trim().split(",");
for (int i = 0; i < parties.length; i++) {
if (Faction.getFaction(parties[i]) == null) {
MekHQ.logError("Invalid faction code in factionhints.xml: " + parties[i]);
continue;
}
if (neutralExceptions.get(faction) == null) {
neutralExceptions.put(faction, new HashMap<String, ArrayList<FactionHint>>());
}
if (neutralExceptions.get(faction).get(parties[i]) == null) {
neutralExceptions.get(faction).put(parties[i], new ArrayList<FactionHint>());
}
neutralExceptions.get(faction).get(parties[i]).add(new FactionHint(faction, localStart, localEnd));
}
}
}
}
public void registerListener(ActionListener l) {
listeners.add(l);
}
public void removeListener(ActionListener l) {
listeners.remove(l);
}
public void updateTables(Date date, Planet currentLocation,
CampaignOptions options) {
final Date FORTRESS_REPUBLIC = new Date (new GregorianCalendar(3135,10,1).getTimeInMillis());
if (!date.after(lastUpdate) && currentLocation == lastLocation) {
return;
}
lastUpdate.setTime(date.getTime());
lastLocation = currentLocation;
employers.clear();
borders.clear();
currentFactions.clear();
for (Planet p : Planets.getInstance().getPlanets().values()) {
for (Faction f : p.getFactionSet(Utilities.getDateTimeDay(date))) {
String fName = f.getShortName();
if (fName.equals("ABN") ||
fName.equals("UND") ||
fName.equals("CLAN") ||
fName.equals("NONE")) {
continue;
}
if (fName.equals("ROS") && date.after(FORTRESS_REPUBLIC)) {
continue;
}
currentFactions.add(fName);
if (p.getDistanceTo(currentLocation) <= options.getSearchRadius()) {
if (!f.isClan()) {
employers.add(fName);
}
updateBorders(f, p);
}
}
}
/* Add factions which do not control any planets to the employer list */
for (String emp : getEmployerSet()) {
int count = 0;
for(String cf : getContainedFactions(emp, date)) {
currentFactions.add(cf);
if (Faction.getFaction(cf).isClan()) {
continue;
}
if (count == 0) {
for (String s : employers) {
if (s.equals(emp)) {
count++;
}
}
}
for (int i = 0; i < count * getAltLocationFraction(emp, cf, date) + 0.5; i++) {
employers.add(cf);
}
}
}
//Add rebels and pirates
currentFactions.add("REB");
currentFactions.add("PIR");
}
private void updateBorders(Faction f, Planet p) {
int distance = BORDER_RANGE_IS;
if (f.isClan()) {
distance = BORDER_RANGE_CLAN;
} else if (deepPeriphery.contains(f.getShortName())) {
distance = BORDER_RANGE_DEEP_PERIPHERY;
} else if (f.isPeriphery()) {
distance = BORDER_RANGE_NEAR_PERIPHERY;
}
for (Planet planetKey : Planets.getInstance().getNearbyPlanets(p, distance)) {
for (Faction f2 : planetKey.getFactionSet(Utilities.getDateTimeDay(lastUpdate))) {
String eName = f2.getShortName();
if (eName.equals("ABN") ||
eName.equals("UND") ||
eName.equals("CLAN") ||
eName.equals("NONE")) {
continue;
}
addBorderEnemy(f.getShortName(), p, eName);
/* Go through all factions contained within f and add this
* planet to their border with this enemy as well */
for(String cf : getContainedFactions(f.getShortName(), lastUpdate)) {
if (isContainedFactionOpponent(f.getShortName(), cf, eName, lastUpdate)) {
addBorderEnemy(cf, p, eName);
}
}
}
}
/* If a faction is located within a faction with which it may be at war,
* add this planet to both borders. */
for (String cf : getContainedFactions(f.getShortName(), lastUpdate)) {
if (isContainedFactionOpponent(f.getShortName(), cf, f.getShortName(), lastUpdate)) {
addBorderEnemy(cf, p, f.getShortName());
addBorderEnemy(f.getShortName(), p, cf);
}
}
}
private void addBorderEnemy(String fName, Planet p, String eName) {
final Date FORTRESS_REPUBLIC = new Date (new GregorianCalendar(3135,10,1).getTimeInMillis());
if (borders.get(fName) == null) {
borders.put(fName, new HashMap<String, HashSet<Planet>>());
borders.get(fName).put("REB", new HashSet<Planet>());
borders.get(fName).put("PIR", new HashSet<Planet>());
}
if (fName.equals(eName)) {
borders.get(fName).get("REB").add(p);
if (hintApplies(wars, fName, fName, lastUpdate)) {
if (borders.get(fName).get(eName) == null) {
borders.get(fName).put(eName, new HashSet<Planet>());
}
borders.get(fName).get(eName).add(p);
}
} else {
/* Locate pirate activity primarily along borders */
borders.get(fName).get("PIR").add(p);
if (borders.get("PIR") == null) {
borders.put("PIR", new HashMap<String, HashSet<Planet>>());
}
if (borders.get("PIR").get(fName) == null) {
borders.get("PIR").put(fName, new HashSet<Planet>());
}
borders.get("PIR").get(fName).add(p);
/* Ignore allies (unless both are Clan) */
if (hintApplies(alliances, fName, eName, lastUpdate) &&
!(Faction.getFaction(fName).isClan() &&
Faction.getFaction(eName).isClan())) {
return;
}
if (neutralFactions.contains(fName) &&
!hintApplies(neutralExceptions, fName, eName, lastUpdate)) {
return;
}
if (neutralFactions.contains(eName) &&
!hintApplies(neutralExceptions, eName, fName, lastUpdate)) {
return;
}
if ((fName.equals("ROS") || eName.equals("ROS")) &&
lastUpdate.after(FORTRESS_REPUBLIC)) {
return;
}
/* Otherwise add planet to the border */
if (borders.get(fName).get(eName) == null) {
borders.get(fName).put(eName, new HashSet<Planet>());
}
borders.get(fName).get(eName).add(p);
}
}
public TreeSet<String> getCurrentFactions() {
return currentFactions;
}
public String getEmployer() {
return Utilities.getRandomItem(employers);
}
public String getEnemy(String fName, boolean useRebels) {
/* Rebels occur on a 1-4 (d20) on nearly every enemy chart */
if (useRebels && Compute.randomInt(5) == 0) {
return "REB";
}
if (borders.get(fName) != null) {
ArrayList<String> enemiesList = new ArrayList<String>();
for (String eName : borders.get(fName).keySet()) {
if (eName.equals("PIR") ||
eName.equals("REB")) {
continue;
/*Pirate and rebel opponent frequency is not
* based on borders.
*/
}
double totalCount = borders.get(fName).get(eName).size();
double count = totalCount;
/* Divide border between the main faction and any factions
* that might be contained within it. */
for (String cf : getContainedFactions(eName, lastUpdate)) {
if (!isContainedFactionOpponent(eName, cf, fName, lastUpdate)) {
continue;
}
if ((neutralFactions.contains(cf) &&
!hintApplies(neutralExceptions, cf, eName, lastUpdate)) ||
neutralFactions.contains(eName) &&
!hintApplies(neutralExceptions, eName, cf, lastUpdate)) {
continue;
}
double cfCount = totalCount;
if (getAltLocationFraction(eName, cf, lastUpdate) > 0.0) {
cfCount = totalCount * getAltLocationFraction(eName, cf, lastUpdate);
count -= cfCount;
}
cfCount = adjustBorderWeight(cfCount, fName, eName, lastUpdate);
for (int i = 0; i < (cfCount + 0.5); i++) {
enemiesList.add(cf);
}
}
count = adjustBorderWeight(count, fName, eName, lastUpdate);
for (int i = 0; i < (count + 0.5); i++) {
enemiesList.add(eName);
}
}
if (enemiesList.size() > 0) {
return Utilities.getRandomItem(enemiesList);
}
}
MekHQ.logMessage("Could not find enemy for " + fName);
return "PIR";
}
public HashSet<String> getEmployerSet() {
HashSet<String> retval = new HashSet<String>();
for (String fName : employers) {
retval.add(fName);
}
return retval;
}
public ArrayList<String> getEnemyList(String employer) {
ArrayList<String> list = new ArrayList<String>();
if (borders.get(employer) != null) {
for (String enemy : borders.get(employer).keySet()) {
list.add(enemy);
for (String cf : getContainedFactions(enemy, lastUpdate)) {
if (isContainedFactionOpponent(enemy, cf, employer, lastUpdate)) {
list.add(cf);
}
}
}
}
return list;
}
private double adjustBorderWeight(double count, String f,
String enemy, Date date) {
final Date tukayyid = new Date (new GregorianCalendar(3052,5,20).getTimeInMillis());
if (Faction.getFaction(f).isClan() && Faction.getFaction(enemy).isClan() &&
(hintApplies(alliances, f, enemy, date) ||
(date.before(tukayyid) &&
borders.get(f).get(enemy) != null &&
borders.get(f).get(enemy).iterator().next().getY() < 600))) {
/* Treat invading Clans as allies in the Inner Sphere */
count /= 4.0;
}
if (hintApplies(wars, f, enemy, date) && f != enemy) {
count *= 2.0;
}
if (hintApplies(rivals, f, enemy, date)) {
count *= 2.0;
}
/* This is pretty hacky, but ComStar does not have many targets
* and tends to fight the Clans too much between Tukayyid and
* the Jihad.
*/
if (f.equals("CS") && Faction.getFaction(enemy).isClan()) {
count /= 12.0;
}
return count;
}
public String getMissionTarget(String attacker, String defender, Date date) {
ArrayList<Planet> planetList = getMissionTargetList(attacker, defender, date);
if (planetList.size() > 0) {
return Utilities.getRandomItem(planetList).getId();
}
return null;
}
public ArrayList<Planet> getMissionTargetList(String attacker, String defender, Date date) {
ArrayList<Planet> planetList = new ArrayList<Planet>();
int maxJumps = 3;
if (deepPeriphery.contains(attacker) || deepPeriphery.contains(defender)) {
maxJumps = 8;
}
HashSet<Planet> border = null;
if (borders.get(attacker) != null && borders.get(attacker).get(defender) != null) {
border = borders.get(attacker).get(defender);
} else {
/* No border with defender found among systems controlled by
* attacker; check for presence of attacker and defender
* in systems controlled by other factions.
*/
for (String fName : Faction.getFactionList()) {
for (String cf : getContainedFactions(fName, date)) {
if (cf.equals(attacker) && isContainedFactionOpponent(fName, cf, defender, date)) {
if (borders.get(fName) != null && borders.get(fName).get(defender) != null) {
border = borders.get(fName).get(defender);
attacker = fName;
break;
}
}
if (cf.equals(defender) && isContainedFactionOpponent(fName, cf, attacker, date)) {
if (borders.get(attacker) != null && borders.get(attacker).get(fName) != null) {
border = borders.get(attacker).get(fName);
defender = fName;
break;
}
}
}
}
}
if (border != null) {
for (Planet startingPlanet : border) {
for (Planet planetKey : Planets.getInstance().getNearbyPlanets(startingPlanet, maxJumps * 30)) {
for (Faction f : planetKey.getFactionSet(Utilities.getDateTimeDay(date))) {
if (f.getShortName().equals(defender) ||
defender.equals("PIR") ||
(f.getShortName().equals(attacker) && defender.equals("REB"))) {
planetList.add(planetKey);
}
}
}
}
}
if (planetList.size() == 0 && borders.get(defender) != null &&
borders.get(defender).get(attacker) != null) {
/* Most likely a deep periphery defender against a non-deep
* attacker; use one of the border planets */
for (Planet p : borders.get(defender).get(attacker)) {
planetList.add(p);
}
}
return planetList;
}
public boolean isMajorPower(Faction f) {
return majorPowers.contains(f.getShortName());
}
public boolean isISMajorPower(String fName) {
return majorPowers.contains(fName);
}
public boolean isAtWarWith(String f1, String f2, Date date) {
return hintApplies(wars, f1, f2, date);
}
public String getCurrentWar(String f1, String f2, Date date) {
if (wars.get(f1) != null && wars.get(f1).get(f2) != null) {
for (FactionHint fh : wars.get(f1).get(f2)) {
if (date.after(fh.start) && date.before(fh.end)) {
return fh.name;
}
}
}
return null;
}
public boolean isNeutral(String faction) {
return neutralFactions.contains(faction);
}
private boolean hintApplies(HashMap<String, HashMap<String, ArrayList<FactionHint>>> hints,
String f1, String f2, Date date) {
if (hints.get(f1) != null && hints.get(f1).get(f2) != null) {
for (FactionHint fh : hints.get(f1).get(f2)) {
if (date.after(fh.start) && date.before(fh.end)) {
return true;
}
}
}
if (hints.get(f2) != null && hints.get(f2).get(f1) != null) {
for (FactionHint fh : hints.get(f2).get(f1)) {
if (date.after(fh.start) && date.before(fh.end)) {
return true;
}
}
}
return false;
}
private HashSet<String> getContainedFactions(String f, Date date) {
HashSet<String> retval = new HashSet<String>();
if (containedFactions.get(f) != null) {
for (String f2 : containedFactions.get(f).keySet()) {
for (AltLocation l : containedFactions.get(f).get(f2)) {
if (date.after(l.start) && date.before(l.end)) {
retval.add(f2);
}
}
}
}
return retval;
}
private double getAltLocationFraction(String f1, String f2, Date date) {
if (containedFactions.get(f1) != null && containedFactions.get(f1).get(f2) != null) {
for (AltLocation l : containedFactions.get(f1).get(f2)) {
if (date.after(l.start) && date.before(l.end)) {
return l.fraction;
}
}
}
return 0.0;
}
public boolean isContainedFactionOpponent(Faction f1, Faction f2,
Faction opponent, Date date) {
return isContainedFactionOpponent(f1.getShortName(),
f2.getShortName(), opponent.getShortName(), date);
}
public boolean isContainedFactionOpponent(String outer, String inner,
String opponent, Date date) {
if (containedFactions.get(outer) != null && containedFactions.get(outer).get(inner) != null) {
for (AltLocation l : containedFactions.get(outer).get(inner)) {
if (date.after(l.start) && date.before(l.end)) {
if (l.opponents == null) {
return !inner.equals(opponent) ||
hintApplies(wars, inner, inner, date);
}
for (String f : l.opponents) {
if (f == opponent) {
return true;
}
}
}
}
}
return false;
}
class FactionHint {
/**
* Each participant in a war or an alliance has one instance
* of this class for each of the other factions involved.
*/
public String name;
public Date start;
public Date end;
public FactionHint (String n, Date s, Date e) {
name = n;
start = (Date)s.clone();
end = (Date)e.clone();
}
}
class AltLocation extends FactionHint {
public double fraction;
public ArrayList<String> opponents;
public AltLocation (Date s, Date e, double f, ArrayList<String> opponents) {
super(null, s, e);
fraction = f;
this.opponents = opponents;
}
}
}