diff --git a/code/src/java/pcgen/core/Globals.java b/code/src/java/pcgen/core/Globals.java index 1a044561948..e810c63cf2b 100644 --- a/code/src/java/pcgen/core/Globals.java +++ b/code/src/java/pcgen/core/Globals.java @@ -127,7 +127,11 @@ public static Campaign getCampaignKeyed(final String aKey) final Campaign campaign = getCampaignKeyedSilently(aKey); if (campaign == null) { - Logging.errorPrint("Could not find campaign: " + aKey); + Logging.errorPrint("Could not find campaign: " + aKey + + " - no loaded .pcc file declares CAMPAIGN:" + aKey + "." + + " If this is third-party (non-OGL) data, install the .pcc into the" + + " vendor data dir (" + PCGenSettings.getVendorDataDir() + + ") or homebrew data dir (" + PCGenSettings.getHomebrewDataDir() + ")."); } return campaign; diff --git a/code/src/java/pcgen/core/PlayerCharacter.java b/code/src/java/pcgen/core/PlayerCharacter.java index 54be7e75f8b..802b76a5c3b 100644 --- a/code/src/java/pcgen/core/PlayerCharacter.java +++ b/code/src/java/pcgen/core/PlayerCharacter.java @@ -228,6 +228,7 @@ import pcgen.core.analysis.ChooseActivation; import pcgen.core.analysis.DomainApplication; import pcgen.core.analysis.RaceUtilities; +import pcgen.core.analysis.SizeUtilities; import pcgen.core.analysis.SkillModifier; import pcgen.core.analysis.SkillRankControl; import pcgen.core.analysis.SpellCountCalc; @@ -542,6 +543,20 @@ private PlayerCharacter(PlayerCharacter from) public PlayerCharacter(Collection loadedCampaigns) { LoadContext context = Globals.getContext(); + // Fail fast on a half-loaded dataset: without a SizeAdjustment flagged + // ISDEFAULTSIZE:YES, damage scaling, equipment sizing and the size facet + // would all NPE deep in facet wiring. + if (SizeUtilities.getDefaultSizeAdjustment() == null) + { + String gameModeName = SettingsHandler.getGameAsProperty().get().getName(); + Logging.errorPrint("Game mode '" + gameModeName + + "' has no default size: no SizeAdjustment was loaded with ISDEFAULTSIZE:YES." + + " Sizes are typically defined in a *__sizes.lst file inside the gamemode's" + + " Core Rules data source (e.g. data/pathfinder/.../core_essentials/ce__sizes.lst)." + + " Exactly one size in that file must be flagged ISDEFAULTSIZE:YES."); + throw new IllegalStateException("Game mode '" + gameModeName + + "' has no default size. Check earlier log errors for the missing data source."); + } id = CharID.getID(context.getDataSetID()); AbstractReferenceContext refContext = context.getReferenceContext(); controller = refContext.constructNowIfNecessary(CodeControl.class, "Controller"); diff --git a/code/src/java/pcgen/persistence/lst/LstObjectFileLoader.java b/code/src/java/pcgen/persistence/lst/LstObjectFileLoader.java index d6e6b29f911..7d1ee79147d 100644 --- a/code/src/java/pcgen/persistence/lst/LstObjectFileLoader.java +++ b/code/src/java/pcgen/persistence/lst/LstObjectFileLoader.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Objects; import java.util.Observable; +import java.util.Optional; import java.util.Set; import pcgen.cdom.base.CDOMObject; @@ -281,7 +282,15 @@ protected void loadLstFile(LoadContext context, CampaignSourceEntry sourceEntry) String aString; try { - aString = LstFileLoader.readFromURI(uri).get(); + // readFromURI returns Optional.empty() when it logs a non-fatal IO/decode error + // (e.g. a non-UTF-8 .lst). Skip the file rather than aborting the whole load. + Optional read = LstFileLoader.readFromURI(uri); + if (read.isEmpty()) + { + setChanged(); + return; + } + aString = read.get(); } catch (PersistenceLayerException ple) { diff --git a/code/src/java/pcgen/system/CharacterManager.java b/code/src/java/pcgen/system/CharacterManager.java index 0232c60a5ab..7556d3a56dd 100644 --- a/code/src/java/pcgen/system/CharacterManager.java +++ b/code/src/java/pcgen/system/CharacterManager.java @@ -84,6 +84,14 @@ private CharacterManager() */ public static CharacterFacade createNewCharacter(UIDelegate delegate, DataSetFacade dataset) { + if (dataset == null) + { + Logging.errorPrint("Unable to create character: data sources failed to load (see earlier errors)"); + delegate.showErrorMessage(LanguageBundle.getString("in_cmCreateErrorTitle"), + LanguageBundle.getFormattedString("in_cmCreateErrorMessage", + "data sources failed to load - see earlier errors in the log")); + return null; + } @SuppressWarnings("rawtypes") List campaigns = ListFacades.wrap(dataset.getCampaigns()); try @@ -169,6 +177,14 @@ public static PlayerCharacter openPlayerCharacter(File file, UIDelegate delegate private static PlayerCharacter openPcInternal(File file, UIDelegate delegate, DataSetFacade dataset, boolean blockLoadedMessage) { + if (dataset == null) + { + Logging.errorPrint("Unable to load character " + file + ": data sources failed to load (see earlier errors)"); + delegate.showErrorMessage(LanguageBundle.getString("in_cmLoadErrorTitle"), + LanguageBundle.getFormattedString("in_cmLoadErrorMessage", + file, "data sources failed to load - see earlier errors in the log")); + return null; + } @SuppressWarnings("rawtypes") List campaigns = ListFacades.wrap(dataset.getCampaigns()); try