/*
* Copyright (C) 2017 - The MegaMek Team
*
* 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.parts;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import megamek.common.Aero;
import megamek.common.Entity;
import megamek.common.Mech;
import megamek.common.Tank;
import megamek.common.TargetRoll;
import mekhq.MekHQ;
import mekhq.campaign.Campaign;
import mekhq.campaign.event.PartChangedEvent;
import mekhq.campaign.parts.equipment.AmmoBin;
import mekhq.campaign.personnel.Person;
import mekhq.campaign.personnel.SkillType;
import mekhq.campaign.unit.Unit;
import mekhq.campaign.work.IPartWork;
/**
* An abstraction of all the pod-mounted equipment within a single location of an omni unit. Used
* to group them together as recipients of a single tech action.
*
* @author Neoancient
*
*/
public class PodSpace implements Serializable, IPartWork {
private static final long serialVersionUID = -9022671736030862210L;
protected Campaign campaign;
protected Unit unit;
protected int location;
protected List<Integer> childPartIds = new ArrayList<>();
protected UUID teamId;
protected int timeSpent = 0;
protected boolean workingOvertime = false;
protected int shorthandedMod = 0;
protected boolean repairInPlace = false;
public PodSpace() {
this(Entity.LOC_NONE, null);
}
public PodSpace(int location, Unit unit) {
this.location = location;
this.unit = unit;
this.campaign = unit.campaign;
//We don't need a LOC_WINGS podspace, but we do need one for the fuselage equipment, which is stored at LOC_NONE.
if (unit.getEntity() instanceof Aero && location == Aero.LOC_WINGS) {
this.location = -1;
}
}
@Override
public int getBaseTime() {
return 30;
}
public List<Part> getPartList() {
return childPartIds.stream().map(id -> campaign.getPart(id))
.filter(Objects::nonNull).collect(Collectors.toList());
}
@Override
public void updateConditionFromEntity(boolean checkForDestruction) {
childPartIds.clear();
for (Part part : getUnit().getParts()) {
if (part.isOmniPodded() && part.getLocation() == location) {
childPartIds.add(part.getId());
}
}
}
@Override
public void updateConditionFromPart() {
//nothing to do here
}
@Override
public void remove(boolean salvage) {
shorthandedMod = 0;
//Iterate through all pod-mounted equipment in space and remove them.
for (int pid : childPartIds) {
final Part part = campaign.getPart(pid);
if (part != null) {
part.remove(salvage);
MekHQ.triggerEvent(new PartChangedEvent(part));
}
}
updateConditionFromEntity(false);
}
@Override
public void fix() {
shorthandedMod = 0;
for (int pid : childPartIds) {
final Part part = campaign.getPart(pid);
if (part != null && !(part instanceof MissingPart)
&& !(part instanceof AmmoBin)
&& part.needsFixing()
&& !repairInPlace) {
part.remove(true);
MekHQ.triggerEvent(new PartChangedEvent(part));
}
}
updateConditionFromEntity(false);
for (int pid : childPartIds) {
final Part part = campaign.getPart(pid);
if (part != null && part instanceof MissingPart) {
part.fix();
MekHQ.triggerEvent(new PartChangedEvent(part));
}
}
updateConditionFromEntity(false);
}
@Override
public MissingPart getMissingPart() {
return null;
}
@Override
public String checkFixable() {
if(isSalvaging() || location < 0) {
return null;
}
// The part is only fixable if the location is not destroyed.
// be sure to check location and second location
if(null != unit) {
if (unit.isLocationBreached(location)) {
return unit.getEntity().getLocationName(location) + " is breached.";
}
if (unit.isLocationDestroyed(location)) {
return unit.getEntity().getLocationName(location) + " is destroyed.";
}
if (repairInPlace) {
for (int id : childPartIds) {
final Part p = unit.campaign.getPart(id);
if (p != null && p instanceof MissingPart) {
return null;
}
}
return unit.getEntity().getLocationName(location) + " is not missing any pod-mounted equipment.";
} else {
for (int id : childPartIds) {
final Part p = unit.campaign.getPart(id);
if (p == null || !p.needsFixing()) {
continue;
}
MissingPart missing = null;
if (p instanceof MissingPart) {
missing = (MissingPart)p;
} else {
missing = p.getMissingPart();
}
if (missing.isReplacementAvailable()) {
return null;
}
}
return "There are no replacement parts available for "
+ unit.getEntity().getLocationName(location) + ".";
}
}
return null;
}
@Override
public boolean needsFixing() {
return childPartIds.stream()
.map(id -> campaign.getPart(id)).filter(Objects::nonNull)
.anyMatch(p -> !(p instanceof AmmoBin) && p.needsFixing());
}
@Override
public int getDifficulty() {
if (unit.getEntity() instanceof Tank) {
return 0;
}
return -2;
}
public String getLocationName() {
if (getUnit() != null) {
if (getUnit().getEntity() instanceof Aero && location == Entity.LOC_NONE) {
return "Fuselage";
} else {
return getUnit().getEntity().getLocationName(location);
}
}
return null;
}
public int getLocation() {
return location;
}
@Override
public TargetRoll getAllMods(Person tech) {
TargetRoll mods = new TargetRoll(getDifficulty(), "difficulty");
if(null != unit) {
mods.append(unit.getSiteMod());
if(unit.getEntity().hasQuirk("easy_maintain")) {
mods.addModifier(-1, "easy to maintain");
}
else if(unit.getEntity().hasQuirk("difficult_maintain")) {
mods.addModifier(1, "difficult to maintain");
}
}
return mods;
}
@Override
public String succeed() {
if (isSalvaging()) {
remove(true);
return " <font color='green'><b> removed.</b></font>";
} else {
fix();
return " <font color='green'><b> fixed.</b></font>";
}
}
@Override
public String fail(int rating) {
timeSpent = 0;
shorthandedMod = 0;
boolean replacing = false;
for (int id : childPartIds) {
final Part part = campaign.getPart(id);
if (part != null && (isSalvaging() ||
(!(part instanceof AmmoBin) && part.needsFixing()))) {
part.fail(rating);
replacing |= part instanceof MissingPart;
}
}
if(rating >= SkillType.EXP_ELITE && replacing) {
return " <font color='red'><b> failed and part(s) destroyed.</b></font>";
} else {
return " <font color='red'><b> failed.</b></font>";
}
}
@Override
public UUID getTeamId() {
return teamId;
}
@Override
public boolean isBeingWorkedOn() {
return teamId != null;
}
@Override
public String getPartName() {
return getLocationName() + " Pod Space";
}
@Override
public int getSkillMin() {
int minSkill = SkillType.EXP_GREEN;
for (int id : childPartIds) {
final Part part = campaign.getPart(id);
if (part != null) {
if ((isSalvaging() && !(part instanceof MissingPart))
|| (!isSalvaging() && (part instanceof MissingPart)
|| (!(part instanceof AmmoBin) && part.needsFixing()))) {
minSkill = Math.max(minSkill, part.getSkillMin());
}
}
}
return minSkill;
}
@Override
public int getActualTime() {
return getBaseTime();
}
@Override
public int getTimeSpent() {
return timeSpent;
}
@Override
public int getTimeLeft() {
return getActualTime() - getTimeSpent();
}
@Override
public void addTimeSpent(int time) {
timeSpent += time;
}
@Override
public void resetTimeSpent() {
timeSpent = 0;
}
@Override
public void resetOvertime() {
workingOvertime = false;
}
@Override
public boolean isRightTechType(String skillType) {
if (unit.getEntity() instanceof Mech) {
return skillType.equals(SkillType.S_TECH_MECH);
} else if (unit.getEntity() instanceof Aero) {
return skillType.equals(SkillType.S_TECH_AERO);
} else if (unit.getEntity() instanceof Tank) {
return skillType.equals(SkillType.S_TECH_MECHANIC);
}
return false;
}
@Override
public TargetRoll getAllModsForMaintenance() {
return null;
}
@Override
public void setTeamId(UUID id) {
teamId = id;
}
@Override
public boolean hasWorkedOvertime() {
return workingOvertime;
}
@Override
public void setWorkedOvertime(boolean b) {
workingOvertime = b;
}
@Override
public int getShorthandedMod() {
return shorthandedMod;
}
@Override
public void setShorthandedMod(int i) {
shorthandedMod = i;
}
@Override
public String getDesc() {
String bonus = getAllMods(null).getValueAsString();
if (getAllMods(null).getValue() > -1) {
bonus = "+" + bonus;
}
bonus = "(" + bonus + ")";
String toReturn = "<html><font size='2'";
String action = "Replace ";
if(isSalvaging()) {
action = "Salvage ";
}
String scheduled = "";
if (getTeamId() != null) {
scheduled = " (scheduled) ";
}
toReturn += ">";
toReturn += "<b>" + action + getPartName() + " Equipment</b><br/>";
toReturn += getDetails() + "<br/>";
if(getSkillMin() > SkillType.EXP_ELITE) {
toReturn += "<font color='red'>Impossible</font>";
} else {
toReturn += "" + getTimeLeft() + " minutes" + scheduled;
if(!campaign.getCampaignOptions().isDestroyByMargin()) {
toReturn += ", " + SkillType.getExperienceLevelName(getSkillMin());
}
toReturn += " " + bonus;
}
toReturn += "</font></html>";
return toReturn;
}
@Override
public String getDetails() {
int allParts = 0;
int replacements = 0;
int inTransit = 0;
int onOrder = 0;
for (int id : childPartIds) {
Part part = campaign.getPart(id);
if (part != null) {
if (!isSalvaging() && !(part instanceof AmmoBin) && part.needsFixing()) {
allParts++;
MissingPart missing;
if (part instanceof MissingPart) {
missing = (MissingPart)part;
} else {
missing = part.getMissingPart();
}
if (missing.isReplacementAvailable()) {
replacements++;
} else {
//FIXME: This won't work if there are multiple items of the same type that need replacing and the number on order or in transit is less than the required number
String[] inventories = campaign.getPartInventory(missing.getNewPart());
if (inventories[1].indexOf(" ") >= 0
&& Integer.parseInt(inventories[1].substring(0, inventories[1].indexOf(" "))) > 0) {
inTransit++;
}
if (inventories[2].indexOf(" ") >= 0
&& Integer.parseInt(inventories[2].substring(0, inventories[2].indexOf(" "))) > 0) {
onOrder++;
}
}
} else if (isSalvaging() && !(part instanceof MissingPart)) {
allParts++;
}
//TODO: add string for reconfiguring
}
}
if (isSalvaging()) {
return allParts + " parts remaining";
} else {
return replacements + "/" + allParts + " available<br />"
+ inTransit + " in transit, " + onOrder + " on order";
}
}
@Override
public Unit getUnit() {
return unit;
}
@Override
public boolean isSalvaging() {
if (unit != null) {
return unit.isSalvage() || unit.isLocationDestroyed(location);
}
return false;
}
public boolean shouldRepairInPlace() {
return repairInPlace;
}
public void setRepairInPlace(boolean repairInPlace) {
this.repairInPlace = repairInPlace;
}
public boolean hasSalvageableParts() {
for (int id : childPartIds) {
final Part p = campaign.getPart(id);
if (p != null && p.isSalvaging()) {
return true;
}
}
return false;
}
@Override
public void reservePart() {
childPartIds.stream().map(id -> campaign.getPart(id))
.filter(Objects::nonNull).forEach(Part::reservePart);
}
@Override
public void cancelReservation() {
childPartIds.stream().map(id -> campaign.getPart(id))
.filter(Objects::nonNull).forEach(Part::cancelReservation);
}
@Override
public int getMassRepairOptionType() {
return Part.REPAIR_PART_TYPE.GENERAL_LOCATION;
}
@Override
public int getRepairPartType() {
return Part.REPAIR_PART_TYPE.POD_SPACE;
}
}