/*
* Lance.java
*
* Copyright (c) 2011 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.force;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.UUID;
import megamek.common.Compute;
import megamek.common.Entity;
import megamek.common.EntityWeightClass;
import megamek.common.Infantry;
import megamek.common.UnitType;
import mekhq.MekHQ;
import mekhq.MekHqXmlSerializable;
import mekhq.MekHqXmlUtil;
import mekhq.campaign.Campaign;
import mekhq.campaign.mission.AtBContract;
import mekhq.campaign.mission.AtBScenario;
import mekhq.campaign.mission.Mission;
import mekhq.campaign.personnel.Person;
import mekhq.campaign.unit.Unit;
import mekhq.campaign.universe.Faction;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Used by Against the Bot to track additional information about each force
* on the TO&E that has at least one unit assigned. Extra info includes whether
* the force counts as a lance (or star or level II) eligible for assignment
* to a mission role and what the assignment is on which contract.
*
* @author Neoancient
*
*/
public class Lance implements Serializable, MekHqXmlSerializable {
private static final long serialVersionUID = -1197697940987478509L;
public static final int ROLE_UNASSIGNED = 0;
public static final int ROLE_FIGHT = 1;
public static final int ROLE_DEFEND = 2;
public static final int ROLE_SCOUT = 3;
public static final int ROLE_TRAINING = 4;
public static final int ROLE_NUM = 5;
public static final String[] roleNames = {
"Unassigned", "Fight", "Defend", "Scout", "Training"
};
public static final int STR_IS = 4;
public static final int STR_CLAN = 5;
public static final int STR_CS = 6;
public static final long ETYPE_GROUND = Entity.ETYPE_MECH |
Entity.ETYPE_TANK | Entity.ETYPE_INFANTRY | Entity.ETYPE_PROTOMECH;
private int forceId;
private int missionId;
private int role;
private UUID commanderId;
public static int getStdLanceSize(Faction f) {
if (f.isClan()) {
return STR_CLAN;
} else if (f.getShortName().equals("CS") || f.getShortName().equals("WOB")) {
return STR_CS;
} else {
return STR_IS;
}
}
public Lance() {}
public Lance(int fid, Campaign c) {
forceId = fid;
role = ROLE_UNASSIGNED;
missionId = -1;
for (Mission m : c.getSortedMissions()) {
if (!m.isActive()) {
break;
}
if (m instanceof AtBContract) {
if (null == ((AtBContract)m).getParentContract()) {
missionId = m.getId();
} else {
missionId = ((AtBContract)m).getParentContract().getId();
}
}
}
commanderId = findCommander(forceId, c);
}
public int getForceId() {
return forceId;
}
public int getMissionId() {
return missionId;
}
public AtBContract getContract(Campaign c) {
return (AtBContract)c.getMission(missionId);
}
public void setContract(AtBContract c) {
if (null == c) {
missionId = -1;
} else {
missionId = c.getId();
}
}
public int getRole() {
return role;
}
public void setRole(int role) {
this.role = role;
}
public UUID getCommanderId() {
return commanderId;
}
public Person getCommander(Campaign c) {
return c.getPerson(commanderId);
}
public void setCommander(UUID id) {
commanderId = id;
}
public void setCommander(Person p) {
commanderId = p.getId();
}
public void refreshCommander(Campaign c) {
commanderId = findCommander(forceId, c);
}
public int getSize(Campaign c) {
if (c.getFaction().isClan()) {
return (int)Math.ceil(getEffectivePoints(c));
}
if (c.getForce(forceId) != null) {
return c.getForce(forceId).getUnits().size();
} else {
return 0;
}
}
public double getEffectivePoints(Campaign c) {
/* Used to check against force size limits; for this purpose we
* consider a 'Mech and a Point of BA to be a single Point so that
* a Nova that has 10 actual Points is calculated as 5 effective
* Points. We also count Points of vehicles with 'Mechs and
* conventional infantry with BA to account for CHH vehicle Novas.
*/
double armor = 0.0;
double infantry = 0.0;
double other = 0.0;
for (UUID id : c.getForce(forceId).getUnits()) {
Unit unit = c.getUnit(id);
if (null != unit) {
Entity entity = unit.getEntity();
if (null != entity) {
if ((entity.getEntityType() & Entity.ETYPE_MECH) != 0) {
armor += 1;
} else if ((entity.getEntityType() & Entity.ETYPE_AERO) != 0) {
other += 0.5;
} else if ((entity.getEntityType() & Entity.ETYPE_TANK) != 0) {
armor += 0.5;
} else if ((entity.getEntityType() & Entity.ETYPE_PROTOMECH) != 0) {
other += 0.2;
} else if ((entity.getEntityType() & Entity.ETYPE_INFANTRY) != 0) {
infantry += ((Infantry)entity).isSquad()?0.2:1;
}
}
}
}
return Math.max(armor, infantry) + other;
}
public int getWeightClass(Campaign c) {
/* Clan units only count half the weight of ASF and vehicles
* (2/Point). IS units only count half the weight of vehicles
* if the option is enabled, possibly dropping the lance to a lower
* weight class and decreasing the enemy force against vehicle/combined
* lances.
*/
double weight = 0.0;
for (UUID id : c.getForce(forceId).getUnits()) {
Unit unit = c.getUnit(id);
if (null != unit) {
Entity entity = unit.getEntity();
if (null != entity) {
if ((entity.getEntityType() & Entity.ETYPE_MECH) != 0 ||
(entity.getEntityType() & Entity.ETYPE_PROTOMECH) != 0 ||
(entity.getEntityType() & Entity.ETYPE_INFANTRY) != 0) {
weight += entity.getWeight();
} else if ((entity.getEntityType() & Entity.ETYPE_TANK) != 0) {
if (c.getFaction().isClan() || c.getCampaignOptions().getAdjustPlayerVehicles()) {
weight += entity.getWeight() * 0.5;
} else {
weight += entity.getWeight();
}
} else if ((entity.getEntityType() & Entity.ETYPE_AERO) != 0) {
if (c.getFaction().isClan()) {
weight += entity.getWeight() * 0.5;
} else {
weight += entity.getWeight();
}
}
}
}
}
weight = weight * 4.0 / getStdLanceSize(c.getFaction());
if (weight < 40) {
return EntityWeightClass.WEIGHT_ULTRA_LIGHT;
}
if (weight <= 130) {
return EntityWeightClass.WEIGHT_LIGHT;
}
if (weight <= 200) {
return EntityWeightClass.WEIGHT_MEDIUM;
}
if (weight <= 280) {
return EntityWeightClass.WEIGHT_HEAVY;
}
if (weight <= 390) {
return EntityWeightClass.WEIGHT_ASSAULT;
}
return EntityWeightClass.WEIGHT_SUPER_HEAVY;
}
public boolean isEligible(Campaign c) {
/* Check that the number of units and weight are within the limits
* and that the force contains at least one ground unit. */
if (c.getCampaignOptions().getLimitLanceNumUnits()) {
int size = getSize(c);
if (size < getStdLanceSize(c.getFaction()) - 1 ||
size > getStdLanceSize(c.getFaction()) + 2) {
return false;
}
}
if (c.getCampaignOptions().getLimitLanceWeight() &&
getWeightClass(c) > EntityWeightClass.WEIGHT_ASSAULT) {
return false;
}
boolean hasGround = false;
for (UUID id : c.getForce(forceId).getUnits()) {
Unit unit = c.getUnit(id);
if (null != unit) {
Entity entity = unit.getEntity();
if (null != entity) {
if (UnitType.determineUnitTypeCode(entity) >= UnitType.JUMPSHIP) {
return false;
}
if ((entity.getEntityType() & ETYPE_GROUND) != 0) {
hasGround = true;
}
}
}
}
return hasGround;
}
/* Code to find unit commander from ForceViewPanel */
public static UUID findCommander(int forceId, Campaign c) {
ArrayList<Person> people = new ArrayList<Person>();
for(UUID uid : c.getForce(forceId).getAllUnits()) {
Unit u = c.getUnit(uid);
if(null != u) {
Person p = u.getCommander();
if(null != p) {
people.add(p);
}
}
}
//sort person vector by rank
Collections.sort(people, new Comparator<Person>(){
public int compare(final Person p1, final Person p2) {
return ((Comparable<Integer>)p2.getRankNumeric()).compareTo(p1.getRankNumeric());
}
});
if(people.size() > 0) {
return people.get(0).getId();
}
return null;
}
public static Date getBattleDate(GregorianCalendar c) {
GregorianCalendar calendar = (GregorianCalendar)c.clone();
calendar.add(Calendar.DATE, Compute.randomInt(7));
return calendar.getTime();
}
public AtBScenario checkForBattle(Campaign c) {
int noBattle;
int roll;
//thresholds are coded from charts with 1-100 range, so we add 1 to mod to adjust 0-based random int
int battleTypeMod = 1 + (AtBContract.MORALE_NORMAL - getContract(c).getMoraleLevel()) * 5;
battleTypeMod += getContract(c).getBattleTypeMod();
switch (role) {
case ROLE_FIGHT:
noBattle = (int)(60.0 / c.getCampaignOptions().getIntensity() + 0.5);
roll = Compute.randomInt(40 + noBattle) + battleTypeMod;
if (roll < 1) {
return new AtBScenario(c, this,
AtBScenario.BASEATTACK, false,
getBattleDate(c.getCalendar()));
} else if (roll < 9) {
return new AtBScenario(c, this,
AtBScenario.BREAKTHROUGH, true,
getBattleDate(c.getCalendar()));
} else if (roll < 9 + noBattle) {
return null;
} else if (roll < 17 + noBattle) {
return new AtBScenario(c, this,
AtBScenario.STANDUP, true,
getBattleDate(c.getCalendar()));
} else if (roll < 25 + noBattle) {
return new AtBScenario(c, this,
AtBScenario.STANDUP, false,
getBattleDate(c.getCalendar()));
} else if (roll < 33 + noBattle) {
return new AtBScenario(c, this,
AtBScenario.CHASE, false,
getBattleDate(c.getCalendar()));
} else if (roll < 41 + noBattle) {
return new AtBScenario(c, this,
AtBScenario.HOLDTHELINE, true,
getBattleDate(c.getCalendar()));
} else {
return new AtBScenario(c, this,
AtBScenario.BASEATTACK, true,
getBattleDate(c.getCalendar()));
}
case Lance.ROLE_SCOUT:
noBattle = (int)(40.0 / c.getCampaignOptions().getIntensity() + 0.5);
roll = Compute.randomInt(60 + noBattle) + battleTypeMod;
if (roll < 1) {
return new AtBScenario(c, this,
AtBScenario.BASEATTACK, false,
getBattleDate(c.getCalendar()));
} else if (roll < 11) {
return new AtBScenario(c, this,
AtBScenario.CHASE, true,
getBattleDate(c.getCalendar()));
} else if (roll < 21) {
return new AtBScenario(c, this,
AtBScenario.HIDEANDSEEK, true,
getBattleDate(c.getCalendar()));
} else if (roll < 31) {
return new AtBScenario(c, this,
AtBScenario.PROBE, true,
getBattleDate(c.getCalendar()));
} else if (roll < 41) {
return new AtBScenario(c, this,
AtBScenario.PROBE, true,
getBattleDate(c.getCalendar()));
} else if (roll < 41 + noBattle) {
return null;
} else if (roll < 51 + noBattle) {
return new AtBScenario(c, this,
AtBScenario.EXTRACTION, true,
getBattleDate(c.getCalendar()));
} else {
return new AtBScenario(c, this,
AtBScenario.RECONRAID, true,
getBattleDate(c.getCalendar()));
}
case Lance.ROLE_DEFEND:
noBattle = (int)(80.0 / c.getCampaignOptions().getIntensity() + 0.5);
roll = Compute.randomInt(20 + noBattle) + battleTypeMod;
if (roll < 1) {
return new AtBScenario(c, this,
AtBScenario.BASEATTACK, false,
getBattleDate(c.getCalendar()));
} else if (roll < 5) {
return new AtBScenario(c, this,
AtBScenario.HOLDTHELINE, false,
getBattleDate(c.getCalendar()));
} else if (roll < 9) {
return new AtBScenario(c, this,
AtBScenario.RECONRAID, false,
getBattleDate(c.getCalendar()));
} else if (roll < 13) {
return new AtBScenario(c, this,
AtBScenario.EXTRACTION, false,
getBattleDate(c.getCalendar()));
} else if (roll < 13 + noBattle) {
return null;
} else if (roll < 17 + noBattle) {
return new AtBScenario(c, this,
AtBScenario.HIDEANDSEEK, true,
getBattleDate(c.getCalendar()));
} else {
return new AtBScenario(c, this,
AtBScenario.BREAKTHROUGH, false,
getBattleDate(c.getCalendar()));
}
case Lance.ROLE_TRAINING:
noBattle = (int)(90.0 / c.getCampaignOptions().getIntensity() + 0.5);
roll = Compute.randomInt(10 + noBattle) + battleTypeMod;
if (roll < 1) {
return new AtBScenario(c, this,
AtBScenario.BASEATTACK, false,
getBattleDate(c.getCalendar()));
} else if (roll < 3) {
return new AtBScenario(c, this,
AtBScenario.HOLDTHELINE, false,
getBattleDate(c.getCalendar()));
} else if (roll < 5) {
return new AtBScenario(c, this,
AtBScenario.BREAKTHROUGH, true,
getBattleDate(c.getCalendar()));
} else if (roll < 7) {
return new AtBScenario(c, this,
AtBScenario.CHASE, true,
getBattleDate(c.getCalendar()));
} else if (roll < 9) {
return new AtBScenario(c, this,
AtBScenario.HIDEANDSEEK, false,
getBattleDate(c.getCalendar()));
} else if (roll < 9 + noBattle) {
return null;
} else {
return new AtBScenario(c, this,
AtBScenario.CHASE, false,
getBattleDate(c.getCalendar()));
}
default:
return null;
}
}
@Override
public void writeToXml(PrintWriter pw1, int indent) {
pw1.println(MekHqXmlUtil.indentStr(indent) + "<lance type=\""
+this.getClass().getName()
+"\">");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<forceId>"
+ forceId
+"</forceId>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<missionId>"
+ missionId
+"</missionId>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<role>"
+ role
+"</role>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<commanderId>"
+ commanderId
+"</commanderId>");
pw1.println(MekHqXmlUtil.indentStr(indent) + "</lance>");
}
public static Lance generateInstanceFromXML(Node wn) {
Lance retVal = null;
NamedNodeMap attrs = wn.getAttributes();
Node classNameNode = attrs.getNamedItem("type");
String className = classNameNode.getTextContent();
try {
retVal = (Lance) Class.forName(className).newInstance();
NodeList nl = wn.getChildNodes();
for (int x=0; x<nl.getLength(); x++) {
Node wn2 = nl.item(x);
if (wn2.getNodeName().equalsIgnoreCase("forceId")) {
retVal.forceId = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("missionId")) {
retVal.missionId = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("role")) {
retVal.role = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("commanderId")) {
retVal.commanderId = UUID.fromString(wn2.getTextContent());
}
}
} catch (Exception ex) {
MekHQ.logError(ex);
}
return retVal;
}
}