/* * Copyright (C) 2016 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 2 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.personnel; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import javax.xml.bind.annotation.adapters.XmlAdapter; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import org.joda.time.DateTime; import mekhq.Utilities; import mekhq.campaign.Campaign; import mekhq.campaign.GameEffect; /** * Flyweight design pattern implementation. InjuryType instances should be singletons and never * hold any data related to specific injuries. Use the {@link Injury} data for that, in particular * it's <tt>extraData</tt> data structure for generic type-safe data storage. */ @XmlJavaTypeAdapter(InjuryType.XMLAdapter.class) public class InjuryType { /** Modifier tag to use for injuries */ public static final String MODTAG_INJURY = "injury"; // Registry methods private static final Map<String, InjuryType> REGISTRY = new HashMap<>(); private static final Map<InjuryType, String> REV_REGISTRY = new HashMap<>(); private static final Map<Integer, InjuryType> ID_REGISTRY = new HashMap<>(); private static final Map<InjuryType, Integer> REV_ID_REGISTRY = new HashMap<>(); public static InjuryType byKey(String key) { InjuryType result = REGISTRY.get(key); if(null == result) { try { result = ID_REGISTRY.get(Integer.valueOf(key)); } catch(NumberFormatException nfex) { // Do nothing } } return result; } public static InjuryType byId(int id) { return ID_REGISTRY.get(Integer.valueOf(id)); } public static void register(int id, String key, InjuryType injType) { Objects.requireNonNull(injType); if(id >= 0) { if(ID_REGISTRY.containsKey(Integer.valueOf(id))) { throw new IllegalArgumentException("Injury type ID " + id + " is already registered."); } } if(REGISTRY.containsKey(Objects.requireNonNull(key))) { throw new IllegalArgumentException("Injury type key \"" + key + "\" is already registered."); } if(key.isEmpty()) { throw new IllegalArgumentException("Injury type key can't be an empty string."); } if(REV_REGISTRY.containsKey(injType)) { throw new IllegalArgumentException("Injury type " + injType + " is already registered"); } // All checks done if(id >= 0) { ID_REGISTRY.put(Integer.valueOf(id), injType); REV_ID_REGISTRY.put(injType, Integer.valueOf(id)); } REGISTRY.put(key, injType); REV_REGISTRY.put(injType, key); } public static void register(String key, InjuryType injType) { register(-1, key, injType); } public static List<String> getAllKeys() { List<String> result = new ArrayList<>(REGISTRY.keySet()); Collections.sort(result); return result; } public static List<InjuryType> getAllTypes() { List<InjuryType> result = new ArrayList<>(REGISTRY.values()); Collections.sort(result, (it1, it2) -> it1.getKey().compareTo(it2.getKey())); return result; } /** Default injury type: reduction in hit points */ public static InjuryType BAD_HEALTH = new InjuryType(); static { BAD_HEALTH.recoveryTime = 7; BAD_HEALTH.fluffText = "Damaged health"; BAD_HEALTH.maxSeverity = 5; BAD_HEALTH.allowedLocations = EnumSet.of(BodyLocation.GENERIC); register("bad_health", BAD_HEALTH); } /** Base recovery time in days */ protected int recoveryTime = 0; protected boolean permanent = false; protected int maxSeverity = 1; protected String fluffText = ""; protected String simpleName = "injured"; protected InjuryLevel level = InjuryLevel.MINOR; protected Set<BodyLocation> allowedLocations = null; protected InjuryType() { } public final int getId() { return Utilities.nonNull(InjuryType.REV_ID_REGISTRY.get(this), Integer.valueOf(-1)).intValue(); } public final String getKey() { return InjuryType.REV_REGISTRY.get(this); } public boolean isValidInLocation(BodyLocation loc) { if(null == allowedLocations) { allowedLocations = EnumSet.allOf(BodyLocation.class); } return allowedLocations.contains(loc); } /** Does having this injury mean the location is missing? (Amputation, genetic defect, ...) */ public boolean impliesMissingLocation(BodyLocation loc) { return false; } /** Does having this injury in this location imply the character is dead? */ public boolean impliesDead(BodyLocation loc) { return false; } public int getBaseRecoveryTime() { return recoveryTime; } public int getRecoveryTime(int severity) { return recoveryTime; } public int getRecoveryTime(Injury i) { return getRecoveryTime(i.getHits()); } public boolean isPermanent() { return permanent; } public int getMaxSeverity() { return maxSeverity; } public boolean isHidden(Injury i) { return false; } public String getSimpleName() { return getSimpleName(1); } public String getSimpleName(int severity) { return simpleName; } public String getName(BodyLocation loc, int severity) { return Utilities.capitalize(fluffText); } public String getFluffText(BodyLocation loc, int severity, int gender) { return fluffText; } public InjuryLevel getLevel(Injury i) { return level; } public Injury newInjury(Campaign c, Person p, BodyLocation loc, int severity) { if(!isValidInLocation(loc)) { return null; } final int recoveryTime = getRecoveryTime(severity); final String fluff = getFluffText(loc, severity, p.getGender()); Injury result = new Injury(recoveryTime, fluff, loc, this, severity, false); result.setVersion(Injury.VERSION); result.setStart(new DateTime(c.getCalendar())); return result; } public Collection<Modifier> getModifiers(Injury inj) { return Arrays.asList(); } /** * Return a function which will generate a list of effects combat and similar stressful * situation while injured would have on the person in question given the random integer source. * Descriptions should be something like "50% chance of losing a leg" and similar. * <p> * Note that specific systems aren't required to use this generator. They are free to * implement their own. */ public List<GameEffect> genStressEffect(Campaign c, Person p, Injury i, int hits) { return Arrays.asList(); } // Standard actions generators protected GameEffect newResetRecoveryTimeAction(Injury i) { return new GameEffect( i.getFluff() + ": recovery timer reset", rnd -> i.setTime(i.getOriginalTime())); } // Helper classes and interfaces /** Why you no have this in java.util.function?!? */ @FunctionalInterface public static interface ToBooleanFunction<T> { boolean applyAsBoolean(T value); } public static final class XMLAdapter extends XmlAdapter<String, InjuryType> { @Override public InjuryType unmarshal(String v) throws Exception { return (null == v) ? null : InjuryType.byKey(v); } @Override public String marshal(InjuryType v) throws Exception { return (null == v) ? null : v.getKey(); } } }