Skip to content

Commit

Permalink
v1.1.3
Browse files Browse the repository at this point in the history
- More fixes to animal training food handling
- Added support for non-Latin characters in preset names
  • Loading branch information
Jaxe-Dev committed Sep 15, 2018
1 parent 562921b commit 2b95ab3
Show file tree
Hide file tree
Showing 24 changed files with 175 additions and 64 deletions.
2 changes: 1 addition & 1 deletion About/About.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
<name>Pawn Rules</name>
<author>Jaxe</author>
<targetVersion>0.19.0</targetVersion>
<description>Mod Version: 1.1.2\n\n\nPawn Rules is a mod that allows custom rules to be assigned individually to your colonists, animals, guests and prisoners.\n\nCurrently the following rules can be applied:\n\n- Disallow certain foods\n- Disallow bonding with certain animals\n- Disallow new romances\n- Disallow constructing items that have a quality level\n\nAny of these rules can be disabled and hidden from the rules window. Rules presets and defaults can be imported and exported between games.</description>
<description>Mod Version: 1.1.3\n\n\nPawn Rules is a mod that allows custom rules to be assigned individually to your colonists, animals, guests and prisoners.\n\nCurrently the following rules can be applied:\n\n- Disallow certain foods\n- Disallow bonding with certain animals\n- Disallow new romances\n- Disallow constructing items that have a quality level\n\nAny of these rules can be disabled and hidden from the rules window. Rules presets and defaults can be imported and exported between games.</description>
<url>https://github.com/Jaxe-Dev/PawnRules</url>
</ModMetaData>
2 changes: 1 addition & 1 deletion About/ModSync.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<ModSyncNinjaData>
<ID>59f538ed-f86d-4506-a4a5-7e9faaa37508</ID>
<ModName>Pawn Rules</ModName>
<Version>v1.1.2</Version>
<Version>v1.1.3</Version>
<SaveBreaking>False</SaveBreaking>
<Host name="Github">
<Owner>Jaxe-Dev</Owner>
Expand Down
8 changes: 1 addition & 7 deletions Defs/UpdateFeaturesDef/UpdateFeatures.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,7 @@
<HugsLib.UpdateFeatureDef ParentName="UpdateFeatureBase">
<defName>PawnRules_1_1_1</defName>
<assemblyVersion>1.1.1</assemblyVersion>
<content>- Re-fixed "not allowed artisan builds" tooltip\n\n- Fixed "Could not find player faction" error on new game\n\n- Fixed ability to train animals if an animal has a food policy set\n\n- New Global option: Allow food if malnourished (if a pawn is is suffering from malnutrition they will ignore all food rules) [Default: False]\n\n- New Global option: Allow food if training (if an animal is being trained they ignore all food rules) [Default: False]\n\n- Plans can be imported/exported between games. A plan consists of all rule presets and defaults bundled into one file.</content>
</HugsLib.UpdateFeatureDef>

<HugsLib.UpdateFeatureDef ParentName="UpdateFeatureBase">
<defName>PawnRules_1_1_2</defName>
<assemblyVersion>1.1.2</assemblyVersion>
<content>- Fixed null reference error when a pawn without rules attempts to build</content>
<content>- New Global option: Allow food if malnourished (if a pawn is is suffering from malnutrition they will ignore all food rules) [Default: False]\n\n- New Global option: Allow food if training (if an animal is being trained they ignore all food rules) [Default: False]\n\n- Plans can be imported/exported between games. A plan consists of all rule presets and defaults bundled into one file.</content>
</HugsLib.UpdateFeatureDef>

</Defs>
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Pawn Rules
![](https://img.shields.io/badge/Mod_Version-1.1.2-blue.svg)
![](https://img.shields.io/badge/Mod_Version-1.1.3-blue.svg)
![](https://img.shields.io/badge/Built_for_RimWorld-B19-blue.svg)
![](https://img.shields.io/badge/Powered_by_Harmony-1.2.0.1-blue.svg)

Expand Down Expand Up @@ -53,11 +53,13 @@ Prefix : RimWorld.FoodUtility.TryFindBestFoodSourceFor
Postfix : RimWorld.GenConstruct.CanConstruct
Prefix : RimWorld.InteractionWorker_RomanceAttempt.RandomSelectionWeight
Prefix : RimWorld.InteractionWorker_RomanceAttempt.SuccessChance
Prefix : RimWorld.JobDriver_InteractAnimal.StartFeedAnimal
Postfix : RimWorld.JobGiver_PackFood.IsGoodPackableFoodFor
Prefix : RimWorld.JoyGiver_Ingest.CanIngestForJoy
Prefix : RimWorld.Pawn_GuestTracker.SetGuestStatus
Postfix : RimWorld.PawnUtility.TrySpawnHatchedOrBornPawn
Prefix : RimWorld.RelationsUtility.TryDevelopBondRelation
Prefix : RimWorld.WorkGiver_InteractAnimal.HasFoodToInteractAnimal
Prefix : RimWorld.WorkGiver_InteractAnimal.TakeFoodForAnimalInteractJob
Postfix : Verse.Game.FinalizeInit
Prefix : Verse.Game.InitNewGame
Expand Down
3 changes: 1 addition & 2 deletions Source/Data/Persistent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal static class Persistent
private const string ExportsExtension = ".xml";
private const string ExportPrefix = "Plan_";

private static readonly Regex ValidNameRegex = new Regex("^(?:[a-zA-Z0-9_\\-]|[a-zA-Z0-9_\\-]+[a-zA-Z0-9_\\- ]*[a-zA-Z0-9_\\-]+)$");
private static readonly Regex ValidNameRegex = new Regex("^(?:[\\p{L}\\p{N}_\\-]|[\\p{L}\\p{N}_\\-]+[\\p{L}\\p{N}_\\- ]*[\\p{L}\\p{N}_\\-]+)$");

private static readonly DirectoryInfo ExportsDirectory = Mod.ConfigDirectory.CreateSubdirectory(ExportsDirectoryName);

Expand All @@ -28,7 +28,6 @@ public static void DeletePlan(string name)
{
var file = GetPlanFile(name);

Mod.Warning($"Delete => {file.FullName}");
if (!file.Exists) { return; }

file.Delete();
Expand Down
5 changes: 3 additions & 2 deletions Source/Data/Presetable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Text.RegularExpressions;
using System.Xml.Linq;
using PawnRules.Interface;
using PawnRules.Patch;
using Verse;

namespace PawnRules.Data
Expand All @@ -12,7 +13,7 @@ internal abstract class Presetable : IExposable, ILoadReferenceable

public static readonly string VoidName = Lang.Get("Preset.None");

private static readonly Regex ValidNameRegex = new Regex("^(?:[a-zA-Z0-9]|[a-zA-Z0-9]+[a-zA-Z0-9 ]*[a-zA-Z0-9]+)$");
private static readonly Regex ValidNameRegex = new Regex("^(?:[\\p{L}\\p{N}]|[\\p{L}\\p{N}]+[\\p{L}\\p{N} ]*[\\p{L}\\p{N}])$");

private static int _count;
protected readonly int Id;
Expand Down Expand Up @@ -54,7 +55,7 @@ public static void SetName<T>(T preset, Action<T> onRename) where T : Presetable
var localPreset = preset;
void OnCommit(string name) => onRename(Registry.RenamePreset(localPreset, name));

Dialog_SetName.Open(Lang.Get("Dialog_SetName.PresetTitle", preset.Name), Lang.Get("Dialog_SetName.PresetLabel"), OnCommit, name => NameIsValid<T>(preset.Type, name), preset.Name);
Dialog_SetName.Open(Lang.Get("Dialog_SetName.PresetTitle", preset.Name.Bold()), Lang.Get("Dialog_SetName.PresetLabel"), OnCommit, name => NameIsValid<T>(preset.Type, name), preset.Name);
}

public static bool NameIsValid<T>(IPresetableType type, string name) => (name.Length <= MaxIdLength) && !string.Equals(name, Lang.Get("Preset.None"), StringComparison.OrdinalIgnoreCase) && !string.Equals(name, Lang.Get("Preset.Personalized"), StringComparison.OrdinalIgnoreCase) && ValidNameRegex.IsMatch(name) && !Registry.PresetNameExists<T>(type, name);
Expand Down
18 changes: 10 additions & 8 deletions Source/Data/Registry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ internal class Registry : WorldObject

private static bool _isDeactivating;

public static bool AllowEmergencyFood { get => _instance._allowEmergencyFood; set => _instance._allowEmergencyFood = value; }
public static bool AllowTrainingFood { get => _instance._allowTrainingFood; set => _instance._allowTrainingFood = value; }

public static bool ShowFoodPolicy { get => _instance._showFoodPolicy; set => _instance._showFoodPolicy = value; }
public static bool ShowBondingPolicy { get => _instance._showBondingPolicy; set => _instance._showBondingPolicy = value; }
public static bool ShowAllowCourting { get => _instance._showAllowCourting; set => _instance._showAllowCourting = value; }
public static bool ShowAllowArtisan { get => _instance._showAllowArtisan; set => _instance._showAllowArtisan = value; }

public static bool AllowEmergencyFood { get => _instance._allowEmergencyFood; set => _instance._allowEmergencyFood = value; }
public static bool AllowTrainingFood { get => _instance._allowTrainingFood; set => _instance._allowTrainingFood = value; }
public static Pawn ExemptedTrainer { get; set; }

private string _loadedVersion;

private readonly Dictionary<Type, Dictionary<IPresetableType, Presetable>> _voidPresets = new Dictionary<Type, Dictionary<IPresetableType, Presetable>>();
Expand All @@ -42,14 +44,14 @@ internal class Registry : WorldObject
private List<Binding> _savedBindings = new List<Binding>();
private List<Binding> _savedDefaults = new List<Binding>();

private bool _allowEmergencyFood;
private bool _allowTrainingFood;

private bool _showFoodPolicy = true;
private bool _showBondingPolicy = true;
private bool _showAllowCourting = true;
private bool _showAllowArtisan = true;

private bool _allowEmergencyFood;
private bool _allowTrainingFood;

public static void Initialize()
{
var worldObjects = Current.Game.World.worldObjects;
Expand Down Expand Up @@ -316,14 +318,14 @@ public override void ExposeData()
_savedBindings.AddRange(_rules.Where(rules => rules.Key.CanHaveRules()).Select(rules => new Binding(rules.Key, rules.Value.IsIgnored() ? null : rules.Value)).ToArray());
}

Scribe_Values.Look(ref _allowEmergencyFood, "allowEmergencyFood");
Scribe_Values.Look(ref _allowTrainingFood, "allowTrainingFood");

Scribe_Values.Look(ref _showFoodPolicy, "showFoodPolicy", true);
Scribe_Values.Look(ref _showBondingPolicy, "showBondingPolicy", true);
Scribe_Values.Look(ref _showAllowCourting, "showAllowCourting", true);
Scribe_Values.Look(ref _showAllowArtisan, "showAllowArtisan", true);

Scribe_Values.Look(ref _allowEmergencyFood, "allowEmergencyFood");
Scribe_Values.Look(ref _allowTrainingFood, "allowTrainingFood");

Scribe_Collections.Look(ref _savedPresets, "presets", LookMode.Deep);
Scribe_Collections.Look(ref _savedBindings, "bindings", LookMode.Deep);
Scribe_Collections.Look(ref _savedDefaults, "defaults", LookMode.Deep);
Expand Down
2 changes: 1 addition & 1 deletion Source/Interface/Dialog_Global.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace PawnRules.Interface
{
internal class Dialog_Global : WindowPlus
{
private Dialog_Global() : base(Lang.Get("Dialog_Global.Title"), new Vector2(300f, 400f))
private Dialog_Global() : base(Lang.Get("Dialog_Global.Title").Bold(), new Vector2(300f, 400f))
{ }

public static void Open() => Find.WindowStack.Add(new Dialog_Global());
Expand Down
2 changes: 1 addition & 1 deletion Source/Interface/Dialog_Plans.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal class Dialog_Plans : WindowPlus
private IEnumerable<string> _plans;
private string _selected;

private Dialog_Plans() : base(Lang.Get("Dialog_Plans.Title"), new Vector2(500f, 600f))
private Dialog_Plans() : base(Lang.Get("Dialog_Plans.Title").Bold(), new Vector2(500f, 600f))
{
doCloseButton = false;

Expand Down
1 change: 0 additions & 1 deletion Source/Interface/WindowPlus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ private Rect DoTitle(Rect rect)
var header = new Listing_StandardPlus();

header.Begin(rect);
Text.Font = GameFont.Medium;
header.LabelMedium(Title);
header.GapLine();
header.End();
Expand Down
23 changes: 12 additions & 11 deletions Source/Mod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using PawnRules.Data;
using PawnRules.Interface;
using PawnRules.Patch;
using RimWorld;
using UnityEngine;
using Verse;

Expand All @@ -15,39 +16,39 @@ internal class Mod : Verse.Mod
{
public const string Id = "PawnRules";
public const string Name = "Pawn Rules";
public const string Author = "Jaxe";
public const string Version = "1.1.2";
public const string Version = "1.1.3";

public static readonly DirectoryInfo ConfigDirectory = new DirectoryInfo(GenFilePaths.ConfigFolderPath).CreateSubdirectory(Id);
public static readonly DirectoryInfo ConfigDirectory = new DirectoryInfo(Path.Combine(GenFilePaths.ConfigFolderPath, Id));

public static Mod Instance { get; private set; }
public static bool FirstTimeUser { get; private set; }

public Mod(ModContentPack contentPack) : base(contentPack)
{
Instance = this;
Log("Loaded");

TryRegisterHugsLibUpdateFeature();
FirstTimeUser = !ConfigDirectory.Exists;
ConfigDirectory.Create();

if (!FirstTimeUser) { TryRegisterHugsLibUpdateFeature(); }
}

private static void TryRegisterHugsLibUpdateFeature()
{
var hugsLib = (from assembly in AppDomain.CurrentDomain.GetAssemblies() from type in assembly.GetTypes() where type.Name == "HugsLibController" select type).FirstOrDefault();
if (hugsLib == null) { return; }

var controllerField = AccessTools.Field(hugsLib, "instance");
var controller = controllerField.GetValue(null);

var updateFeaturesField = AccessTools.Property(controller.GetType(), "UpdateFeatures");
var updateFeatures = updateFeaturesField.GetValue(controller, null);
var updateFeatures = Traverse.Create(hugsLib)?.Field("instance")?.Property("UpdateFeatures")?.GetValue();
if (updateFeatures == null) { return; }

var inspectActiveModMethod = AccessTools.Method(updateFeatures.GetType(), "InspectActiveMod");
inspectActiveModMethod.Invoke(updateFeatures, new object[] { Id, Assembly.GetExecutingAssembly().GetName().Version });
AccessTools.Method(updateFeatures.GetType(), "InspectActiveMod")?.Invoke(updateFeatures, new object[] { Id, Assembly.GetExecutingAssembly().GetName().Version });
}

public static void Log(string message) => Verse.Log.Message(PrefixMessage(message));
public static void Warning(string message) => Verse.Log.Warning(PrefixMessage(message));
public static void Error(string message) => Verse.Log.Error(PrefixMessage(message));
public static void Message(string message) => Messages.Message(message, MessageTypeDefOf.TaskCompletion, false);

public static string PrefixMessage(string message) => $"[{Name} v{Version}] {message}";
public override string SettingsCategory() => Name;
Expand Down
2 changes: 0 additions & 2 deletions Source/Patch/Access.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,13 @@ internal static class Access
private static readonly MethodInfo Method_RimWorld_FoodUtility_IsFoodSourceOnMapSociallyProper = AccessTools.Method(typeof(FoodUtility), "IsFoodSourceOnMapSociallyProper");
private static readonly MethodInfo Method_RimWorld_FoodUtility_SpawnedFoodSearchInnerScan = AccessTools.Method(typeof(FoodUtility), "SpawnedFoodSearchInnerScan");
private static readonly FieldInfo Field_RimWorld_FoodUtility_Filtered = AccessTools.Field(typeof(FoodUtility), "filtered");
private static readonly FieldInfo Field_RimWorld_Pawn_GuestTracker_Pawn = AccessTools.Field(typeof(Pawn_GuestTracker), "pawn");
private static readonly FieldInfo Field_Verse_LoadedModManager_RunningMods = AccessTools.Field(typeof(LoadedModManager), "runningMods");

public static Pawn Method_RimWorld_FoodUtility_BestPawnToHuntForPredator_Call(Pawn predator, bool forceScanWholeMap) => (Pawn) Method_RimWorld_FoodUtility_BestPawnToHuntForPredator.Invoke(null, new object[] { predator, forceScanWholeMap });
public static int Method_RimWorld_FoodUtility_GetMaxRegionsToScan_Call(Pawn getter, bool forceScanWholeMap) => (int) Method_RimWorld_FoodUtility_GetMaxRegionsToScan.Invoke(null, new object[] { getter, forceScanWholeMap });
public static bool Method_RimWorld_FoodUtility_IsFoodSourceOnMapSociallyProper_Call(Thing thing, Pawn getter, Pawn eater, bool allowSociallyImproper) => (bool) Method_RimWorld_FoodUtility_IsFoodSourceOnMapSociallyProper.Invoke(null, new object[] { thing, getter, eater, allowSociallyImproper });
public static Thing Method_RimWorld_FoodUtility_SpawnedFoodSearchInnerScan_Call(Pawn eater, IntVec3 root, List<Thing> searchSet, PathEndMode peMode, TraverseParms traverseParams, float maxDistance = 9999f, Predicate<Thing> validator = null) => (Thing) Method_RimWorld_FoodUtility_SpawnedFoodSearchInnerScan.Invoke(null, new object[] { eater, root, searchSet, peMode, traverseParams, maxDistance, validator });
public static HashSet<Thing> Field_RimWorld_FoodUtility_Filtered_Get() => (HashSet<Thing>) Field_RimWorld_FoodUtility_Filtered.GetValue(null);
public static Pawn Field_RimWorld_Pawn_GuestTracker_Pawn_Get(Pawn_GuestTracker instance) => (Pawn) Field_RimWorld_Pawn_GuestTracker_Pawn.GetValue(instance);
public static List<ModContentPack> Field_Verse_LoadedModManager_RunningMods_Get() => (List<ModContentPack>) Field_Verse_LoadedModManager_RunningMods.GetValue(null);
}
}
4 changes: 2 additions & 2 deletions Source/Patch/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System.Collections;
using PawnRules.Data;
using RimWorld;
using UnityEngine;
Expand All @@ -11,7 +11,7 @@ internal static class Extensions
public static string Italic(this string self) => "<i>" + self + "</i>";
public static string Bold(this string self) => "<b>" + self + "</b>";

public static int LastIndex(this Array self) => self.Length - 1;
public static int LastIndex(this IList self) => self.Count - 1;
public static int ToInt(this string self, int defaultValue = 0) => int.TryParse(self, out var result) ? result : defaultValue;
public static float ToFloat(this string self, float defaultValue = 0f) => float.TryParse(self, out var result) ? result : defaultValue;

Expand Down
12 changes: 9 additions & 3 deletions Source/Patch/RimWorld_FoodUtility_BestFoodInInventory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ internal static class RimWorld_FoodUtility_BestFoodInInventory
{
private static bool Prefix(ref Thing __result, Pawn holder, Pawn eater = null, FoodPreferability minFoodPref = FoodPreferability.NeverForNutrition, FoodPreferability maxFoodPref = FoodPreferability.MealLavish, float minStackNutrition = 0.0f, bool allowDrug = false)
{
if (Registry.ExemptedTrainer != null)
{
Registry.ExemptedTrainer = null;
return true;
}
if (!Registry.IsActive) { return true; }

if (holder.inventory == null)
{
__result = null;
Expand All @@ -20,14 +26,14 @@ private static bool Prefix(ref Thing __result, Pawn holder, Pawn eater = null, F

if (eater == null) { eater = holder; }

var rules = Registry.GetRules(eater);
if (eater.InMentalState || (rules == null) || rules.GetRestriction(RestrictionType.Food).IsVoid) { return true; }
var restriction = Registry.GetRules(eater)?.GetRestriction(RestrictionType.Food);
if (eater.InMentalState || (restriction == null) || restriction.IsVoid) { return true; }

var innerContainer = holder.inventory.innerContainer;
foreach (var thing in innerContainer.ToArray())
{
// Pawn Rules - Food check below
if (!thing.def.IsNutritionGivingIngestible || !thing.IngestibleNow || !eater.RaceProps.CanEverEat(thing) || (thing.def.ingestible.preferability < minFoodPref) || (thing.def.ingestible.preferability > maxFoodPref) || (!allowDrug && thing.def.IsDrug) || !(thing.GetStatValue(StatDefOf.Nutrition) * thing.stackCount >= (double) minStackNutrition) || !rules.GetRestriction(RestrictionType.Food).AllowsFood(thing.def, eater)) { continue; }
if (!thing.def.IsNutritionGivingIngestible || !thing.IngestibleNow || !eater.RaceProps.CanEverEat(thing) || (thing.def.ingestible.preferability < minFoodPref) || (thing.def.ingestible.preferability > maxFoodPref) || (!allowDrug && thing.def.IsDrug) || !(thing.GetStatValue(StatDefOf.Nutrition) * thing.stackCount >= (double) minStackNutrition) || !restriction.AllowsFood(thing.def, eater)) { continue; }

__result = thing;
return false;
Expand Down
Loading

0 comments on commit 2b95ab3

Please sign in to comment.