diff --git a/1.6/Assemblies/RunAndGun.dll b/1.6/Assemblies/RunAndGun.dll
index 66f993b..b685b28 100644
Binary files a/1.6/Assemblies/RunAndGun.dll and b/1.6/Assemblies/RunAndGun.dll differ
diff --git a/Languages/English/Keyed/RunAndGun_Keys.xml b/Languages/English/Keyed/RunAndGun_Keys.xml
index b1f1d63..9487ff1 100644
--- a/Languages/English/Keyed/RunAndGun_Keys.xml
+++ b/Languages/English/Keyed/RunAndGun_Keys.xml
@@ -13,6 +13,8 @@
The movement penalty while shooting for heavy weapons
Movement penalty for light weapons (%)
The movement penalty while shooting for light weapons
+ Enable Run And Gun for Colonists
+ Enables Run And Gun for all colonist pawns by default.
Enable Run And Gun for AI
Enables Run And Gun for all AI pawns. Beware, this makes them incredibly deadly.
Flee and gun chance(%)
diff --git a/Source/RunAndGun/CanRunAndGunDebug.cs b/Source/RunAndGun/CanRunAndGunDebug.cs
new file mode 100644
index 0000000..06cc673
--- /dev/null
+++ b/Source/RunAndGun/CanRunAndGunDebug.cs
@@ -0,0 +1,20 @@
+using HarmonyLib;
+using Verse;
+
+#if DEBUG
+namespace RunAndGun
+{
+ [HarmonyPatch(typeof(Pawn), nameof(Pawn.GetInspectString))]
+ public static class CanRunAndGunDebug
+ {
+ public static void Postfix(Pawn __instance, ref string __result)
+ {
+ var comp = __instance.GetComp();
+ if (comp == null)
+ return;
+ __result += $"\nRunAndGun: {comp._isEnabled}, Disabled: {comp._disabled}";
+ }
+
+ }
+}
+#endif
diff --git a/Source/RunAndGun/CompRunAndGun.cs b/Source/RunAndGun/CompRunAndGun.cs
index 9afa681..c39bb02 100644
--- a/Source/RunAndGun/CompRunAndGun.cs
+++ b/Source/RunAndGun/CompRunAndGun.cs
@@ -22,11 +22,11 @@ private Pawn pawn
}
}
- internal bool _isEnabled = false;
- internal bool _disabled = false;
+ internal bool? _isEnabled;
+ internal bool? _disabled;
public bool isEnabled
{
- get => _isEnabled && IsValid();
+ get => (_isEnabled??= pawn.IsColonist ? RunAndGun.settings.enabledByDefault : RunAndGun.settings.enableForAI) && IsValid();
set => _isEnabled = value;
}
@@ -38,7 +38,10 @@ public void RefreshDisabledState()
private bool IsValid()
{
- if (_disabled)
+ if(_disabled == null)
+ RefreshDisabledState();
+
+ if (_disabled ?? true)
return false;
if (pawn == null)
@@ -70,19 +73,6 @@ public override string GetDescriptionPart()
return isEnabled.ToString();
}
- public override void Initialize(CompProperties props)
- {
- base.Initialize(props);
- Pawn pawn = (Pawn)(parent as Pawn);
- bool enableRGForAI = RunAndGun.settings.enableForAI;
- if (!pawn.IsColonist && enableRGForAI)
- {
- isEnabled = true;
- }
-
- RefreshDisabledState();
- }
-
public override void PostExposeData()
{
base.PostExposeData();
diff --git a/Source/RunAndGun/Harmony/Pawn_GetGizmos.cs b/Source/RunAndGun/Harmony/Pawn_GetGizmos.cs
index 11cc150..452907c 100644
--- a/Source/RunAndGun/Harmony/Pawn_GetGizmos.cs
+++ b/Source/RunAndGun/Harmony/Pawn_GetGizmos.cs
@@ -51,7 +51,7 @@ public static IEnumerable Postfix(IEnumerable __result, Pawn __ins
icon = ContentFinder.Get(("UI/Buttons/enable_RG"), true),
isActive = () => data.isEnabled,
toggleAction = () => { data.isEnabled = !data.isEnabled; } ,
- Disabled = data._disabled,
+ Disabled = data._disabled ?? true,
};
}
}
diff --git a/Source/RunAndGun/RunAndGun.csproj b/Source/RunAndGun/RunAndGun.csproj
index 19a0443..6de76a2 100644
--- a/Source/RunAndGun/RunAndGun.csproj
+++ b/Source/RunAndGun/RunAndGun.csproj
@@ -9,7 +9,7 @@
false
Debug;Release
..\..\1.6\Assemblies\
- 8
+ 12
@@ -57,7 +57,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
diff --git a/Source/RunAndGun/Settings.cs b/Source/RunAndGun/Settings.cs
index 92620a8..0d42c8e 100644
--- a/Source/RunAndGun/Settings.cs
+++ b/Source/RunAndGun/Settings.cs
@@ -5,6 +5,7 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using RimWorld;
using RunAndGun.Utilities;
using UnityEngine;
using Verse;
@@ -17,6 +18,7 @@ public class Settings : ModSettings
{
// === Stored settings ===
public bool dialogCEShown = false;
+ public bool enabledByDefault = true;
public bool enableForAI = true;
public int enableForFleeChance = 100;
public int accuracyPenalty = 10;
@@ -27,18 +29,19 @@ public class Settings : ModSettings
public Dictionary selectedWeapons = new Dictionary();
public Dictionary forbiddenWeapons = new Dictionary();
- public readonly bool DualWieldInstalled;
+ public readonly bool DualWieldInstalled = ModLister.AnyModActiveNoSuffix(["MemeGoddess.DualWield"]);
+ public const string CEIDs = "CETeam.CombatExtended";
+ public readonly bool CEInstalled = ModLister.AnyModActiveNoSuffix(CEIDs.Split(','));
+
+
+ private SettingsTab tab;
+ private QuickSearchWidget search = new QuickSearchWidget();
+
// === Cached state ===
public List allWeapons;
- private string[] tabNames = new[] { "Weapons", "Forbidden" };
private float maxWeightMelee, maxWeightRanged, maxWeightTotal;
-
- public Settings()
- {
- DualWieldInstalled = ModsConfig.IsActive("MemeGoddess.DualWield") ||
- ModsConfig.IsActive("MemeGoddess.DualWield_steam");
- }
+ private Color MenuSectionBGBorderColor = new ColorInt(135, 135, 135).ToColor;
public void Initialize()
{
@@ -48,15 +51,15 @@ public void Initialize()
maxWeightRanged += 1;
maxWeightTotal = Math.Max(maxWeightMelee, maxWeightRanged);
- bool combatExtendedLoaded = AssemblyExists("CombatExtended");
- if (combatExtendedLoaded && !dialogCEShown)
+ switch (CEInstalled)
{
- Find.WindowStack.Add(new Dialog_CE("RG_Dialog_CE_Title".Translate(), "RG_Dialog_CE_Description".Translate()));
- dialogCEShown = true;
- }
- else if (!combatExtendedLoaded)
- {
- dialogCEShown = false;
+ case true when !dialogCEShown:
+ Find.WindowStack.Add(new Dialog_CE("RG_Dialog_CE_Title".Translate(), "RG_Dialog_CE_Description".Translate()));
+ dialogCEShown = true;
+ break;
+ case false:
+ dialogCEShown = false;
+ break;
}
if (selectedWeapons == null)
@@ -68,59 +71,99 @@ public void Initialize()
public void DoWindowContents(Rect rect)
{
Initialize();
-
+ var color = GUI.color;
var listing = new Listing_Standard();
listing.Begin(rect);
// === General Settings ===
+ listing.CheckboxLabeled("RG_EnableRGForColonists_Title".Translate(), ref enabledByDefault, "RG_EnableRGForColonists_Description".Translate());
listing.CheckboxLabeled("RG_EnableRGForAI_Title".Translate(), ref enableForAI, "RG_EnableRGForAI_Description".Translate());
+ var box = listing.GetRect((22f + Text.LineHeight + listing.verticalSpacing + listing.verticalSpacing) * 2);
if (enableForAI)
{
- listing.Label("RG_EnableRGForFleeChance_Title".Translate() + ": " + enableForFleeChance + "%");
- enableForFleeChance = (int)Widgets.HorizontalSlider(listing.GetRect(22f), enableForFleeChance, 0, 100, false, "");
+ var AISettings = new Listing_Standard();
+ box.SplitVerticallyWithMargin(out var aiListingBox, out box, 6f);
+ AISettings.Begin(aiListingBox);
+ AISettings.Label("RG_EnableRGForFleeChance_Title".Translate() + ": " + enableForFleeChance + "%");
+ enableForFleeChance = (int)Widgets.HorizontalSlider(AISettings.GetRect(22f), enableForFleeChance, 0, 100, false, "");
- listing.Label("RG_AccuracyPenalty_Title".Translate() + ": " + accuracyPenalty + "%");
- accuracyPenalty = (int)Widgets.HorizontalSlider(listing.GetRect(22f), accuracyPenalty, 0, 100, false, "");
+ AISettings.Label("RG_AccuracyPenalty_Title".Translate() + ": " + accuracyPenalty + "%");
+ accuracyPenalty = (int)Widgets.HorizontalSlider(AISettings.GetRect(22f), accuracyPenalty, 0, 100, false, "");
+ AISettings.End();
}
// === Movement Penalties ===
- listing.Label("RG_MovementPenaltyHeavy_Title".Translate() + ": " + movementPenaltyHeavy + "%");
- movementPenaltyHeavy = (int)Widgets.HorizontalSlider(listing.GetRect(22f), movementPenaltyHeavy, 0, 100, false, "");
+ var movementSettings = new Listing_Standard();
+ movementSettings.Begin(box);
- listing.Label("RG_MovementPenaltyLight_Title".Translate() + ": " + movementPenaltyLight + "%");
- movementPenaltyLight = (int)Widgets.HorizontalSlider(listing.GetRect(22f), movementPenaltyLight, 0, 100, false, "");
+ movementSettings.Label("RG_MovementPenaltyHeavy_Title".Translate() + ": " + movementPenaltyHeavy + "%");
+ movementPenaltyHeavy = (int)Widgets.HorizontalSlider(movementSettings.GetRect(22f), movementPenaltyHeavy, 0, 100, false, "");
+
+ movementSettings.Label("RG_MovementPenaltyLight_Title".Translate() + ": " + movementPenaltyLight + "%");
+ movementPenaltyLight = (int)Widgets.HorizontalSlider(movementSettings.GetRect(22f), movementPenaltyLight, 0, 100, false, "");
+ movementSettings.End();
listing.GapLine();
// === Tabs ===
listing.Label("RG_Tabs_Title".Translate());
- if (Widgets.ButtonText(listing.GetRect(24f), tabsHandler))
+
+ //var tabs = new Listing_Standard();
+ var tabRect = listing.GetRect(Text.LineHeight);
+
+ var tabsList = new List
{
- List menu = new List();
- foreach (var name in tabNames)
- {
- string local = name;
- menu.Add(new FloatMenuOption(local, () => tabsHandler = local));
- }
- Find.WindowStack.Add(new FloatMenu(menu));
- }
+ new("RG_tab1".Translate(), () => tab = SettingsTab.Heavy, () => tab == SettingsTab.Heavy),
+ new("RG_tab2".Translate(), () => tab = SettingsTab.Forbidden, () => tab == SettingsTab.Forbidden)
- listing.GapLine();
+ };
+
+ DrawTabs(tabRect, tabsList);
+ listing.Gap(4f);
// === Filters and Custom UI ===
- if (tabsHandler == tabNames[0])
+ float remainingHeight = rect.height - listing.CurHeight;
+ var tabViewBox = listing.GetRect(remainingHeight);
+ GUI.color = MenuSectionBGBorderColor;
+ Widgets.DrawBox(tabViewBox);
+ GUI.color = color;
+ var tabView = new Listing_Standard();
+ var contraction = 8f;
+ tabView.Begin(tabViewBox.ContractedBy(contraction));
+ switch (tab)
{
- listing.Label("RG_WeightLimitFilter_Title".Translate() + $" ({weightLimitFilter:F1})");
- weightLimitFilter = Widgets.HorizontalSlider(listing.GetRect(22f), weightLimitFilter, 0f, maxWeightTotal, false, "", "0", maxWeightTotal.ToString("F1"));
+ case SettingsTab.Heavy:
+ tabView.Label("RG_WeightLimitFilter_Title".Translate() + $" ({weightLimitFilter:F1})");
+ tabView.Gap(4f);
+ weightLimitFilter = Widgets.HorizontalSlider(tabView.GetRect(22f), weightLimitFilter, 0f, maxWeightTotal, false, "", "0", maxWeightTotal.ToString("F1"));
- //DrawUtility.CustomDrawer_Filter(listing.GetRect(120f), weightLimitFilter, false, 0, maxWeightTotal, Color.yellow);
- DrawUtility.CustomDrawer_MatchingWeapons_active(listing.GetRect(253f), ref selectedWeapons, allWeapons, weightLimitFilter, "RG_ConsideredLight".Translate(), "RG_ConsideredHeavy".Translate());
- }
- else if (tabsHandler == tabNames[1])
- {
- DrawUtility.CustomDrawer_MatchingWeapons_active(listing.GetRect(253f), ref forbiddenWeapons, allWeapons, null, "RG_Allow".Translate(), "RG_Forbid".Translate());
+ search.OnGUI(tabView.GetRect(Text.LineHeight));
+ tabView.Gap(4f);
+ tabViewBox.SplitHorizontally(tabView.CurHeight + contraction, out tabViewBox, out var heavyRect);
+ tabView.End();
+
+ DrawUtility.CustomDrawer_MatchingWeapons_active(heavyRect.ContractedBy(1f), ref selectedWeapons,
+ allWeapons.Where(weapon =>
+ search.filter.Matches(weapon.label) ||
+ search.filter.Matches(weapon.defName)
+ ).ToList(),
+ weightLimitFilter, "RG_ConsideredLight".Translate(), "RG_ConsideredHeavy".Translate());
+ break;
+ case SettingsTab.Forbidden:
+ tabView.Gap(4f);
+ search.OnGUI(tabView.GetRect(Text.LineHeight));
+ tabView.Gap(4f);
+ tabViewBox.SplitHorizontally(tabView.CurHeight + contraction, out tabViewBox, out var forbiddenRect);
+ tabView.End();
+
+ DrawUtility.CustomDrawer_MatchingWeapons_active(forbiddenRect.ContractedBy(1f), ref forbiddenWeapons,
+ allWeapons.Where(weapon =>
+ search.filter.Matches(weapon.label) ||
+ search.filter.Matches(weapon.defName)
+ ).ToList(),
+ null, "RG_Allow".Translate(), "RG_Forbid".Translate());
+ break;
}
-
listing.End();
}
@@ -143,7 +186,7 @@ public override void ExposeData()
if (forbiddenWeapons == null)
forbiddenWeapons = new Dictionary();
-
+
base.ExposeData();
}
@@ -154,6 +197,70 @@ private bool AssemblyExists(string assemblyName)
return true;
return false;
}
+
+ //private void DoTabClick(SettingsTab selectedTab)
+ //{
+ // search = new QuickSearchWidget();
+
+ // tab = selectedTab == tab
+ // ? SettingsTab.None
+ // : selectedTab;
+ //}
+
+ private static Color SelectedColor = new Color(0.5f, 1f, 0.5f, 1f);
+ private void DrawTabs(Rect rect, List tabs)
+ {
+ var buttons = tabs.Count;
+ var rects = SplitRectangle(rect, buttons, 4f);
+
+ var color = GUI.color;
+ for (var index = 0; index < rects.Length; index++)
+ {
+ var button = rects[index];
+ var tab = tabs[index];
+
+ if (tab.Selected)
+ GUI.color = SelectedColor;
+ if (Widgets.ButtonText(button, tab.label))
+ tab.clickedAction();
+ GUI.color = color;
+ }
+ }
+
+ private Rect[] SplitRectangle(Rect rect, int count, float margin)
+ {
+ var rects = new Rect[count];
+ var totalMargin = margin * (count - 1);
+ var usableWidth = rect.width - totalMargin;
+ var rectWidth = usableWidth / count;
+
+ for (var i = 0; i < count; i++)
+ {
+ var xPosition = rect.x + (i * (rectWidth + margin));
+ rects[i] = new Rect(xPosition, rect.y, rectWidth, rect.height);
+ }
+
+ return rects;
+ }
+ }
+
+ public enum SettingsTab
+ {
+ Heavy,
+ Forbidden
}
+ public static class SettingsExtensions
+ {
+ private static Color SelectedColor = new Color(0.5f, 1f, 0.5f, 1f);
+ public static float Button(this Listing_Standard listing, string label, bool active, Action action)
+ {
+ var original = GUI.color;
+ GUI.color = active ? SelectedColor : original;
+ if (listing.ButtonText(label))
+ action.Invoke();
+ GUI.color = original;
+ return 30f + listing.verticalSpacing;
+ }
+ }
}
diff --git a/Source/RunAndGun/Stance_RunAndGun_Cooldown.cs b/Source/RunAndGun/Stance_RunAndGun_Cooldown.cs
index ae31e0f..64b8031 100644
--- a/Source/RunAndGun/Stance_RunAndGun_Cooldown.cs
+++ b/Source/RunAndGun/Stance_RunAndGun_Cooldown.cs
@@ -14,17 +14,15 @@ namespace RunAndGun
class Stance_RunAndGun_Cooldown : Stance_Cooldown
{
private static FieldInfo burstsLeft;
-
+ public static HashSet allowedJobs = [JobDefOf.Goto.shortHash];
private bool? hasOffHand;
private int ticksBetweenBurst;
- public override bool StanceBusy => Pawn?.CurJob == null || (Pawn.CurJob.def != JobDefOf.Goto && CheckDWBusy());
+ public override bool StanceBusy => Pawn?.CurJob == null || (!allowedJobs.Contains(Pawn.CurJobDef.shortHash) && CheckDWBusy());
public Stance_RunAndGun_Cooldown()
{
- if (!RunAndGun.settings.DualWieldInstalled || !verb.CasterIsPawn) return;
- hasOffHand = verb.CasterPawn?.equipment.HasOffHand();
}
public Stance_RunAndGun_Cooldown(int ticks, LocalTargetInfo focusTarg, Verb verb) : base(ticks, focusTarg, verb)
{
@@ -61,9 +59,7 @@ private bool CheckDWBusy()
var stance = Pawn.GetOffHandStance();
return !(stance?.curStance is Stance_Mobile);
-
}
-
}
}
diff --git a/Source/RunAndGun/Utilities/DrawUtility.cs b/Source/RunAndGun/Utilities/DrawUtility.cs
index 8888bfe..da7af34 100644
--- a/Source/RunAndGun/Utilities/DrawUtility.cs
+++ b/Source/RunAndGun/Utilities/DrawUtility.cs
@@ -21,6 +21,7 @@ public static class DrawUtility
private static readonly Color SelectedOptionColor = new Color(0.5f, 1f, 0.5f, 1f);
private static readonly Color constGrey = new Color(0.8f, 0.8f, 0.8f, 1f);
private static readonly Color background = new Color(0.5f, 0f, 0f, 0.1f);
+ private static Dictionary _colorCache = new Dictionary();
private static void DrawBackground(Rect rect, Color color)
{
@@ -41,9 +42,6 @@ private static void DrawLabel(string labelText, Rect textRect, float offset)
Text.Anchor = TextAnchor.UpperLeft;
}
- private static Color GetColor(ThingDef weapon) =>
- weapon.graphicData?.color ?? Color.white;
-
private static bool DrawIconForWeapon(ThingDef weapon, KeyValuePair item, Rect contentRect, Vector2 iconOffset, int buttonID)
{
var iconRect = new Rect(contentRect.x + iconOffset.x, contentRect.y + iconOffset.y, IconSize, IconSize);
@@ -56,7 +54,7 @@ private static bool DrawIconForWeapon(ThingDef weapon, KeyValuePair.Get("square"));
Texture2D resolvedIcon = weapon.uiIcon ?? weapon.graphicData?.Graphic?.MatSingle.mainTexture as Texture2D ?? BaseContent.BadTex;
- GUI.color = GetColor(weapon);
+ GUI.color = GetColorCached(weapon);
GUI.DrawTexture(iconRect, resolvedIcon);
GUI.color = Color.white;
@@ -68,39 +66,23 @@ private static bool DrawIconForWeapon(ThingDef weapon, KeyValuePair setting, L
setting = new Dictionary();
- Dictionary selection = new Dictionary();
+ Dictionary selection = setting;
foreach (ThingDef weapon in allWeapons)
{
@@ -146,11 +128,23 @@ public static bool CustomDrawer_MatchingWeapons_active(Rect wholeRect, ref Dicti
{
var iconSizeAndGap = IconGap + IconSize;
int iconsPerRow = (int)(wholeRect.width / 2 / iconSizeAndGap);
- var totalRows = MathF.Ceiling((setting.Count) / (float)iconsPerRow);
+
+ Dictionary weaponDefs = allWeapons.ToDictionary(o => o.defName);
+ var enabled = setting.Sum(x => weaponDefs.TryGetValue(x.Key, out _) && x.Value.isSelected ? 1 : 0);
+ var disabled = setting.Sum(x => weaponDefs.TryGetValue(x.Key, out _) && !x.Value.isSelected ? 1 : 0);
+ var totalRows = MathF.Ceiling((Math.Max(enabled, disabled)) / (float)iconsPerRow);
var fullHeight = totalRows * iconSizeAndGap + TextMargin;
+ var offset = 0f;
+ if (fullHeight > wholeRect.height)
+ {
+ iconsPerRow -= 1;
+ totalRows = MathF.Ceiling((Math.Max(enabled, disabled)) / (float)iconsPerRow);
+ fullHeight = totalRows * iconSizeAndGap + TextMargin;
+ offset = iconSizeAndGap / 2;
+ }
- Rect leftRect = new Rect(wholeRect.x, 0, wholeRect.width / 2, fullHeight);
- Rect rightRect = new Rect(leftRect.xMax, 0, wholeRect.width / 2, fullHeight);
+ Rect leftRect = new Rect(wholeRect.x + offset, 0, wholeRect.width / 2, fullHeight);
+ Rect rightRect = new Rect(leftRect.xMax - offset, 0, wholeRect.width / 2, fullHeight);
Widgets.BeginScrollView(wholeRect, ref scroll, new Rect(0f, 0f, wholeRect.width - 16f, fullHeight));
DrawBackground(new Rect(0, 0, wholeRect.width, fullHeight), background);
@@ -168,19 +162,18 @@ public static bool CustomDrawer_MatchingWeapons_active(Rect wholeRect, ref Dicti
int indexLeft = 0, indexRight = 0;
bool changed = false;
- Dictionary weaponDefs = allWeapons.ToDictionary(o => o.defName);
foreach (var item in selection)
{
- bool isSelected = item.Value.isSelected;
- Rect targetRect = isSelected ? rightRect : leftRect;
- int index = isSelected ? indexRight++ : indexLeft++;
-
- int col = index % iconsPerRow;
- int row = index / iconsPerRow;
-
if (weaponDefs.TryGetValue(item.Key, out ThingDef weapon))
{
+ bool isSelected = item.Value.isSelected;
+ Rect targetRect = isSelected ? rightRect : leftRect;
+ int index = isSelected ? indexRight++ : indexLeft++;
+
+ int col = index % iconsPerRow;
+ int row = index / iconsPerRow;
+
Vector2 pos = new Vector2(IconSize * col + col * IconGap, IconSize * row + row * IconGap);
if (DrawIconForWeapon(weapon, item, targetRect, pos, index))
{