Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Languages/English/Keyed/Keys.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@
<CE_ReloadLabel>Reload</CE_ReloadLabel>
<CE_ReloadDesc>Reloads this gun.</CE_ReloadDesc>
<CE_TurretReloading>Reloading</CE_TurretReloading>
<CE_TryReloadAmmoTooltip>Pawn will opportunistically reload at {0} rounds remaining. </CE_TryReloadAmmoTooltip>
<CE_Settings_OpportunisticReload_Title>Opportunistic reload</CE_Settings_OpportunisticReload_Title>
<CE_Settings_OpportunisticReload_Desc>Pawns will reload if their mag is empty enough (can be changed via slider) and it's safe</CE_Settings_OpportunisticReload_Desc>
<CE_Settings_OpportunisticReload_Off>Off</CE_Settings_OpportunisticReload_Off>
<CE_Settings_OpportunisticReload_DraftedOnly>Drafted only</CE_Settings_OpportunisticReload_DraftedOnly>
<CE_Settings_OpportunisticReload_Any>Any</CE_Settings_OpportunisticReload_Any>
<CE_Settings_OpportunisticReload_SafeDistance_Title>Safe distance for reload: {0}</CE_Settings_OpportunisticReload_SafeDistance_Title>
<CE_Settings_OpportunisticReload_SafeDistance_Desc>Minimal distance to closest hostile pawn (no LoS required)</CE_Settings_OpportunisticReload_SafeDistance_Desc>
<CE_Settings_OpportunisticReload_SecondsAfterFight_Title>Cooldown after fight: {0} sec.</CE_Settings_OpportunisticReload_SecondsAfterFight_Title>
<CE_Settings_OpportunisticReload_SecondsAfterFight_Desc>Count of seconds after fight that must pass before pawn will be able to use opportunistic reload</CE_Settings_OpportunisticReload_SecondsAfterFight_Desc>

<!-- Inventory -->
<CE_TakeFromOther_Equipping>Equipping</CE_TakeFromOther_Equipping>
Expand Down
14 changes: 12 additions & 2 deletions Languages/Russian/Keyed/Keys.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,18 @@
<CE_ReloadLabel>Перезарядить</CE_ReloadLabel>
<CE_ReloadDesc>Перезарядить это оружие.</CE_ReloadDesc>
<CE_TurretReloading>Перезарядка</CE_TurretReloading>

<!-- Inventory -->
<CE_TryReloadAmmoTooltip>Пешка будет перезаряжаться при {0} патронах в магазине, если безопасно</CE_TryReloadAmmoTooltip>
<CE_Settings_OpportunisticReload_Title>Оппортунистическая перезарядка</CE_Settings_OpportunisticReload_Title>
<CE_Settings_OpportunisticReload_Desc>Пешки будут перезаряжаться если их магазин достаточно пуст (может быть настроено помощью слайдера) и если это безопасно.</CE_Settings_OpportunisticReload_Desc>
<CE_Settings_OpportunisticReload_Off>Отключено</CE_Settings_OpportunisticReload_Off>
<CE_Settings_OpportunisticReload_DraftedOnly>Только в призыве</CE_Settings_OpportunisticReload_DraftedOnly>
<CE_Settings_OpportunisticReload_Any>В любом случае</CE_Settings_OpportunisticReload_Any>
<CE_Settings_OpportunisticReload_SafeDistance_Title>Безопасная дистанция перезарядки: {0}</CE_Settings_OpportunisticReload_SafeDistance_Title>
<CE_Settings_OpportunisticReload_SafeDistance_Desc>Минимальная дистанция до противника (прямая видимость не требуется)</CE_Settings_OpportunisticReload_SafeDistance_Desc>
<CE_Settings_OpportunisticReload_SecondsAfterFight_Title>Не перезаряжаться после сражения: {0} сек.</CE_Settings_OpportunisticReload_SecondsAfterFight_Title>
<CE_Settings_OpportunisticReload_SecondsAfterFight_Desc>Минимальное количество секунд которое должно пройти после сражения перед оппортунистической перезарядкой.</CE_Settings_OpportunisticReload_SecondsAfterFight_Desc>

<!-- Inventory -->
<CE_TakeFromOther_Equipping>Экипирует</CE_TakeFromOther_Equipping>
<CE_TakeFromOther_Taking>Берёт</CE_TakeFromOther_Taking>

Expand Down
14 changes: 14 additions & 0 deletions Patches/Core/ThinkTreeDefs/AllDrafted.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Patch>

<!-- ========== All draftable pawns now have custom reload job giver ========== -->

<Operation Class="PatchOperationInsert">
<xpath>Defs/ThinkTreeDef//li[@Class="JobGiver_Orders"]</xpath>
<value>
<li Class="CombatExtended.JobGiver_CheckReload" />
</value>
</Operation>


</Patch>
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public override IEnumerable<Gizmo> GetGizmos()
var ammo = GetAmmo();
if (ammo != null)
{
foreach (Command com in ammo.CompGetGizmosExtra())
foreach (var com in ammo.CompGetGizmosExtra())
{
if (base.Faction != Faction.OfPlayer && Prefs.DevMode && com is GizmoAmmoStatus)
{
Expand Down
11 changes: 11 additions & 0 deletions Source/CombatExtended/CombatExtended/Comps/CompAmmoUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class CompAmmoUser : CompRangedGizmoGiver
#region Fields

private int curMagCountInt = 0;
private int tryReloadOn = 0;
private AmmoDef currentAmmoInt = null;
private AmmoDef selectedAmmo;

Expand Down Expand Up @@ -78,6 +79,15 @@ public int MagAmmoCount
}
}

public int TryReloadOn
{
get => tryReloadOn;
set => tryReloadOn = value;
}
public float SafeDistanceToReload => Controller.settings.OpportunisticReloadSafeDistance;

public int MinimalTicksAfterFight => GenTicks.TicksPerRealSecond * Controller.settings.SecondsAfterFightToOpportunisticReload;

public int MagSizeOverride
{
get
Expand Down Expand Up @@ -324,6 +334,7 @@ public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref curMagCountInt, "count", 0);
Scribe_Values.Look(ref tryReloadOn, nameof(tryReloadOn));
Scribe_Defs.Look(ref currentAmmoInt, "currentAmmo");
Scribe_Defs.Look(ref selectedAmmo, "selectedAmmo");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CombatExtended
{
public enum OpportunisticReloadMode
{
Off,
DraftedOnly,
Any
}
}
63 changes: 42 additions & 21 deletions Source/CombatExtended/CombatExtended/Gizmos/GizmoAmmoStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,61 @@

namespace CombatExtended;
[StaticConstructorOnStartup]
public class GizmoAmmoStatus : Command
public class GizmoAmmoStatus : Gizmo_Slider
{

public CompAmmoUser compAmmo;
public string prefix = "";
private static readonly new Texture2D BGTex = ContentFinder<Texture2D>.Get("UI/Widgets/DesButBG", true);

public override float GetWidth(float maxWidth)
public override float Width => 120;

public override float Target
{
return 120;
get => (float)compAmmo.TryReloadOn / compAmmo.MagSize;
set => compAmmo.TryReloadOn = Mathf.FloorToInt(value * compAmmo.MagSize);
}

public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
{
Rect backgroundRect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), Height);
public override bool IsDraggable => Controller.settings.OpportunisticReloadMode != OpportunisticReloadMode.Off && compAmmo.MagSize > 1;

Rect inRect = backgroundRect.ContractedBy(6);
GUI.DrawTexture(backgroundRect, BGTex);
public override float ValuePercent => (float)compAmmo.CurMagCount / compAmmo.MagSize;

Text.Font = GameFont.Tiny;
Rect textRect = inRect.TopHalf();
Widgets.Label(textRect, prefix + (compAmmo.CurrentAmmo == null ? compAmmo.parent.def.LabelCap : compAmmo.CurrentAmmo.ammoClass.LabelCap));
public override string Title => prefix + (compAmmo.CurrentAmmo == null ? compAmmo.parent.def.LabelCap : compAmmo.CurrentAmmo.ammoClass.LabelCap);

if (compAmmo.HasMagazine)
{
Rect barRect = inRect.BottomHalf();
Widgets.FillableBar(barRect, (float)compAmmo.CurMagCount / compAmmo.MagSize);
private static bool draggingBar = false;
public override bool DraggingBar
{
get => draggingBar;
set => draggingBar = value;
}

public override string GetTooltip()
{
return "CE_ReloadAmmoTooltip".Translate();
}

Text.Font = GameFont.Small;
Text.Anchor = TextAnchor.MiddleCenter;
Widgets.Label(barRect, compAmmo.CurMagCount + " / " + compAmmo.MagSize);
Text.Anchor = TextAnchor.UpperLeft;
public override string BarLabel => compAmmo.CurMagCount + " / " + compAmmo.MagSize;
public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
{
if (IsDraggable)
{
Rect rect = new Rect(topLeft.x, topLeft.y, this.GetWidth(maxWidth), 75f);
if (Mouse.IsOver(rect))
{
TooltipHandler.TipRegion(rect, new Func<string>(this.AutoReloadTip), 42827123);
}
}
return base.GizmoOnGUI(topLeft, maxWidth, parms);
}
public override void DrawHeader(Rect rect, ref bool mouseOverElement)
{

return new GizmoResult(GizmoState.Clear);
Text.Font = GameFont.Tiny;
base.DrawHeader(rect, ref mouseOverElement);
Text.Font = GameFont.Small;
}

private string AutoReloadTip()
{
return "CE_TryReloadAmmoTooltip".Translate(compAmmo.TryReloadOn);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,40 @@ private bool DoReloadCheck(Pawn pawn, out ThingWithComps reloadWeapon, out AmmoD
{
return false; // There isn't any work to do since the pawn doesn't have a CE Inventory.
}
tmpComp = pawn.equipment?.Primary?.TryGetComp<CompAmmoUser>();
if (pawn.Drafted)
{
if (Controller.settings.OpportunisticReloadMode == OpportunisticReloadMode.Off)
{
return false;
}
if (tmpComp == null)
{
return false;
}
if (Find.TickManager.TicksGame - pawn.LastAttackTargetTick < tmpComp.MinimalTicksAfterFight)
{
return false;
}
var enemiesAround = pawn.Map.mapPawns.AllPawnsSpawned.Where(x => x.Position.InHorDistOf(pawn.Position, tmpComp.SafeDistanceToReload) && !x.IsPsychologicallyInvisible() && x.HostileTo(pawn));
if (enemiesAround.Any())
{
return false;
}

}

if ((tmpComp = pawn.equipment?.Primary?.TryGetComp<CompAmmoUser>()) != null && tmpComp.HasMagazine)
if (tmpComp != null && tmpComp.HasMagazine)
{
guns.Add(pawn.equipment.Primary);
}

// CompInventory doesn't track equipment and it's desired to check the pawn's equipped weapon before inventory items so need to copy stuff from Inventory Cache.
guns.AddRange(inventory.rangedWeaponList.Where(t => t.TryGetComp<CompAmmoUser>() != null && t.GetComp<CompAmmoUser>().HasMagazine));
// We don't reload other guns if pawn is drafted
if (!pawn.Drafted)
{
// CompInventory doesn't track equipment and it's desired to check the pawn's equipped weapon before inventory items so need to copy stuff from Inventory Cache.
guns.AddRange(inventory.rangedWeaponList.Where(t => t.TryGetComp<CompAmmoUser>() != null && t.GetComp<CompAmmoUser>().HasMagazine));
}

if (guns.NullOrEmpty())
{
Expand All @@ -133,6 +159,10 @@ private bool DoReloadCheck(Pawn pawn, out ThingWithComps reloadWeapon, out AmmoD
tmpComp = gun.TryGetComp<CompAmmoUser>();
AmmoDef ammoType = tmpComp.CurrentAmmo;
int ammoAmount = tmpComp.CurMagCount;
if ((Controller.settings.OpportunisticReloadMode == OpportunisticReloadMode.Any || (Controller.settings.OpportunisticReloadMode == OpportunisticReloadMode.DraftedOnly && pawn.Drafted)) && ammoAmount > tmpComp.TryReloadOn)
{
continue;
}
int magazineSize = tmpComp.MagSize;

// Is the gun loaded with ammo not in a Loadout/HoldTracker?
Expand Down
51 changes: 50 additions & 1 deletion Source/CombatExtended/CombatExtended/ModSettings/Settings.cs
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using UnityEngine;
using CombatExtended.Loader;
using System.Collections.Generic;
using System;

namespace CombatExtended;
public class Settings : ModSettings, ISettingsCE
Expand All @@ -29,6 +30,9 @@ public class Settings : ModSettings, ISettingsCE
private bool enableArcOfFire = false;

private bool enableCIWS = false;
private OpportunisticReloadMode opportunisticReloadMode = OpportunisticReloadMode.Any;
private float opportunisticReloadSafeDistance = 12.9f;
private int secondsAfterFightToOpportunisticReload = 5;

private bool showExtraTooltips = false;
private bool detailedMeleeTooltip = false;
Expand Down Expand Up @@ -75,6 +79,9 @@ public class Settings : ModSettings, ISettingsCE

public bool ShowExtraStats => showExtraStats;
public bool EnableCIWS => enableCIWS;
public OpportunisticReloadMode OpportunisticReloadMode => opportunisticReloadMode;
public float OpportunisticReloadSafeDistance => opportunisticReloadSafeDistance;
public int SecondsAfterFightToOpportunisticReload => secondsAfterFightToOpportunisticReload;

public float MedicineSearchRadiusSquared => medicineSearchRadius * medicineSearchRadius;

Expand Down Expand Up @@ -265,6 +272,11 @@ public override void ExposeData()
lastAmmoSystemStatus = enableAmmoSystem; // Store this now so we can monitor for changes

Scribe_Values.Look(ref medicineSearchRadius, "medicineSearchRadius", 5f);

//OpportunisticReload
Scribe_Values.Look(ref opportunisticReloadMode, nameof(opportunisticReloadMode), OpportunisticReloadMode.Any);
Scribe_Values.Look(ref opportunisticReloadSafeDistance, nameof(opportunisticReloadSafeDistance), 12.9f);
Scribe_Values.Look(ref secondsAfterFightToOpportunisticReload, nameof(secondsAfterFightToOpportunisticReload), 5);
}
public void DoWindowContents(Listing_Standard list)
{
Expand All @@ -289,7 +301,7 @@ public void DoWindowContents(Listing_Standard list)
private void DoSettingsWindowContents_Mechanics(Listing_Standard list)
{

Rect fullRect = list.GetRect(500f);
Rect fullRect = list.GetRect(600f);
const float columnPadding = 16f;
float columnWidth = (fullRect.width - columnPadding) / 2f;

Expand Down Expand Up @@ -319,6 +331,25 @@ private void DoSettingsWindowContents_Mechanics(Listing_Standard list)
left.Gap();
left.CheckboxLabeled("CE_Settings_BipodMechanics_Title".Translate(), ref bipodMechanics, "CE_Settings_BipodMechanics_Desc".Translate());
left.CheckboxLabeled("CE_Settings_BipodAutoSetUp_Title".Translate(), ref autosetup, "CE_Settings_BipodAutoSetUp_Desc".Translate());
left.GapLine();
if (left.ButtonTextLabeled("CE_Settings_OpportunisticReload_Title".Translate(), OpportunisticReloadModeLabel(this.OpportunisticReloadMode), anchor: TextAnchor.MiddleLeft, tooltip: "CE_Settings_OpportunisticReload_Desc".Translate()))
{
var floatMenuList = new List<FloatMenuOption>();
foreach (var option in Enum.GetValues(typeof(OpportunisticReloadMode)))
{
var enumOption = (OpportunisticReloadMode)option;
var menuOption = new FloatMenuOption(OpportunisticReloadModeLabel(enumOption), () => opportunisticReloadMode = enumOption);
floatMenuList.Add(menuOption);
}
Find.WindowStack.Add(new FloatMenu(floatMenuList));
}
if (OpportunisticReloadMode == OpportunisticReloadMode.Off)
{
GUI.contentColor = Color.gray;
}
opportunisticReloadSafeDistance = left.SliderLabeled("CE_Settings_OpportunisticReload_SafeDistance_Title".Translate(opportunisticReloadSafeDistance), opportunisticReloadSafeDistance, 0f, 34.9f, tooltip: "CE_Settings_OpportunisticReload_SafeDistance_Desc".Translate());
secondsAfterFightToOpportunisticReload = (int)left.SliderLabeled("CE_Settings_OpportunisticReload_SecondsAfterFight_Title".Translate(secondsAfterFightToOpportunisticReload), secondsAfterFightToOpportunisticReload, 0f, 15f, tooltip: "CE_Settings_OpportunisticReload_SecondsAfterFight_Desc".Translate());
GUI.contentColor = Color.white;
left.End();

// RIGHT COLUMN
Expand Down Expand Up @@ -512,6 +543,9 @@ private void ResetToDefault_Mechanics()
autosetup = true;
medicineSearchRadius = 5f;
suppressionCausesRunning = true;
opportunisticReloadMode = OpportunisticReloadMode.Any;
opportunisticReloadSafeDistance = 12.9f;
secondsAfterFightToOpportunisticReload = 5;
}
private void ResetToDefault_Ammo()
{
Expand Down Expand Up @@ -602,6 +636,21 @@ public float GetOrCalculateHeightForTab(int tabIndex, float width)
return height;
}

private string OpportunisticReloadModeLabel(OpportunisticReloadMode mode)
{
switch (mode)
{
case OpportunisticReloadMode.Off:
return "CE_Settings_OpportunisticReload_Off".Translate();
case OpportunisticReloadMode.DraftedOnly:
return "CE_Settings_OpportunisticReload_DraftedOnly".Translate();
case OpportunisticReloadMode.Any:
return "CE_Settings_OpportunisticReload_Any".Translate();
default:
return "";
}
}

#endregion
}

Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ public override IEnumerable<Gizmo> GetGizmos() // Modified
// Ammo gizmos
if (CompAmmo != null && (PlayerControlled || Prefs.DevMode))
{
foreach (Command com in CompAmmo.CompGetGizmosExtra())
foreach (var com in CompAmmo.CompGetGizmosExtra())
{
if (!PlayerControlled && Prefs.DevMode && com is GizmoAmmoStatus)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using HarmonyLib;
using RimWorld;
using RimWorld.Utility;
using System;
using System.Collections.Generic;
using UnityEngine;
using Verse;
using Verse.AI;

namespace CombatExtended.HarmonyCE;
[HarmonyPatch(typeof(JobGiver_Orders), "TryGiveJob")]
internal static class Harmony_JobGiver_Orders_TryGiveJob
{
internal static void Postfix(Pawn pawn, Job __result)
{
if (__result != null && __result.expiryInterval < 0)
{
__result.expiryInterval = GenTicks.TicksPerRealSecond * 5; //Let other jobs (like a reload) start sometimes
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public override IEnumerable<Gizmo> GetGizmos()
var ammo = GetAmmo();
if (ammo != null)
{
foreach (Command com in ammo.CompGetGizmosExtra())
foreach (var com in ammo.CompGetGizmosExtra())
{
if (base.Faction != Faction.OfPlayer && Prefs.DevMode && com is GizmoAmmoStatus)
{
Expand Down