Track LocalMods as part of monolith
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
using Barotrauma;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace MoreLevelContent.Shared.Utils
|
||||
{
|
||||
internal static class AfflictionHelper
|
||||
{
|
||||
internal static bool TryGetAffliction(string identifier, out AfflictionPrefab affliction)
|
||||
{
|
||||
affliction = AfflictionPrefab.List.FirstOrDefault(a => a.Identifier == identifier);
|
||||
return affliction != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.MoreLevelContent.Shared.Utils
|
||||
{
|
||||
public static class CharacterUtils
|
||||
{
|
||||
internal static Character CreateHuman(HumanPrefab humanPrefab, List<Character> characters, Dictionary<Character, List<Item>> characterItems, Submarine submarine, CharacterTeamType teamType, ISpatialEntity positionToStayIn = null)
|
||||
{
|
||||
CharacterInfo characterInfo = humanPrefab.CreateCharacterInfo(Rand.RandSync.ServerAndClient);
|
||||
characterInfo.TeamID = teamType;
|
||||
|
||||
if (positionToStayIn == null)
|
||||
{
|
||||
positionToStayIn =
|
||||
WayPoint.GetRandom(SpawnType.Human, characterInfo.Job?.Prefab, submarine) ??
|
||||
WayPoint.GetRandom(SpawnType.Human, null, submarine);
|
||||
}
|
||||
|
||||
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, positionToStayIn.WorldPosition, ToolBox.RandomSeed(8), characterInfo);
|
||||
spawnedCharacter.HumanPrefab = humanPrefab;
|
||||
humanPrefab.InitializeCharacter(spawnedCharacter, positionToStayIn);
|
||||
humanPrefab.GiveItems(spawnedCharacter, submarine, null, Rand.RandSync.ServerAndClient);
|
||||
|
||||
characters.Add(spawnedCharacter);
|
||||
characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true));
|
||||
|
||||
return spawnedCharacter;
|
||||
}
|
||||
|
||||
internal static Character CreateHuman(HumanPrefab humanPrefab, List<Character> characters, Dictionary<Character, List<Item>> characterItems, CharacterTeamType teamType, Vector2 spawnPosition, bool createNetEvent = true)
|
||||
{
|
||||
CharacterInfo characterInfo = humanPrefab.CreateCharacterInfo(Rand.RandSync.ServerAndClient);
|
||||
characterInfo.TeamID = teamType;
|
||||
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, spawnPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: createNetEvent);
|
||||
spawnedCharacter.HumanPrefab = humanPrefab;
|
||||
humanPrefab.InitializeCharacter(spawnedCharacter);
|
||||
humanPrefab.GiveItems(spawnedCharacter, null, null, Rand.RandSync.ServerAndClient, createNetworkEvents: createNetEvent);
|
||||
|
||||
characters.Add(spawnedCharacter);
|
||||
characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true));
|
||||
|
||||
return spawnedCharacter;
|
||||
}
|
||||
|
||||
internal static HumanPrefab GetHumanPrefabFromElement(XElement element)
|
||||
{
|
||||
if (element.Attribute("name") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Identifier characterIdentifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
Identifier characterFrom = element.GetAttributeIdentifier("from", Identifier.Empty);
|
||||
HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
|
||||
if (humanPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn character for mission: character prefab \"" + characterIdentifier + "\" not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
return humanPrefab;
|
||||
}
|
||||
|
||||
public static XElement GetRandomDifficultyModifiedElement(XElement parentElement, float levelDifficulty, float randomnessModifier, Random rand)
|
||||
{
|
||||
// look for the element that is closest to our difficulty, with some randomness
|
||||
XElement bestElement = null;
|
||||
float bestValue = float.MaxValue;
|
||||
foreach (XElement element in parentElement.Elements())
|
||||
{
|
||||
float applicabilityValue = GetDifficultyModifiedValue(element.GetAttributeFloat(0f, "preferreddifficulty"), levelDifficulty, randomnessModifier, rand);
|
||||
if (applicabilityValue < bestValue)
|
||||
{
|
||||
bestElement = element;
|
||||
bestValue = applicabilityValue;
|
||||
}
|
||||
}
|
||||
return bestElement;
|
||||
}
|
||||
|
||||
private static float GetDifficultyModifiedValue(float preferredDifficulty, float levelDifficulty, float randomnessModifier, Random rand) => Math.Abs(levelDifficulty - preferredDifficulty + MathHelper.Lerp(-randomnessModifier, randomnessModifier, (float)rand.NextDouble()));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.MoreLevelContent.Shared.Utils
|
||||
{
|
||||
public static class CommandUtils
|
||||
{
|
||||
public static void AddCommand(string cmdName, string cmdHelp, LuaCsAction callback, LuaCsFunc args = null, bool isCheat = false) =>
|
||||
GameMain.LuaCs.Game.AddCommand(cmdName, cmdHelp, callback, args, isCheat);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Barotrauma;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.MoreLevelContent.Shared.Utils;
|
||||
using Barotrauma.Steam;
|
||||
using MoreLevelContent.Shared;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
public class CompatabilityHelper : Singleton<CompatabilityHelper>
|
||||
{
|
||||
public bool ReactorModInstalled { get; private set; }
|
||||
public bool HazardousReactorsInstalled { get; private set; }
|
||||
public bool DynamicEuropaInstalled { get; private set; }
|
||||
|
||||
internal Faction BanditFaction
|
||||
{
|
||||
get
|
||||
{
|
||||
_Bandits ??= GameMain.GameSession.Campaign.GetFaction("bandits");
|
||||
return _Bandits;
|
||||
}
|
||||
}
|
||||
|
||||
private Faction _Bandits;
|
||||
|
||||
internal static void SetupHazReactor(Reactor reactor)
|
||||
{
|
||||
Item reactorItem = reactor.Item;
|
||||
reactorItem.InvulnerableToDamage = true;
|
||||
|
||||
// tell bots not to stay in the hull the reactor is in
|
||||
if (reactorItem.CurrentHull != null) reactorItem.CurrentHull.AvoidStaying = true;
|
||||
}
|
||||
|
||||
public override void Setup()
|
||||
{
|
||||
HazardousReactorsInstalled = CheckInstalled(2547888957);
|
||||
if (ItemPrefab.Prefabs.TryGet("reactor1", out ItemPrefab prefab))
|
||||
{
|
||||
ReactorModInstalled = prefab.IsOverride;
|
||||
}
|
||||
|
||||
DynamicEuropaInstalled = CheckInstalled(2532991202);
|
||||
|
||||
|
||||
Log.Debug(
|
||||
$"-= MLC Compatability =-\n" +
|
||||
$"- HazReactorsInstalled: {HazardousReactorsInstalled}\n" +
|
||||
$"- ReactorModInstalled: {ReactorModInstalled}\n" +
|
||||
$"- DynamicEuropa: {DynamicEuropaInstalled}");
|
||||
}
|
||||
|
||||
bool CheckInstalled(ulong workshopID) => ContentPackageManager.EnabledPackages.All.Any(p => p.TryExtractSteamWorkshopId(out SteamWorkshopId idOut) && idOut.Value == workshopID);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using Barotrauma;
|
||||
using HarmonyLib;
|
||||
using System.Reflection;
|
||||
using System;
|
||||
using Barotrauma.MoreLevelContent.Shared.Utils;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection.Emit;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MoreLevelContent.Shared.Generation.Pirate;
|
||||
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
#endif
|
||||
|
||||
namespace MoreLevelContent.Shared.Utils
|
||||
{
|
||||
internal class Hooks : Singleton<Hooks>
|
||||
{
|
||||
public delegate void UpdateAction(float deltaTime, Camera cam);
|
||||
|
||||
internal event Action<Structure, float, Character> OnStructureDamaged;
|
||||
private List<UpdateAction> _UpdateActions = new();
|
||||
#if CLIENT
|
||||
internal event Action<SpriteBatch, Camera> OnDebugDraw;
|
||||
internal event Action<Sonar, Vector2, float> OnUpdateSonarDisruption;
|
||||
private FieldInfo Sonar_activePingsCount;
|
||||
#endif
|
||||
//float deltaTime, Camera cam
|
||||
public override void Setup()
|
||||
{
|
||||
var Structure_SetDamage = typeof(HumanAIController).GetMethod(nameof(HumanAIController.StructureDamaged), BindingFlags.Public | BindingFlags.Static);
|
||||
_ = Main.Harmony.Patch(Structure_SetDamage, prefix: new HarmonyMethod(typeof(Hooks), nameof(StructureDamaged)));
|
||||
|
||||
MethodInfo level_update = AccessTools.Method(typeof(Level), "Update");
|
||||
_ = Main.Harmony.Patch(level_update, postfix: new HarmonyMethod(AccessTools.Method(typeof(Hooks), nameof(Hooks.Update))));
|
||||
|
||||
#if CLIENT
|
||||
var Level_DrawDebugOverlay = typeof(Level).GetMethod(nameof(Level.DrawDebugOverlay));
|
||||
_ = Main.Harmony.Patch(Level_DrawDebugOverlay, postfix: new HarmonyMethod(typeof(Hooks), nameof(DebugDraw)));
|
||||
|
||||
var Sonar_UpdateDisruptions = AccessTools.Method(typeof(Sonar), "UpdateDisruptions");//typeof(Sonar).GetMethod("UpdateDisruptions");
|
||||
Sonar_activePingsCount = AccessTools.Field(typeof(Sonar), "activePingsCount");
|
||||
if (Sonar_UpdateDisruptions == null) Log.Error("\n\n\n\nNull");
|
||||
_ = Main.Harmony.Patch(Sonar_UpdateDisruptions, postfix: new HarmonyMethod(typeof(Hooks), nameof(SonarDisruption)));
|
||||
#endif
|
||||
Log.Debug("Patched Hooks");
|
||||
}
|
||||
|
||||
public void AddUpdateAction(UpdateAction action)
|
||||
{
|
||||
_UpdateActions.Add(action);
|
||||
}
|
||||
|
||||
private static void StructureDamaged(Structure structure, float damageAmount, Character character) => Instance.OnStructureDamaged?.Invoke(structure, damageAmount, character);
|
||||
|
||||
private static void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
foreach (var item in Instance._UpdateActions)
|
||||
{
|
||||
item.Invoke(deltaTime, cam);
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
private static void DebugDraw(SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
if (!GameMain.DebugDraw) return;
|
||||
Instance.OnDebugDraw?.Invoke(spriteBatch, cam);
|
||||
}
|
||||
|
||||
private static void SonarDisruption(Sonar __instance, Vector2 pingSource, float worldPingRadius)
|
||||
{
|
||||
int activePingsCount = (int)Instance.Sonar_activePingsCount.GetValue(__instance);
|
||||
for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex)
|
||||
{
|
||||
Instance.OnUpdateSonarDisruption?.Invoke(__instance, pingSource, worldPingRadius);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
using Barotrauma;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using HarmonyLib;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Voronoi2;
|
||||
using static Barotrauma.Level;
|
||||
|
||||
namespace MoreLevelContent.Shared.Utils
|
||||
{
|
||||
public static class MLCUtils
|
||||
{
|
||||
internal static string GetRandomTag(string baseTag)
|
||||
{
|
||||
int maxIndex = 1;
|
||||
while (TextManager.ContainsTag(baseTag + maxIndex))
|
||||
{
|
||||
maxIndex++;
|
||||
}
|
||||
return "mlc.lostcargo.tooslow" + Rand.Range(0, maxIndex);
|
||||
}
|
||||
|
||||
internal static string GetRandomTag(string baseTag, LevelData data)
|
||||
{
|
||||
Random rand = new MTRandom(ToolBox.StringToInt(data.Seed));
|
||||
int maxIndex = 1;
|
||||
while (TextManager.ContainsTag(baseTag + maxIndex))
|
||||
{
|
||||
maxIndex++;
|
||||
}
|
||||
return baseTag + rand.Next(0, maxIndex);
|
||||
}
|
||||
|
||||
|
||||
internal static Vector2 PositionItemOnEdge(Item target, GraphEdge edge, float height, bool setRotation = false)
|
||||
{
|
||||
Vector2 dir = Vector2.Normalize(edge.GetNormal(edge.Cell1 ?? edge.Cell2));
|
||||
float angle = Angle(dir) - 90;
|
||||
Vector2 pos = ConvertUnits.ToSimUnits(edge.Center + (edge.GetNormal(edge.Cell1 ?? edge.Cell2) * height));
|
||||
SetItemPosition(target, pos, setRotation ? MathHelper.ToRadians(angle) : 0);
|
||||
target.Rotation = -angle;
|
||||
return dir;
|
||||
}
|
||||
internal static void SetItemPosition(Item target, Vector2 simPos, float rot) => target.SetTransform(simPos - (target.Submarine?.SimPosition ?? Vector2.Zero), rot, false);
|
||||
internal static float Angle(Vector2 dir) => (float)(MathUtils.VectorToAngle(dir) * 180 / Math.PI);
|
||||
|
||||
internal static Random GetLevelRandom()
|
||||
{
|
||||
if (Level.Loaded == null)
|
||||
{
|
||||
Log.Error("Level was null when we tried to get a random instance!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new MTRandom(ToolBox.StringToInt(Level.Loaded.LevelData.Seed));
|
||||
}
|
||||
|
||||
internal static Location FindUnlockLocation(FindLocationInfo info)
|
||||
{
|
||||
if (GameMain.GameSession.GameMode is not CampaignMode campaign)
|
||||
{
|
||||
Log.Warn("Not campaign mode, can't find location");
|
||||
return null;
|
||||
}
|
||||
if (info.MinDistance <= 1)
|
||||
{
|
||||
return campaign.Map.CurrentLocation;
|
||||
}
|
||||
|
||||
var currentLocation = campaign.Map.CurrentLocation;
|
||||
int distance = 0;
|
||||
HashSet<Location> checkedLocations = new HashSet<Location>();
|
||||
HashSet<Location> pendingLocations = new HashSet<Location>() { currentLocation };
|
||||
do
|
||||
{
|
||||
List<Location> currentLocations = pendingLocations.ToList();
|
||||
pendingLocations.Clear();
|
||||
foreach (var location in currentLocations)
|
||||
{
|
||||
checkedLocations.Add(location);
|
||||
if (IsLocationValid(currentLocation, location, distance, info))
|
||||
{
|
||||
return location;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (LocationConnection connection in location.Connections)
|
||||
{
|
||||
var otherLocation = connection.OtherLocation(location);
|
||||
if (checkedLocations.Contains(otherLocation)) { continue; }
|
||||
pendingLocations.Add(otherLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
distance++;
|
||||
} while (pendingLocations.Any());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static bool IsLocationValid(Location currentLocation, Location location, int distance, FindLocationInfo info)
|
||||
{
|
||||
if (!info.RequiredFaction.IsEmpty)
|
||||
{
|
||||
if (location.Faction?.Prefab.Identifier != info.RequiredFaction &&
|
||||
location.SecondaryFaction?.Prefab.Identifier != info.RequiredFaction)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (info.AllowedLocationTypes != null && info.AllowedLocationTypes.Count() > 0 && !info.AllowedLocationTypes.Contains(location.Type.Identifier) && !(location.HasOutpost() && info.AllowedLocationTypes.Contains(Tags.AnyOutpost)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (distance < info.MinDistance)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (info.MustBeFurtherOnMap && location.MapPosition.X < currentLocation.MapPosition.X)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (info.MustBeHidden && !location.Discovered)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
internal struct FindLocationInfo
|
||||
{
|
||||
public int MinDistance;
|
||||
public int MaxDistance;
|
||||
public bool MustBeFurtherOnMap;
|
||||
public IEnumerable<Identifier> AllowedLocationTypes;
|
||||
public Identifier RequiredFaction;
|
||||
public bool MustBeHidden;
|
||||
}
|
||||
|
||||
internal static Random GetRandomFromString(string seed)
|
||||
{
|
||||
return new MTRandom(ToolBox.StringToInt(seed));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,544 @@
|
||||
using Barotrauma;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using HarmonyLib;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Voronoi2;
|
||||
using static Barotrauma.Level;
|
||||
|
||||
namespace MoreLevelContent.Shared.Utils
|
||||
{
|
||||
public class TrackingSonarMarker
|
||||
{
|
||||
public (LocalizedString Label, Vector2 Position) CurrentPosition { get; private set; }
|
||||
private readonly LocalizedString label;
|
||||
|
||||
public TrackingSonarMarker(float updateInterval, Func<Vector2> getPositionFunc, LocalizedString sonarLabel)
|
||||
{
|
||||
_updateInterval = updateInterval;
|
||||
_getPositionFunc = getPositionFunc;
|
||||
label = sonarLabel;
|
||||
Init();
|
||||
}
|
||||
|
||||
internal TrackingSonarMarker(float updateInterval, Submarine submarine, LocalizedString sonarLabel)
|
||||
{
|
||||
_updateInterval = updateInterval;
|
||||
_getPositionFunc = () => submarine.WorldPosition;
|
||||
label = sonarLabel;
|
||||
Init();
|
||||
}
|
||||
|
||||
readonly float _updateInterval;
|
||||
readonly Func<Vector2> _getPositionFunc;
|
||||
private float _timeSinceLastUpdate;
|
||||
|
||||
private void Init() => CurrentPosition = (label, _getPositionFunc.Invoke());
|
||||
|
||||
public void Update(float delta)
|
||||
{
|
||||
_timeSinceLastUpdate += delta;
|
||||
if (_timeSinceLastUpdate > _updateInterval)
|
||||
{
|
||||
_timeSinceLastUpdate = 0;
|
||||
CurrentPosition = (label, _getPositionFunc.Invoke());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class StructureDamageTracker : IDisposable
|
||||
{
|
||||
public event Action ThresholdCrossed;
|
||||
public event OnStructureTakeDamage DamageAfterThreshold;
|
||||
|
||||
public delegate void OnStructureTakeDamage(float amount);
|
||||
|
||||
const float MaxDamagePerSecond = 5.0f;
|
||||
const float MaxDamagePerFrame = MaxDamagePerSecond * (float)Timing.Step;
|
||||
|
||||
private readonly Submarine _sub;
|
||||
private readonly float _threshold;
|
||||
private readonly float _decayPerSec;
|
||||
private readonly float _decayDelay;
|
||||
private float _accumulatedDamage;
|
||||
private bool _threshholdCrossed = false;
|
||||
private float _decayTimer;
|
||||
|
||||
internal StructureDamageTracker(Submarine subToTrack, Func<float, float> resetDamageFunc, float threshold = 20f, float decay = 1f, float decayDelay = 5f)
|
||||
{
|
||||
Hooks.Instance.OnStructureDamaged += OnStructureDamaged;
|
||||
_sub = subToTrack;
|
||||
_threshold = threshold;
|
||||
_decayPerSec = decay * (float)Timing.Step;
|
||||
_decayDelay = decayDelay;
|
||||
_resetDamageFunc = resetDamageFunc;
|
||||
}
|
||||
|
||||
internal StructureDamageTracker(Submarine subToTrack, float threshold = 20f, float decay = 1f, float decayDelay = 5f)
|
||||
{
|
||||
Hooks.Instance.OnStructureDamaged += OnStructureDamaged;
|
||||
_sub = subToTrack;
|
||||
_threshold = threshold;
|
||||
_decayPerSec = decay * (float)Timing.Step;
|
||||
_decayDelay = decayDelay;
|
||||
_resetDamageFunc = (float val) => 0;
|
||||
}
|
||||
|
||||
readonly Func<float, float> _resetDamageFunc;
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_accumulatedDamage > 0 && _decayTimer <= 0)
|
||||
{
|
||||
_accumulatedDamage -= _decayPerSec;
|
||||
}
|
||||
|
||||
if (_decayTimer > 0)
|
||||
{
|
||||
_decayTimer -= (float)Timing.Step;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStructureDamaged(Structure structure, float damageAmount, Character character)
|
||||
{
|
||||
if (character == null || !character.IsPlayer) { return; }
|
||||
if (structure?.Submarine == null || structure.Submarine != _sub) { return; }
|
||||
// ignore interior walls so gun fights don't cause rep loss
|
||||
if (structure.Prefab.Tags.Contains("inner")) { return; }
|
||||
|
||||
_accumulatedDamage += MathHelper.Clamp(damageAmount, 0, MaxDamagePerFrame);
|
||||
_decayTimer = _decayDelay;
|
||||
|
||||
if (_accumulatedDamage < _threshold) return;
|
||||
|
||||
if (!_threshholdCrossed)
|
||||
{
|
||||
ThresholdCrossed?.Invoke();
|
||||
_accumulatedDamage = _resetDamageFunc.Invoke(_accumulatedDamage);
|
||||
_threshholdCrossed = true;
|
||||
Log.Debug($"Reset accumulated damage to {_accumulatedDamage}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameMain.GameSession?.Campaign?.Map?.CurrentLocation?.Reputation != null)
|
||||
{
|
||||
DamageAfterThreshold?.Invoke(damageAmount);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Don't know if I really need to do this but why not
|
||||
Hooks.Instance.OnStructureDamaged -= OnStructureDamaged;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MissionUtils
|
||||
{
|
||||
internal static bool TryGetInterestingPosition(PositionType positionType, float minDistFromSubs, out Point position)
|
||||
{
|
||||
if (!Loaded.PositionsOfInterest.Any())
|
||||
{
|
||||
position = new Point(Loaded.Size.X / 2, Loaded.Size.Y / 2);
|
||||
Log.Debug("Failed to find point, default to middle of level");
|
||||
return false;
|
||||
}
|
||||
|
||||
List<InterestingPosition> suitablePositions = Loaded.PositionsOfInterest.FindAll(p => positionType.HasFlag(p.PositionType));
|
||||
|
||||
if (positionType.HasFlag(PositionType.MainPath) || positionType.HasFlag(PositionType.SidePath))
|
||||
{
|
||||
suitablePositions.RemoveAll(p => Loaded.IsPositionInsideWall(p.Position.ToVector2()));
|
||||
}
|
||||
|
||||
if (!suitablePositions.Any())
|
||||
{
|
||||
Log.Debug("Failed to find point, no positions not inside walls");
|
||||
position = Loaded.PositionsOfInterest[Rand.Int(Loaded.PositionsOfInterest.Count, Rand.RandSync.ServerAndClient)].Position;
|
||||
return false;
|
||||
}
|
||||
|
||||
List<InterestingPosition> farEnoughPositions = new List<InterestingPosition>(suitablePositions);
|
||||
if (minDistFromSubs > 0.0f)
|
||||
{
|
||||
int beforeFilter = farEnoughPositions.Count;
|
||||
int filtered = farEnoughPositions.RemoveAll(p => Vector2.DistanceSquared(p.Position.ToVector2(), Loaded.StartPosition) < minDistFromSubs * minDistFromSubs);
|
||||
Log.Debug($"Removed {filtered} positions, after filer {farEnoughPositions.Count}, before: {beforeFilter}");
|
||||
}
|
||||
|
||||
if (!farEnoughPositions.Any())
|
||||
{
|
||||
string errorMsg = "Could not find a position of interest far enough from the submarines. (PositionType: " + positionType + ", minDistFromSubs: " + minDistFromSubs + ")\n" + Environment.StackTrace.CleanupStackTrace();
|
||||
Log.Error(errorMsg);
|
||||
|
||||
float maxDist = 0.0f;
|
||||
position = suitablePositions.First().Position;
|
||||
foreach (InterestingPosition pos in suitablePositions)
|
||||
{
|
||||
float dist = Submarine.Loaded.Sum(s =>
|
||||
Submarine.MainSubs.Contains(s) ? Vector2.DistanceSquared(s.WorldPosition, pos.Position.ToVector2()) : 0.0f);
|
||||
if (dist > maxDist)
|
||||
{
|
||||
position = pos.Position;
|
||||
maxDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
position = farEnoughPositions[Rand.Int(farEnoughPositions.Count, Rand.RandSync.ServerAndClient)].Position;
|
||||
Log.Debug("Found position!");
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Vector2 GetPosInRect(Rectangle rect, Random rand)
|
||||
{
|
||||
float x = rand.Next(0, rect.Width);
|
||||
float y = rand.Next(0, rect.Height);
|
||||
y = Math.Clamp(y, rect.Height / 3, rect.Height / 2);
|
||||
return new Vector2(rect.X + x, rect.Y + y);
|
||||
}
|
||||
static FieldInfo _waypointTagsField;
|
||||
|
||||
internal static void TagSubmarineWaypoints(this Submarine submarine, string tag)
|
||||
{
|
||||
if (_waypointTagsField == null)
|
||||
{
|
||||
_waypointTagsField = typeof(WayPoint).GetField("tags", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
}
|
||||
var validWaypoints = WayPoint.WayPointList.Where(wp => wp.Submarine == submarine && wp.SpawnType == SpawnType.Human);
|
||||
foreach (var waypoint in validWaypoints)
|
||||
{
|
||||
HashSet<Identifier> tags = (HashSet<Identifier>)_waypointTagsField.GetValue(waypoint);
|
||||
_ = tags.Add(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We copy the whole method over here just so we can add a check
|
||||
// to see if we're too close to a beacon station because
|
||||
// trying to patch local methods through IL code is fuckin ass
|
||||
public static class SubPlacementUtils
|
||||
{
|
||||
private static List<Rectangle> BlockedRects = new List<Rectangle>();
|
||||
|
||||
public static void ClearBlockedRects() => BlockedRects.Clear();
|
||||
|
||||
internal static Submarine SpawnSubOnPath(string subName, ContentFile contentFile, SubmarineType type, PlacementType placementType = PlacementType.Bottom) => SpawnSubOnPath(subName, contentFile.Path.Value, type, placementType);
|
||||
|
||||
internal static Submarine SpawnSubOnPath(string subName, string path, SubmarineType type, PlacementType placementType = PlacementType.Bottom)
|
||||
{
|
||||
var tempSW = new Stopwatch();
|
||||
FieldInfo _levelCells = AccessTools.Field(typeof(Level), "cells");
|
||||
List<VoronoiCell> cells = (List<VoronoiCell>)_levelCells.GetValue(Loaded);
|
||||
|
||||
// Min distance between a sub and the start/end/other sub.
|
||||
const float minDistance = Sonar.DefaultSonarRange;
|
||||
var waypoints = WayPoint.WayPointList.Where(wp =>
|
||||
wp.Submarine == null &&
|
||||
wp.SpawnType == SpawnType.Path &&
|
||||
wp.WorldPosition.X < Loaded.EndExitPosition.X &&
|
||||
!Loaded.IsCloseToStart(wp.WorldPosition, minDistance) &&
|
||||
!Loaded.IsCloseToEnd(wp.WorldPosition, minDistance)).ToList();
|
||||
|
||||
var subDoc = SubmarineInfo.OpenFile(path);
|
||||
Rectangle subBorders = Submarine.GetBorders(subDoc.Root);
|
||||
SubmarineInfo info = new SubmarineInfo(path)
|
||||
{
|
||||
Type = type
|
||||
};
|
||||
|
||||
// Add some margin so that the sub doesn't block the path entirely. It's still possible that some larger subs can't pass by.
|
||||
Point paddedDimensions = new Point(subBorders.Width + 3000, subBorders.Height + 3000);
|
||||
|
||||
var positions = new List<Vector2>();
|
||||
var rects = new List<Rectangle>();
|
||||
int maxAttempts = 50;
|
||||
int attemptsLeft = maxAttempts;
|
||||
bool success = false;
|
||||
Vector2 spawnPoint = Vector2.Zero;
|
||||
var allCells = Loaded.GetAllCells();
|
||||
while (attemptsLeft > 0)
|
||||
{
|
||||
if (attemptsLeft < maxAttempts)
|
||||
{
|
||||
Debug.WriteLine($"Failed to position the sub {subName}. Trying again.");
|
||||
}
|
||||
attemptsLeft--;
|
||||
if (TryGetSpawnPoint(out spawnPoint))
|
||||
{
|
||||
success = TryPositionSub(subBorders, subName, placementType, ref spawnPoint);
|
||||
if (success)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
positions.Clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.NewMessage($"Failed to find any spawn point for the sub: {subName} (No valid waypoints left).", Color.Red);
|
||||
break;
|
||||
}
|
||||
}
|
||||
tempSW.Stop();
|
||||
if (success)
|
||||
{
|
||||
Debug.WriteLine($"Sub {subName} successfully positioned to {spawnPoint} in {tempSW.ElapsedMilliseconds} (ms)");
|
||||
tempSW.Restart();
|
||||
Submarine sub = new Submarine(info);
|
||||
tempSW.Stop();
|
||||
Debug.WriteLine($"Sub {sub.Info.Name} loaded in {tempSW.ElapsedMilliseconds} (ms)");
|
||||
sub.SetPosition(spawnPoint);
|
||||
BlockedRects.Add(sub.GetDockedBorders());
|
||||
return sub;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.NewMessage($"Failed to position wreck {subName}. Used {tempSW.ElapsedMilliseconds} (ms).", Color.Red);
|
||||
return null;
|
||||
}
|
||||
|
||||
bool TryPositionSub(Rectangle subBorders, string subName, PlacementType placement, ref Vector2 spawnPoint)
|
||||
{
|
||||
positions.Add(spawnPoint);
|
||||
bool bottomFound = TryRaycast(subBorders, placement, ref spawnPoint);
|
||||
positions.Add(spawnPoint);
|
||||
|
||||
bool leftSideBlocked = IsSideBlocked(subBorders, false);
|
||||
bool rightSideBlocked = IsSideBlocked(subBorders, true);
|
||||
int step = 5;
|
||||
if (rightSideBlocked && !leftSideBlocked)
|
||||
{
|
||||
bottomFound = TryMove(subBorders, placement, ref spawnPoint, -step);
|
||||
}
|
||||
else if (leftSideBlocked && !rightSideBlocked)
|
||||
{
|
||||
bottomFound = TryMove(subBorders, placement, ref spawnPoint, step);
|
||||
}
|
||||
else if (!bottomFound)
|
||||
{
|
||||
if (!leftSideBlocked)
|
||||
{
|
||||
bottomFound = TryMove(subBorders, placement, ref spawnPoint, -step);
|
||||
}
|
||||
else if (!rightSideBlocked)
|
||||
{
|
||||
bottomFound = TryMove(subBorders, placement, ref spawnPoint, step);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"Invalid position {spawnPoint}. Does not touch the ground.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
positions.Add(spawnPoint);
|
||||
bool isBlocked = IsBlocked(spawnPoint, subBorders.Size - new Point(step + 50));
|
||||
if (isBlocked)
|
||||
{
|
||||
rects.Add(ToolBox.GetWorldBounds(spawnPoint.ToPoint(), subBorders.Size));
|
||||
Debug.WriteLine($"Invalid position {spawnPoint}. Blocked by level walls.");
|
||||
}
|
||||
else if (!bottomFound)
|
||||
{
|
||||
Debug.WriteLine($"Invalid position {spawnPoint}. Does not touch the ground.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var sp = spawnPoint;
|
||||
if (Loaded.Wrecks.Any(w => Vector2.DistanceSquared(w.WorldPosition, sp) < minDistance * minDistance))
|
||||
{
|
||||
Debug.WriteLine($"Invalid position {spawnPoint}. Too close to other wreck(s).");
|
||||
return false;
|
||||
}
|
||||
if (Loaded.BeaconStation != null)
|
||||
{
|
||||
if (Vector2.DistanceSquared(Loaded.BeaconStation.WorldPosition, sp) < minDistance * minDistance)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (Vector2.DistanceSquared(Loaded.StartPosition, sp) < minDistance * minDistance)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Vector2.DistanceSquared(Loaded.EndPosition, sp) < minDistance * minDistance)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
return !isBlocked && bottomFound;
|
||||
|
||||
bool TryMove(Rectangle subBorders, PlacementType placement, ref Vector2 spawnPoint, float amount)
|
||||
{
|
||||
float maxMovement = 5000;
|
||||
float totalAmount = 0;
|
||||
bool foundBottom = TryRaycast(subBorders, placement, ref spawnPoint);
|
||||
while (!IsSideBlocked(subBorders, amount > 0))
|
||||
{
|
||||
foundBottom = TryRaycast(subBorders, placement, ref spawnPoint);
|
||||
totalAmount += amount;
|
||||
spawnPoint = new Vector2(spawnPoint.X + amount, spawnPoint.Y);
|
||||
if (Math.Abs(totalAmount) > maxMovement)
|
||||
{
|
||||
Debug.WriteLine($"Moving the sub {subName} failed.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return foundBottom;
|
||||
}
|
||||
}
|
||||
|
||||
bool TryGetSpawnPoint(out Vector2 spawnPoint)
|
||||
{
|
||||
spawnPoint = Vector2.Zero;
|
||||
while (waypoints.Any())
|
||||
{
|
||||
var wp = waypoints.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
waypoints.Remove(wp);
|
||||
if (!IsBlocked(wp.WorldPosition, paddedDimensions))
|
||||
{
|
||||
spawnPoint = wp.WorldPosition;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryRaycast(Rectangle subBorders, PlacementType placement, ref Vector2 spawnPoint)
|
||||
{
|
||||
// Shoot five rays and pick the highest hit point.
|
||||
int rayCount = 5;
|
||||
var positions = new Vector2[rayCount];
|
||||
bool hit = false;
|
||||
for (int i = 0; i < rayCount; i++)
|
||||
{
|
||||
float quarterWidth = subBorders.Width * 0.25f;
|
||||
Vector2 rayStart = spawnPoint;
|
||||
switch (i)
|
||||
{
|
||||
case 1:
|
||||
rayStart = new Vector2(spawnPoint.X - quarterWidth, spawnPoint.Y);
|
||||
break;
|
||||
case 2:
|
||||
rayStart = new Vector2(spawnPoint.X + quarterWidth, spawnPoint.Y);
|
||||
break;
|
||||
case 3:
|
||||
rayStart = new Vector2(spawnPoint.X - quarterWidth / 2, spawnPoint.Y);
|
||||
break;
|
||||
case 4:
|
||||
rayStart = new Vector2(spawnPoint.X + quarterWidth / 2, spawnPoint.Y);
|
||||
break;
|
||||
}
|
||||
var simPos = ConvertUnits.ToSimUnits(rayStart);
|
||||
var body = Submarine.PickBody(simPos, new Vector2(simPos.X, placement == PlacementType.Bottom ? -1 : Loaded.Size.Y + 1),
|
||||
customPredicate: f => f.Body == Loaded.TopBarrier || f.Body == Loaded.BottomBarrier || (f.Body?.UserData is VoronoiCell cell && cell.Body.BodyType == BodyType.Static && !Loaded.ExtraWalls.Any(w => w.Body == f.Body)),
|
||||
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall);
|
||||
if (body != null)
|
||||
{
|
||||
positions[i] =
|
||||
ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) +
|
||||
new Vector2(0, subBorders.Height / 2 * (placement == PlacementType.Bottom ? 1 : -1));
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
float highestPoint = placement == PlacementType.Bottom ? positions.Max(p => p.Y) : positions.Min(p => p.Y);
|
||||
spawnPoint = new Vector2(spawnPoint.X, highestPoint);
|
||||
return hit;
|
||||
}
|
||||
|
||||
bool IsSideBlocked(Rectangle subBorders, bool front)
|
||||
{
|
||||
// Shoot three rays and check whether any of them hits.
|
||||
int rayCount = 3;
|
||||
Vector2 halfSize = subBorders.Size.ToVector2() / 2;
|
||||
Vector2 quarterSize = halfSize / 2;
|
||||
var positions = new Vector2[rayCount];
|
||||
for (int i = 0; i < rayCount; i++)
|
||||
{
|
||||
float dir = front ? 1 : -1;
|
||||
Vector2 rayStart;
|
||||
Vector2 to;
|
||||
switch (i)
|
||||
{
|
||||
case 1:
|
||||
rayStart = new Vector2(spawnPoint.X + halfSize.X * dir, spawnPoint.Y + quarterSize.Y);
|
||||
to = new Vector2(spawnPoint.X + (halfSize.X - quarterSize.X) * dir, rayStart.Y);
|
||||
break;
|
||||
case 2:
|
||||
rayStart = new Vector2(spawnPoint.X + halfSize.X * dir, spawnPoint.Y - quarterSize.Y);
|
||||
to = new Vector2(spawnPoint.X + (halfSize.X - quarterSize.X) * dir, rayStart.Y);
|
||||
break;
|
||||
case 0:
|
||||
default:
|
||||
rayStart = spawnPoint;
|
||||
to = new Vector2(spawnPoint.X + halfSize.X * dir, rayStart.Y);
|
||||
break;
|
||||
}
|
||||
Vector2 simPos = ConvertUnits.ToSimUnits(rayStart);
|
||||
if (Submarine.PickBody(simPos, ConvertUnits.ToSimUnits(to),
|
||||
customPredicate: f => f.Body?.UserData is VoronoiCell cell,
|
||||
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsBlocked(Vector2 pos, Point size, float maxDistanceMultiplier = 1)
|
||||
{
|
||||
float maxDistance = size.Multiply(maxDistanceMultiplier).ToVector2().LengthSquared();
|
||||
Rectangle bounds = ToolBox.GetWorldBounds(pos.ToPoint(), size);
|
||||
if (Loaded.Ruins.Any(r => ToolBox.GetWorldBounds(r.Area.Center, r.Area.Size).IntersectsWorld(bounds)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Loaded.Caves.Any(c =>
|
||||
ToolBox.GetWorldBounds(c.Area.Center, c.Area.Size).IntersectsWorld(bounds) ||
|
||||
ToolBox.GetWorldBounds(c.StartPos, new Point(1500)).IntersectsWorld(bounds)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return cells.Any(c => c.Body != null && Vector2.DistanceSquared(pos, c.Center) <= maxDistance && c.BodyVertices.Any(v => bounds.ContainsWorld(v)));
|
||||
}
|
||||
}
|
||||
internal static void PositionSubmarine(Submarine submarine, PositionType positionType)
|
||||
{
|
||||
float dist = Sonar.DefaultSonarRange * 2;
|
||||
if (MissionUtils.TryGetInterestingPosition(positionType, dist, out Point point))
|
||||
{
|
||||
Vector2 spawnPos = point.ToVector2();
|
||||
Point subSize = submarine.GetDockedBorders().Size;
|
||||
int graceDistance = 500; // the sub still spawns awkwardly close to walls, so this helps. could also be given as a parameter instead
|
||||
spawnPos = submarine.FindSpawnPos(spawnPos, new Point(subSize.X + graceDistance, subSize.Y + graceDistance));
|
||||
submarine.SetPosition(spawnPos);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SetCrushDepth(Submarine sub, bool inf = false)
|
||||
{
|
||||
if (inf)
|
||||
{
|
||||
sub.SetCrushDepth(float.MaxValue);
|
||||
return;
|
||||
}
|
||||
float depth = Math.Max(sub.RealWorldCrushDepth, Submarine.MainSub.RealWorldCrushDepth);
|
||||
depth = Math.Max(depth, Loaded.GetRealWorldDepth(sub.Position.Y) + 1000);
|
||||
sub.SetCrushDepth(depth);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using Barotrauma;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
namespace MoreLevelContent.Shared.Utils
|
||||
{
|
||||
public static class PhysUtil
|
||||
{
|
||||
// public static RayHit RayCastFirst(Vector2 point_0, Vector2 point_1)
|
||||
// {
|
||||
// RayHit hit = new RayHit() { Hit = false };
|
||||
// Func<Fixture, Vector2, Vector2, float, float> get_first_callback = delegate (Fixture fixture, Vector2 point, Vector2 normal, float fraction)
|
||||
// {
|
||||
// hit = new RayHit(fixture, point, normal);
|
||||
// return 0;
|
||||
// };
|
||||
//
|
||||
// // Summary:
|
||||
// // Ray-cast the world for all fixtures in the path of the ray. Your callback
|
||||
// // controls whether you get the closest point, any point, or n-points. The
|
||||
// // ray-cast ignores shapes that contain the starting point. Inside the callback:
|
||||
// // return -1: ignore this fixture and continue
|
||||
// // return 0: terminate the ray cast
|
||||
// // return fraction: clip the ray to this point
|
||||
// // return 1: don't clip the ray and continue
|
||||
// GameMain.World.RayCast(get_first_callback, point_0, point_1);
|
||||
// return hit;
|
||||
// }
|
||||
|
||||
public static RayHit RaycastWorld(Vector2 start_simpos, Vector2 end_simpos, IEnumerable<Body> ignoredBodies = null)
|
||||
{
|
||||
RayHit hit = new RayHit() { Hit = false };
|
||||
|
||||
|
||||
GameMain.World.RayCast((fixture, point, normal, fraction) =>
|
||||
{
|
||||
if (!CheckFixtureCollision(fixture, ignoredBodies, Physics.CollisionLevel | Physics.CollisionWall, true, CheckForWalls)) return -1;
|
||||
hit.Hit = true;
|
||||
hit.Body = fixture.Body;
|
||||
return 0;
|
||||
}, start_simpos, end_simpos, Physics.CollisionLevel | Physics.CollisionWall);
|
||||
|
||||
|
||||
return hit;
|
||||
}
|
||||
|
||||
public static bool WorldPositionClear(Vector2 simPos)
|
||||
{
|
||||
var aabb = new FarseerPhysics.Collision.AABB(simPos + Vector2.One, simPos - Vector2.One);
|
||||
bool clear = true;
|
||||
GameMain.World.QueryAABB((fixture) =>
|
||||
{
|
||||
if (!CheckFixtureCollision(fixture, null, Physics.CollisionLevel | Physics.CollisionWall, true, CheckForWalls)) return true;
|
||||
clear = false;
|
||||
return false;
|
||||
}, ref aabb);
|
||||
return clear;
|
||||
}
|
||||
|
||||
static bool CheckForWalls(Fixture f) => (f.Body?.UserData is VoronoiCell cell && cell.Body.BodyType == BodyType.Static) ||
|
||||
!Level.Loaded.ExtraWalls.Any(w => w.Body == f.Body);
|
||||
|
||||
private static bool CheckFixtureCollision(Fixture fixture, IEnumerable<Body> ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate<Fixture> customPredicate = null)
|
||||
{
|
||||
if (fixture == null ||
|
||||
(ignoreSensors && fixture.IsSensor) ||
|
||||
fixture.CollisionCategories == Category.None ||
|
||||
fixture.CollisionCategories == Physics.CollisionItem)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (customPredicate != null && !customPredicate(fixture))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (collisionCategory != null &&
|
||||
!fixture.CollisionCategories.HasFlag((Category)collisionCategory) &&
|
||||
!((Category)collisionCategory).HasFlag(fixture.CollisionCategories))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ignoredBodies != null && ignoredBodies.Contains(fixture.Body))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fixture.Body.UserData is Structure structure)
|
||||
{
|
||||
if (structure.IsPlatform && collisionCategory != null && !((Category)collisionCategory).HasFlag(Physics.CollisionPlatform))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public struct RayHit
|
||||
{
|
||||
public bool Hit;
|
||||
public Body Body;
|
||||
public RayHit(Body body)
|
||||
{
|
||||
Body = body;
|
||||
Hit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.MoreLevelContent.Shared.Utils
|
||||
{
|
||||
public abstract class Singleton<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Static instance. Needs to use lambda expression
|
||||
/// to construct an instance (since constructor is private).
|
||||
/// </summary>
|
||||
private static readonly Lazy<T> sInstance = new Lazy<T>(() => CreateInstanceOfT());
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance of this singleton.
|
||||
/// </summary>
|
||||
public static T Instance => sInstance.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of T via reflection since T's constructor is expected to be private.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static T CreateInstanceOfT() => Activator.CreateInstance(typeof(T), true) as T;
|
||||
|
||||
public abstract void Setup();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using Barotrauma;
|
||||
using Barotrauma.MoreLevelContent.Shared.Utils;
|
||||
using HarmonyLib;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using static Barotrauma.Items.Components.Sonar;
|
||||
|
||||
namespace MoreLevelContent.Shared.Utils
|
||||
{
|
||||
public class SonarExtensions : Singleton<SonarExtensions>
|
||||
{
|
||||
private readonly List<SonarDisturbance> _Disturbances = new();
|
||||
|
||||
public override void Setup()
|
||||
{
|
||||
#if CLIENT
|
||||
Hooks.Instance.OnUpdateSonarDisruption += Instance_OnUpdateSonarDisruption;
|
||||
Dictionary<BlipType, Color[]> sonarBlips = (Dictionary<BlipType, Color[]>)ReflectionInfo.Instance.blipColorGradient.GetValue(null);
|
||||
if (!sonarBlips.ContainsKey((BlipType)5))
|
||||
{
|
||||
sonarBlips.Add((BlipType)5, new Color[] { Color.TransparentBlack, new Color(0, 68, 65) * 0.9f, new Color(0, 68, 65) * 0.65f, new Color(0, 68, 65) * 0.25f });
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
internal void Add(Item source, float strength)
|
||||
{
|
||||
_Disturbances.Add(new SonarDisturbance(source, strength));
|
||||
}
|
||||
|
||||
internal void Remove(Item source)
|
||||
{
|
||||
var dist = _Disturbances.Find(d => d.Item == source);
|
||||
if (dist != null)
|
||||
{
|
||||
_Disturbances.Remove(dist);
|
||||
}
|
||||
}
|
||||
|
||||
private void Instance_OnUpdateSonarDisruption(Barotrauma.Items.Components.Sonar sonar, Vector2 pingSource, float worldPingRadius)
|
||||
{
|
||||
for (int i = 0; i < _Disturbances.Count; i++)
|
||||
{
|
||||
var disturbance = _Disturbances[i];
|
||||
if (disturbance.Item == null || disturbance.Item.Removed)
|
||||
{
|
||||
_Disturbances.Remove(disturbance);
|
||||
continue;
|
||||
}
|
||||
MLCExtensions.AddSonarDisruption(sonar, pingSource, disturbance.Position, disturbance.Strength);
|
||||
}
|
||||
}
|
||||
|
||||
private class SonarDisturbance
|
||||
{
|
||||
public SonarDisturbance(Item source, float strength)
|
||||
{
|
||||
Item = source;
|
||||
Strength = strength;
|
||||
}
|
||||
internal Item Item { get; private set; }
|
||||
public float Strength { get; private set; }
|
||||
|
||||
public Vector2 Position => Item?.WorldPosition ?? Vector2.Zero;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using Barotrauma;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.MoreLevelContent.Shared.Utils;
|
||||
using HarmonyLib;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Steamworks.Ugc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using static Barotrauma.Items.Components.Sonar;
|
||||
|
||||
namespace MoreLevelContent.Shared.Utils
|
||||
{
|
||||
internal static class MLCExtensions
|
||||
{
|
||||
// Private field access
|
||||
internal static float GetMinRotation(this Turret turret) => (float)ReflectionInfo.Instance.minRotation.GetValue(turret);
|
||||
internal static float GetMaxRotation(this Turret turret) => (float)ReflectionInfo.Instance.maxRotation.GetValue(turret);
|
||||
internal static List<(Vector2 pos, float strength)> GetDisruptedDirections(this Sonar sonar) => (List<(Vector2 pos, float strength)>)ReflectionInfo.Instance.disruptedDirections.GetValue(sonar);
|
||||
|
||||
internal static void AddSonarDisruption(this Sonar sonar, Vector2 pingSource, Vector2 disruptionPos, float disruptionStrength)
|
||||
{
|
||||
#if CLIENT
|
||||
disruptionStrength = Math.Min(disruptionStrength, 10.0f);
|
||||
Vector2 dir = disruptionPos - pingSource;
|
||||
float disruptionDist = Vector2.Distance(pingSource, disruptionPos);
|
||||
sonar.GetDisruptedDirections().Add(((disruptionPos - pingSource) / disruptionDist, disruptionStrength));
|
||||
for (int i = 0; i < disruptionStrength * 10.0f; i++)
|
||||
{
|
||||
Vector2 pos = disruptionPos + Rand.Vector(Rand.Range(0.0f, Level.GridCellSize * 4 * disruptionStrength));
|
||||
if (Vector2.Dot(pos - pingSource, -dir) > 1.0f - disruptionStrength) { continue; }
|
||||
var blip = new SonarBlip(
|
||||
pos,
|
||||
MathHelper.Lerp(0.1f, 1.5f, Math.Min(disruptionStrength, 1.0f)),
|
||||
Rand.Range(0.2f, 1.0f + disruptionStrength),
|
||||
BlipType.Disruption);
|
||||
List<SonarBlip> blips = (List<SonarBlip>)ReflectionInfo.Instance.sonarBlips.GetValue(sonar);
|
||||
blips.Add(blip);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
// Still doesn't work
|
||||
internal static void AddSonarCircle(this Sonar sonar, Vector2 pingSource, BlipType type, int blipCount = 10, float range = 0)
|
||||
{
|
||||
Vector2 sonarPos = sonar.Item.Submarine != null && sonar.Item.body == null ? sonar.Item.Submarine.WorldPosition : sonar.Item.WorldPosition;
|
||||
Vector2 targetVector = pingSource - sonarPos;
|
||||
if (targetVector.LengthSquared() > MathUtils.Pow2(range)) return;
|
||||
|
||||
// Don't display pings if we're in sonar range
|
||||
if (targetVector.LengthSquared() < MathUtils.Pow2(DefaultSonarRange)) return;
|
||||
|
||||
float dist = targetVector.Length();
|
||||
Vector2 targetDir = targetVector / dist;
|
||||
for (int i = 0; i < blipCount; i++)
|
||||
{
|
||||
float angle = Rand.Range(-0.5f, 0.5f);
|
||||
Vector2 blipDir = MathUtils.RotatePoint(targetDir, angle);
|
||||
Vector2 invBlipDir = MathUtils.RotatePoint(targetDir, -angle);
|
||||
var longRangeBlip = new SonarBlip(sonarPos + blipDir * sonar.Range * 0.9f, Rand.Range(1.9f, 2.1f), Rand.Range(1.0f, 1.5f), type)
|
||||
{
|
||||
Velocity = -invBlipDir * (MathUtils.Round(Rand.Range(8000.0f, 15000.0f), 2000.0f) - Math.Abs(angle * angle * 10000.0f)),
|
||||
Rotation = (float)Math.Atan2(-invBlipDir.Y, invBlipDir.X),
|
||||
Alpha = MathUtils.Pow2((range - dist) / range) * 0.25f
|
||||
};
|
||||
longRangeBlip.Size.Y *= 4.0f;
|
||||
List<SonarBlip> blips = (List<SonarBlip>)ReflectionInfo.Instance.sonarBlips.GetValue(sonar);
|
||||
blips.Add(longRangeBlip);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// for (int i = 0; i < amount; i++)
|
||||
// {
|
||||
// Vector2 dir = Rand.Vector(1.0f);
|
||||
// var longRangeBlip = new SonarBlip(pingSource, Rand.Range(1.9f, 2.1f), Rand.Range(1.0f, 1.5f), type)
|
||||
// {
|
||||
// Velocity = dir * MathUtils.Round(Rand.Range(4000.0f, 6000.0f), 1000.0f),
|
||||
// Rotation = (float)Math.Atan2(-dir.Y, dir.X)
|
||||
// };
|
||||
// longRangeBlip.Size.Y *= 4.0f;
|
||||
// List<SonarBlip> blips = (List<SonarBlip>)ReflectionInfo.Instance.sonarBlips.GetValue(sonar);
|
||||
// blips.Add(longRangeBlip);
|
||||
// }
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
public class ReflectionInfo : Singleton<ReflectionInfo>
|
||||
{
|
||||
public FieldInfo minRotation;
|
||||
public FieldInfo maxRotation;
|
||||
public FieldInfo disruptedDirections;
|
||||
public FieldInfo sonarBlips;
|
||||
public FieldInfo blipColorGradient;
|
||||
|
||||
public override void Setup()
|
||||
{
|
||||
// Fields
|
||||
minRotation = AccessTools.Field(typeof(Turret), "minRotation");
|
||||
maxRotation = AccessTools.Field(typeof(Turret), "maxRotation");
|
||||
disruptedDirections = AccessTools.Field(typeof(Sonar), "disruptedDirections");
|
||||
sonarBlips = AccessTools.Field(typeof(Sonar), "sonarBlips");
|
||||
blipColorGradient = AccessTools.Field(typeof(Sonar), "blipColorGradient");
|
||||
Log.Debug("Setup field references");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user