3643 lines
102 KiB
C#
3643 lines
102 KiB
C#
using Sandbox.Game.EntityComponents;
|
|
using Sandbox.ModAPI.Ingame;
|
|
using Sandbox.ModAPI.Interfaces;
|
|
using SpaceEngineers.Game.ModAPI.Ingame;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using VRage;
|
|
using VRage.Collections;
|
|
using VRage.Game;
|
|
using VRage.Game.Components;
|
|
using VRage.Game.GUI.TextPanel;
|
|
using VRage.Game.ModAPI.Ingame;
|
|
using VRage.Game.ModAPI.Ingame.Utilities;
|
|
using VRage.Game.ObjectBuilders.Definitions;
|
|
using VRage.Scripting.MemorySafeTypes;
|
|
using VRageMath;
|
|
|
|
namespace IngameScript
|
|
{
|
|
public partial class Program : MyGridProgram
|
|
{
|
|
#region mdk preserve
|
|
string Ver = "1.1.295-R";
|
|
|
|
// This is a version of TGP adapted to work with Nebulous Radar
|
|
//
|
|
// TGP Targeting Pod (Raycast tracking custom turret) (c)cheerkin
|
|
// Steam WS: https://steamcommunity.com/sharedfiles/filedetails/?id=3158039679
|
|
|
|
static int LOGGER_MAX_CHARS = 5000;
|
|
// no toggle support yet
|
|
Dictionary<string, string> keyUpBindings = new Dictionary<string, string>()
|
|
{
|
|
{ "a", "command:prev-target" },
|
|
{ "d", "command:next-target" },
|
|
{ "e", "command:cast" },
|
|
{ "q", "command:drop-and-ban" },
|
|
{ "w", "command:cycle-offset" },
|
|
{ "s", "command:static-cast" }
|
|
};
|
|
Dictionary<string, string> dTapBindings = new Dictionary<string, string>()
|
|
{
|
|
{ "spacebar", "command:clear-user-shift" },
|
|
{ "c", "command:reset-rotors" }
|
|
};
|
|
|
|
static class Variables
|
|
{
|
|
static Dictionary<string, ISettable> v = new Dictionary<string, ISettable> {
|
|
{ "turret-target-min-size", new Variable<double> { value = 0f, parser = s => double.Parse(s) } },
|
|
{ "raycast-range", new Variable<float> { value = 3000f, parser = s => float.Parse(s) } }, // this is used for manual forward-style casts, but not for maintaining the lock
|
|
{ "tracking-clock", new Variable<int> { value = 1, parser = s => int.Parse(s) } },
|
|
{ "squared-offset-filter", new Variable<float> { value = 16f, parser = s => int.Parse(s) } } // "resolution" for ai-turret-added target offsets, squared (16 is 4m)
|
|
};
|
|
public static void Set(string key, string value) { v[key].Set(value); }
|
|
public static void Set<T>(string key, T value) { (v[key] as Variable<T>).value = value; }
|
|
public static T Get<T>(string key) { return (v[key] as Variable<T>).value; }
|
|
public interface ISettable
|
|
{
|
|
void Set(string v);
|
|
}
|
|
public class Variable<T> : ISettable
|
|
{
|
|
public T value;
|
|
public Func<string, T> parser;
|
|
public void Set(string v) { value = parser(v); }
|
|
}
|
|
}
|
|
|
|
public enum TargetSelection { First, Closest, Random, Loop }
|
|
public enum MultiTrackingSelector { Selected, AllOffsets, AllTargets, Everything }
|
|
#endregion
|
|
// further down code can be minified
|
|
|
|
// TODO: rumors say static refs are not released on script recompile, refactor to instance
|
|
|
|
class Toggle
|
|
{
|
|
Toggle() { }
|
|
Action<string> onToggleStateChangeHandler;
|
|
Dictionary<string, bool> sw;
|
|
Toggle(Dictionary<string, bool> switches, Action<string> handler)
|
|
{
|
|
onToggleStateChangeHandler = handler;
|
|
sw = switches;
|
|
}
|
|
|
|
public static Toggle C { get; private set; }
|
|
|
|
public static void Init(Dictionary<string, bool> switches, Action<string> handler)
|
|
{
|
|
if (C == null)
|
|
C = new Toggle(switches, handler);
|
|
}
|
|
|
|
public void Set(string key, bool value)
|
|
{
|
|
if (sw[key] != value)
|
|
Invert(key);
|
|
}
|
|
public void Invert(string key)
|
|
{
|
|
sw[key] = !sw[key];
|
|
onToggleStateChangeHandler(key);
|
|
}
|
|
public bool Check(string key)
|
|
{
|
|
return sw[key];
|
|
}
|
|
public ImmutableArray<MyTuple<string, string>> GetToggleCommands(string tmcNode)
|
|
{
|
|
return sw.Select(n => new MyTuple<string, string>($"{n.Key}: {(n.Value ? "on" : "off")}", $"[toggle:{n.Key}],[{tmcNode}]")).ToImmutableArray();
|
|
}
|
|
}
|
|
|
|
public Program()
|
|
{
|
|
Runtime.UpdateFrequency = UpdateFrequency.Update1;
|
|
|
|
Toggle.Init(new Dictionary<string, bool>
|
|
{
|
|
{ "pirate-scan", true },
|
|
{ "sweep", false },
|
|
{ "raycast-burst", true },
|
|
{ "stabilize", true },
|
|
{ "lock-friendlies", false },
|
|
},
|
|
key =>
|
|
{
|
|
switch (key)
|
|
{
|
|
|
|
}
|
|
}
|
|
);
|
|
|
|
this.commandRegistry = new CommandRegistry(
|
|
new Dictionary<string, Action<string[], object>> // <cmd>, <params, source>
|
|
{
|
|
{
|
|
"cast", (parts, src) => {
|
|
targeterRC.ForwardCast(null, parts.Length > 2 ? float.Parse(parts[2]) : 5f);
|
|
TTM.TrackerTurrets.ForEach(x => x.ClearUserShift());
|
|
}
|
|
},
|
|
{
|
|
"static-cast", (parts, src) => {
|
|
STPool.StaticCast(RCaster, MainCtrl, parts.Length > 2 ? int.Parse(parts[2]) : -1);
|
|
TTM.TrackerTurrets.ForEach(x => x.ClearUserShift());
|
|
}
|
|
},
|
|
{
|
|
"cycle-offset", (parts, src) =>
|
|
{
|
|
var t = GetTurretOrTscTarget(src);
|
|
if (t != null)
|
|
t.SelectedOffsetIndex = Tom.CycleOffset(t.Info.EntityId, t.SelectedOffsetIndex);
|
|
}
|
|
},
|
|
{
|
|
"tom-src", (parts, src) => Tom?.CycleSource()
|
|
},
|
|
{
|
|
"remove-offset", (parts, src) => {
|
|
var t = GetTurretOrTscTarget(src);
|
|
if (t != null)
|
|
Tom.RemoveOffset(t.Info.EntityId, t.SelectedOffsetIndex);
|
|
}
|
|
},
|
|
{
|
|
"next-target", (parts, src) => Tsc.CycleTarget(TTPool.Targets, false)
|
|
},
|
|
{
|
|
"prev-target", (parts, src) => Tsc.CycleTarget(TTPool.Targets, true)
|
|
},
|
|
{
|
|
"save-offsets", (parts, src) => TORepo.SaveCurrent(parts, Tsc.SelectedTrackedTarget?.Info.EntityId)
|
|
},
|
|
{
|
|
"set-target-def", (parts, src) => TORepo.SetTargetDef(parts)
|
|
},
|
|
{
|
|
"cycle-selector", (parts, src) => {
|
|
if (++GlobalSelector > MultiTrackingSelector.Everything)
|
|
GlobalSelector = MultiTrackingSelector.Selected;
|
|
}
|
|
},
|
|
{
|
|
"set-selector", (parts, src) => Enum.TryParse(parts[2], out GlobalSelector)
|
|
},
|
|
{
|
|
"set-offset-scan-designator-tag", (parts, src) => targeterTurret.ScanModeTurretTag = parts[2]
|
|
},
|
|
{
|
|
"set-offset-scan-min-d", (parts, src) => targeterTurret.ScanModeMinD = long.Parse(parts[2])
|
|
},
|
|
{
|
|
"set-offset-scan-min-size", (parts, src) => targeterTurret.ScanModeMinSize = long.Parse(parts[2])
|
|
},
|
|
{
|
|
"set-filter-deny-inertial", (parts, src) => TGatingComp.DenyInertial = bool.Parse(parts[2])
|
|
},
|
|
{
|
|
"set-filter-deny-smaller-than", (parts, src) => TGatingComp.DenySmallerThan = float.Parse(parts[2])
|
|
},
|
|
{
|
|
"set-filter-deny-missing-sphere", (parts, src) => TGatingComp.DenyMissingSphere = float.Parse(parts[2])
|
|
},
|
|
{
|
|
"set-filter-deny-wc-threat-lower-than", (parts, src) => TGatingComp.DenyWcThreatLowerThan = float.Parse(parts[2])
|
|
},
|
|
{
|
|
"probe-surface", (parts, src) =>
|
|
RCaster.ProbeSurfaceHandler(parts, (pos, norm) => {
|
|
var nStr = VectorOpsHelper.V3DtoBroadcastString(norm).Replace(':', ';');
|
|
var lTask = $"command:create-task:land:Name=Aligned Landing,AimNormal={nStr}:{VectorOpsHelper.V3DtoBroadcastString(pos)}";
|
|
UnicastCommandAutoPillock(lTask);
|
|
})
|
|
},
|
|
{
|
|
"recycle", (parts, src) => Recycle() // reset-trackers
|
|
},
|
|
{
|
|
"reset-rotors", (parts, src) => TTM.TrackerTurrets.ForEach(x => x.RequestReset())
|
|
},
|
|
{
|
|
"clear-user-shift", (parts, src) =>
|
|
{
|
|
if (src is TrackerTurret)
|
|
{
|
|
((TrackerTurret)src).ClearUserShift();
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"drop", (parts, src) => DropTarget(src, false)
|
|
},
|
|
{
|
|
"drop-and-ban", (parts, src) => DropTarget(src, true)
|
|
},
|
|
{
|
|
"clear-bans", (parts, src) => TGatingComp.ClearBans()
|
|
},
|
|
{
|
|
"set-value", (parts, src) => Variables.Set(parts[2], parts[3])
|
|
},
|
|
{
|
|
"allow-target-relation-radar", (parts, src) => targeterRadar?.AllowRadarRelation(parts[2])
|
|
},
|
|
{
|
|
"allow-target-relation", (parts, src) => targeterWc?.AllowWcRelation(parts[2])
|
|
},
|
|
{
|
|
"refresh-designators", (parts, src) =>
|
|
{
|
|
var localTurrets = new List<IMyLargeTurretBase>();
|
|
GridTerminalSystem.GetBlocksOfType(localTurrets, x => x.CubeGrid == Me.CubeGrid && x.CustomName.Contains("tgp"));
|
|
var localCTC = new List<IMyTurretControlBlock>();
|
|
GridTerminalSystem.GetBlocksOfType(localCTC, x => x.CubeGrid == Me.CubeGrid && x.CustomName.Contains("tgp"));
|
|
targeterTurret.AddDesignators(localTurrets, localCTC);
|
|
}
|
|
},
|
|
{
|
|
"get-toggles", (parts, src) => {
|
|
var tmcNode = string.Join(":", parts.Take(3));
|
|
IGC.SendUnicastMessage(long.Parse(parts[2]),
|
|
$"menucommand.get-commands.reply:{ tmcNode }",
|
|
Toggle.C.GetToggleCommands(tmcNode));
|
|
}
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
void DropTarget(object src, bool ban)
|
|
{
|
|
long? id = null;
|
|
if (src is TrackerTurret)
|
|
id = ((TrackerTurret)src).GetFocusedTargetId();
|
|
|
|
TrackedTarget toRemove = null;
|
|
if ((id == null) && (Tsc.SelectedTrackedTarget != null))
|
|
toRemove = Tsc.SelectedTrackedTarget;
|
|
else
|
|
toRemove = TTPool.GetById(id ?? -1);
|
|
|
|
if (toRemove != null)
|
|
{
|
|
toRemove.MarkedForRemoval = true;
|
|
TTM.TrackerTurrets.ForEach(x => x.ClearUserShift());
|
|
if (ban)
|
|
TGatingComp.BanPermanent(toRemove.Info.EntityId);
|
|
}
|
|
}
|
|
|
|
TrackedTarget GetTurretOrTscTarget(object src)
|
|
{
|
|
if (src is TrackerTurret)
|
|
{
|
|
var id = ((TrackerTurret)src).GetFocusedTargetId();
|
|
if (id.HasValue)
|
|
return TTPool.GetById(id.Value);
|
|
}
|
|
else if (Tsc.SelectedTrackedTarget != null)
|
|
return Tsc.SelectedTrackedTarget;
|
|
return null;
|
|
}
|
|
|
|
static void AddUniqueItem<T>(T item, IList<T> c) where T : class
|
|
{
|
|
if ((item != null) && !c.Contains(item))
|
|
c.Add(item);
|
|
}
|
|
|
|
IMyBlockGroup GetThisBlockGroup(IMyTerminalBlock block)
|
|
{
|
|
List<IMyBlockGroup> groups = new List<IMyBlockGroup>();
|
|
GridTerminalSystem.GetBlockGroups(groups);
|
|
return groups.Where(g => {
|
|
var bs = new List<IMyTerminalBlock>();
|
|
g.GetBlocksOfType(bs);
|
|
return bs.Contains(block);
|
|
}).FirstOrDefault();
|
|
}
|
|
|
|
List<T> GetCoreC<T>(List<IMyTerminalBlock> set, string n = null) where T : class, IMyTerminalBlock
|
|
{
|
|
E.Echo($"Looking for blocks ({typeof(T).Name})");
|
|
var f = set.Where(b => b is T && ((n == null) || b.CustomName.Contains(n))).Cast<T>().ToList();
|
|
return f;
|
|
}
|
|
|
|
void Recycle()
|
|
{
|
|
var gr = GetThisBlockGroup(Me);
|
|
if (gr == null)
|
|
{
|
|
Runtime.UpdateFrequency = UpdateFrequency.None;
|
|
Echo("Can't find hardware group containing this PB, stopping now.");
|
|
}
|
|
else
|
|
{
|
|
var blocks = new List<IMyTerminalBlock>();
|
|
gr.GetBlocks(blocks);
|
|
|
|
var controllers = GetCoreC<IMyShipController>(blocks);
|
|
UserCtrlListener = new InputListener(controllers, () => E.T);
|
|
|
|
// required
|
|
MainCtrl = controllers.FirstOrDefault(x => x.CubeGrid == Me.CubeGrid);
|
|
if (MainCtrl == null)
|
|
throw new Exception("Need a ship controller on the same grid/group as the PB");
|
|
|
|
// optional
|
|
var cams = GetCoreC<IMyCameraBlock>(blocks);
|
|
RCaster = new Raycaster(cams);
|
|
|
|
var tTurrets = GetCoreC<IMyLargeTurretBase>(blocks);
|
|
var tControllers = GetCoreC<IMyTurretControlBlock>(blocks);
|
|
|
|
var localTurrets = new List<IMyLargeTurretBase>();
|
|
GridTerminalSystem.GetBlocksOfType(localTurrets, x => x.CubeGrid == Me.CubeGrid && x.CustomName.Contains("tgp"));
|
|
tTurrets.AddRange(localTurrets);
|
|
|
|
var localCTC = new List<IMyTurretControlBlock>();
|
|
GridTerminalSystem.GetBlocksOfType(localCTC, x => x.CubeGrid == Me.CubeGrid && x.CustomName.Contains("tgp"));
|
|
tControllers.AddRange(localCTC);
|
|
|
|
TORepo = new TargetOffsetRepo(Tom, s => Storage = s);
|
|
TORepo.LoadFromStorage(Storage); // TODO: defer
|
|
Tom.OnAddedOffset += TORepo.ConsiderOffset;
|
|
TORepo.OnDefinitionMatched += Tom.ReplaceOffsetsForEntity;
|
|
|
|
TGatingComp = new TargetGatingComponent(MainCtrl);
|
|
|
|
Tsc = new TargetSelectorComponent(TORepo);
|
|
|
|
TTPool = new TrackedTargetsPool(TGatingComp);
|
|
|
|
targeterRC = new RaycastTrackerTargeter(RCaster, MainCtrl, Tom, Tsc, TTPool);
|
|
|
|
TTM = new TrackingTurretManager(MainCtrl, Tom);
|
|
|
|
var allStators = GetCoreC<IMyMotorStator>(blocks);
|
|
foreach (var trCam in cams.Where(x => x.CustomName.Contains("tracker")))
|
|
{
|
|
var topGrid = trCam.CubeGrid;
|
|
E.Echo($"Getting RC for tracker cam: {trCam.CustomName}");
|
|
var rc = GetCoreC<IMyRemoteControl>(blocks).Where(x => x.CubeGrid == topGrid).Single();
|
|
var stators = new List<IMyMotorStator>();
|
|
var stator1 = allStators.Where(x => x.TopGrid == topGrid).Single();
|
|
stators.Add(stator1);
|
|
var stator2 = allStators.Where(x => x.TopGrid == stator1.CubeGrid).Single();
|
|
if (stator2 != null)
|
|
stators.Add(stator2);
|
|
var turr = new TrackerTurret(stators, rc, trCam, UserCtrlListener, commandRegistry, keyUpBindings, dTapBindings);
|
|
turr.RequestReset();
|
|
TTM.TrackerTurrets.Add(turr);
|
|
}
|
|
|
|
targeterRC.OnCamRcSuccess = (cam, id, p) =>
|
|
{
|
|
foreach (var tracker in TTM.TrackerTurrets)
|
|
{
|
|
if ((tracker.Cam == cam) || (cam == null) && tracker.FacingPosition(p))
|
|
{
|
|
if (tracker.GetFocusedTargetId() != id)
|
|
tracker.FocusedTargetIds.Push(id);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
targeterTurret = new TurretAiTargeter(Tom, TTPool);
|
|
targeterTurret.AddDesignators(tTurrets, tControllers);
|
|
TORepo.OnDefinitionMatched += (id, offs) => targeterTurret.StopScan(id);
|
|
// redundant since hybrid target tracking?
|
|
targeterTurret.OnTargetEncountered += targeterRC.CheckEncounter;
|
|
|
|
caster = new TargetCasterP2P(IGC, processedTargets);
|
|
|
|
List<IMyProgrammableBlock> pbs = GetCoreC<IMyProgrammableBlock>(blocks);
|
|
outputSvcPb = pbs.FirstOrDefault(b => b.CustomName.Contains("a-hud-svc") && b.IsSameConstructAs(Me));
|
|
APckCoreId = pbs.FirstOrDefault(b => (b.CustomName.Contains("core") || b.CustomData.Contains("pillock-mode")) && b.IsSameConstructAs(Me))?.EntityId;
|
|
|
|
Targeters = new List<ITgpTargeter> { targeterRC, targeterTurret };
|
|
|
|
var dict = Me.GetProperty("WcPbAPI")?.As<IReadOnlyDictionary<string, Delegate>>().GetValue(Me);
|
|
if (dict?.ContainsKey("GetSortedThreatsByID") == true)
|
|
{
|
|
E.DebugLog("WC detected, disabling ITgpTargeter and TurretAiTargeter instances.");
|
|
//Targeters.Clear(); // TODO: is this still needed?
|
|
var wcDataProvider = dict["GetSortedThreatsByID"] as Action<IMyTerminalBlock, IDictionary<long, MyDetectedEntityInfo>>;
|
|
if (wcDataProvider != null)
|
|
{
|
|
E.DebugLog("Creating WcTargeter.");
|
|
targeterWc = new WcTargeter(TTPool, Tom, wcDataProvider, Me);
|
|
Targeters.Add(targeterWc);
|
|
}
|
|
}
|
|
PbNebRadarAPI RadarApi = new PbNebRadarAPI();
|
|
if (RadarApi.Load(Me))
|
|
{
|
|
E.DebugLog("Nebulous Radar detected.");
|
|
E.DebugLog("Creating RadarTargeter.");
|
|
targeterRadar = new RadarTargeter(TTPool, Tom, RadarApi, Me.CubeGrid);
|
|
Targeters.Add(targeterRadar);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(Me.CustomData))
|
|
pendingInitSequence = true;
|
|
}
|
|
|
|
// ctor ends
|
|
bool constructed;
|
|
bool pendingInitSequence;
|
|
|
|
IMyProgrammableBlock outputSvcPb;
|
|
long? APckCoreId;
|
|
|
|
IMyShipController MainCtrl;
|
|
|
|
CommandRegistry commandRegistry;
|
|
public class CommandRegistry
|
|
{
|
|
Dictionary<string, Action<string[], object>> commands;
|
|
public CommandRegistry(Dictionary<string, Action<string[], object>> commands)
|
|
{
|
|
this.commands = commands;
|
|
}
|
|
public void RunCommand(string id, string[] cmdParts, object source = null)
|
|
{
|
|
E.DebugLog($"Got cmd: {id}");
|
|
this.commands[id].Invoke(cmdParts, source);
|
|
}
|
|
}
|
|
|
|
TrackingTurretManager TTM;
|
|
public class TrackingTurretManager
|
|
{
|
|
public List<TrackerTurret> TrackerTurrets = new List<TrackerTurret>();
|
|
IMyShipController _ctrl;
|
|
TargetOffsetManager _tom;
|
|
|
|
public TrackingTurretManager(IMyShipController ctrl, TargetOffsetManager tom)
|
|
{
|
|
_ctrl = ctrl;
|
|
_tom = tom;
|
|
}
|
|
|
|
public void HandleTick()
|
|
{
|
|
Vector3D av = Toggle.C.Check("stabilize") ? _ctrl.GetShipVelocities().AngularVelocity : Vector3D.Zero;
|
|
foreach (var t in TrackerTurrets)
|
|
{
|
|
t.HandleTick(ref av);
|
|
}
|
|
}
|
|
|
|
public void OnTargetRootUpdated(TrackedTarget t)
|
|
{
|
|
for (int n = 0; n < TrackerTurrets.Count; n++)
|
|
{
|
|
if (TrackerTurrets[n].GetFocusedTargetId() == t.Info.EntityId)
|
|
{
|
|
TrackerTurrets[n].FocusedTargetPos = _tom.GetOffset(t.Info.EntityId, t.SelectedOffsetIndex, t.Info.Orientation, t.PredictedPosition);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnTargetRootRemoved(TrackedTarget tt)
|
|
{
|
|
for (int n = 0; n < TrackerTurrets.Count; n++)
|
|
{
|
|
if (TrackerTurrets[n].GetFocusedTargetId() == tt.Info.EntityId)
|
|
{
|
|
E.DebugLog($"TrackingTurretManager: Target root {tt.Info.EntityId} removed");
|
|
TrackerTurrets[n].FocusedTargetPos = null;
|
|
TrackerTurrets[n].FocusedTargetIds.Pop();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public class TrackerTurret
|
|
{
|
|
IMyRemoteControl _rc;
|
|
List<IMyMotorStator> _rotors;
|
|
InputListener _il;
|
|
public IMyCameraBlock Cam { get; private set; }
|
|
CommandRegistry _cmdR;
|
|
float[] prevCV;
|
|
Dictionary<string, string> _keyUpBindings;
|
|
Dictionary<string, string> _dTapBindings;
|
|
|
|
public TrackerTurret(List<IMyMotorStator> r, IMyRemoteControl rc, IMyCameraBlock cam, InputListener il,
|
|
CommandRegistry cmdR, Dictionary<string, string> keyUpBindings, Dictionary<string, string> dTapBindings)
|
|
{
|
|
_rotors = r;
|
|
_rc = rc;
|
|
_il = il;
|
|
Cam = cam;
|
|
prevCV = new float[r.Count];
|
|
_cmdR = cmdR;
|
|
_keyUpBindings = keyUpBindings;
|
|
_dTapBindings = dTapBindings;
|
|
}
|
|
|
|
//public long? FocusedTargetId;
|
|
public Stack<long> FocusedTargetIds = new Stack<long>();
|
|
public Vector3D? FocusedTargetPos;
|
|
|
|
public long? GetFocusedTargetId()
|
|
{
|
|
if (FocusedTargetIds.Count > 0)
|
|
return FocusedTargetIds.Peek();
|
|
return null;
|
|
}
|
|
|
|
public bool FacingPosition(Vector3D p)
|
|
{
|
|
return Vector3D.Dot(Cam.WorldMatrix.Forward, Vector3D.Normalize(p - Cam.WorldMatrix.Translation)) > 0.71f;
|
|
}
|
|
|
|
public void HandleTick(ref Vector3D angVel)
|
|
{
|
|
if (_resettingTstamp != 0)
|
|
Reset();
|
|
else
|
|
{
|
|
if (FocusedTargetPos.HasValue)
|
|
AimTo(FocusedTargetPos.Value);
|
|
else
|
|
{
|
|
var ctrl = angVel;
|
|
|
|
//E.Echo($"ctrl: {ctrl}");
|
|
|
|
if (_rc.IsUnderControl)
|
|
{
|
|
HandleWasd();
|
|
var r = _il.GetRot();
|
|
var wR = Vector3D.Rotate(new Vector3D(r.X, r.Y, 0), _rc.WorldMatrix);
|
|
//E.Echo($"wR: {wR}");
|
|
ctrl += wR / 10;
|
|
//E.Echo($"ctrl2: {ctrl}");
|
|
}
|
|
|
|
if (ctrl != Vector3D.Zero)
|
|
{
|
|
foreach (var stator in _rotors)
|
|
{
|
|
var inv = MatrixD.Transpose(stator.WorldMatrix);
|
|
stator.TargetVelocityRad = (float)Vector3D.Rotate(ctrl, inv).Y;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (var stator in _rotors)
|
|
stator.TargetVelocityRad = 0;
|
|
}
|
|
}
|
|
|
|
//if (FocusedTargetPos.HasValue)
|
|
// AimTo(FocusedTargetPos.Value);
|
|
//else
|
|
// RotateByInput();
|
|
}
|
|
}
|
|
|
|
void AimTo(Vector3D tPos)
|
|
{
|
|
var toTarget = tPos - Cam.GetPosition();
|
|
|
|
if (_rc.IsUnderControl)
|
|
{
|
|
HandleWasd();
|
|
|
|
var r = _il.GetRot();
|
|
_cumulative += r;
|
|
var adj = _cumulative;
|
|
|
|
float maxF = 1000;
|
|
Func<float, float> constrain = x =>
|
|
{
|
|
x = Math.Min(Math.Abs(x), maxF) * Math.Sign(x);
|
|
return x;
|
|
};
|
|
|
|
adj.X = constrain(adj.X);
|
|
adj.Y = constrain(adj.Y);
|
|
|
|
// we don't want the indicator to float away too far
|
|
_cumulative = adj;
|
|
// adjusted to degree max
|
|
//adj = adj / maxF;
|
|
adj = adj / 4;
|
|
|
|
//toTarget += _cam.WorldMatrix.Down * adj.X + _cam.WorldMatrix.Right * adj.Y;
|
|
|
|
// 0,785398 - 45 deg
|
|
var q1 = QuaternionD.CreateFromAxisAngle(_rc.WorldMatrix.Down, adj.Y / 250 * 0.785);
|
|
var q2 = QuaternionD.CreateFromAxisAngle(_rc.WorldMatrix.Left, adj.X / 250 * 0.785);
|
|
toTarget = Vector3D.Transform(toTarget, q1 * q2);
|
|
}
|
|
|
|
ControlRotors(toTarget);
|
|
}
|
|
|
|
Vector3D? fwCapture;
|
|
|
|
void RotateByInput()
|
|
{
|
|
//if (_rc.IsUnderControl)
|
|
//{
|
|
// HandleWasd();
|
|
|
|
// var r = _il.GetRot();
|
|
|
|
// var wm = Cam.WorldMatrix;
|
|
// if (fwCapture.HasValue)
|
|
// {
|
|
// Func<double, double> adjust = (x) => Math.Sign(x) * Math.Pow(x, 2);
|
|
// r.X = (float)adjust(r.X / 10);
|
|
// r.Y = (float)adjust(r.Y / 10);
|
|
// var fwUpdated = Vector3D.Normalize(fwCapture.Value * 1000 + wm.Down * r.X + wm.Right * r.Y);
|
|
// fwCapture = fwUpdated;
|
|
// ControlRotors(fwCapture.Value * 1000 + wm.Down * r.X + wm.Right * r.Y, false);
|
|
// }
|
|
// else
|
|
// {
|
|
// Func<double, double> adjust = (x) => Math.Sign(x) * Math.Pow(x, 2);
|
|
// ControlRotors(wm.Forward * 1000 + wm.Down * adjust(r.X) + wm.Right * adjust(r.Y), false);
|
|
// }
|
|
//}
|
|
}
|
|
|
|
void HandleWasd()
|
|
{
|
|
foreach (var keyUp in _keyUpBindings)
|
|
{
|
|
if (_il.KeyReleased(keyUp.Key))
|
|
{
|
|
string[] cmdParts = keyUp.Value.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
|
|
_cmdR.RunCommand(cmdParts[1], cmdParts, this);
|
|
}
|
|
}
|
|
foreach (var dTap in _dTapBindings)
|
|
{
|
|
if (_il.CheckDoubleTap(dTap.Key))
|
|
{
|
|
string[] cmdParts = dTap.Value.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
|
|
_cmdR.RunCommand(cmdParts[1], cmdParts, this);
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector2 _cumulative;
|
|
int _resettingTstamp;
|
|
public void Reset()
|
|
{
|
|
E.Echo($"Reset: {E.T - _resettingTstamp}");
|
|
if (E.T - _resettingTstamp < 120)
|
|
{
|
|
foreach (var r in _rotors)
|
|
{
|
|
r.UpperLimitRad = 0;
|
|
r.LowerLimitRad = 0;
|
|
//r.TargetVelocityRPM = r.Angle > 3.14 ? 60 : -60;
|
|
//var v = GetRotorAngleStatorZeroBased(r, false, r.WorldMatrix.Backward);
|
|
//r.TargetVelocityRad = Math.Sign(v) * 60;
|
|
if (r.Angle > 0 && r.Angle < Math.PI)
|
|
r.TargetVelocityRPM = -60;
|
|
else
|
|
r.TargetVelocityRPM = 60;
|
|
//r.TargetVelocityRPM = 60;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (var r in _rotors)
|
|
{
|
|
r.UpperLimitRad = float.MaxValue;
|
|
r.LowerLimitRad = float.MinValue;
|
|
r.TargetVelocityRPM = 0;
|
|
}
|
|
_cumulative = Vector2.Zero;
|
|
_resettingTstamp = 0;
|
|
FocusedTargetIds.Clear();
|
|
FocusedTargetPos = null;
|
|
}
|
|
}
|
|
|
|
public void RequestReset()
|
|
{
|
|
_resettingTstamp = E.T + 1;
|
|
}
|
|
|
|
public void ClearUserShift()
|
|
{
|
|
if (GetFocusedTargetId() != null)
|
|
_cumulative = Vector2.Zero;
|
|
else
|
|
{
|
|
if (!fwCapture.HasValue)
|
|
{
|
|
fwCapture = Cam.WorldMatrix.Forward;
|
|
}
|
|
else
|
|
{
|
|
fwCapture = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ControlRotors(Vector3D toTarget, bool adjustRotorSpeed = true)
|
|
{
|
|
int rotorIndex = 0;
|
|
foreach (var stator in _rotors)
|
|
{
|
|
var correction = GetRotorAngleStatorZeroBased(stator, false, toTarget);
|
|
|
|
var AlignMag = Math.Abs(correction);
|
|
var deltaS = AlignMag - prevCV[rotorIndex];
|
|
|
|
var maxFactor = 2.5f;
|
|
var curv = 2f;
|
|
Func<double, double, double> ampF = (x, d) => x * (Math.Exp(-d * curv) + 0.8) * maxFactor / 2;
|
|
|
|
if ((deltaS < 0.035) && (AlignMag > 0.0175)) // 2 and 1 grad
|
|
{
|
|
var raw = correction;
|
|
correction = (float)ampF(correction, deltaS);
|
|
}
|
|
|
|
prevCV[rotorIndex] = AlignMag;
|
|
rotorIndex++;
|
|
|
|
if (float.IsNaN(correction))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
float vel = correction;
|
|
var mag = Math.Abs(correction);
|
|
int gyroRPMlimit = 30;
|
|
double RPMtoRadsSec = 2 * Math.PI / 60f;
|
|
var radsPerTickMax = gyroRPMlimit * RPMtoRadsSec;
|
|
|
|
if (adjustRotorSpeed)
|
|
{
|
|
float intertLagC = 7; // 7
|
|
Func<double, double> rpmLimitSimple = x =>
|
|
{
|
|
var m = Math.Abs(x); // rads
|
|
if (m < 0.15)
|
|
intertLagC = 1f;
|
|
else if (m < 0.2)
|
|
intertLagC = 1f;
|
|
else if (m < 0.25)
|
|
intertLagC = 4f;
|
|
return m > radsPerTickMax * intertLagC ? gyroRPMlimit : m / (radsPerTickMax * intertLagC) * gyroRPMlimit;
|
|
};
|
|
vel = Math.Sign(correction) * (float)rpmLimitSimple(mag);
|
|
}
|
|
|
|
stator.TargetVelocityRad = vel;
|
|
stator.Enabled = true;
|
|
}
|
|
}
|
|
|
|
void AlignVtolRotors(List<IMyMotorStator> rotors, Vector3D toTarget, int clock)
|
|
{
|
|
//foreach (var stator in rotors)
|
|
//{
|
|
// var correction = GetRotorAngleStatorZeroBased(stator, false, toTarget);
|
|
// E.Echo($"{stator.CustomName} err: {correction:f4}");
|
|
// var mag = Math.Abs(correction);
|
|
// int gyroRPMlimit = 30;
|
|
// float adjustmentsPerSec = 60f / clock;
|
|
// float RPMtoRadsSec = 2 * (float)Math.PI / 60f;
|
|
// var radsPerTickMax = gyroRPMlimit * RPMtoRadsSec / clock;
|
|
|
|
// float intertLagC = Variables.Get<float>("inertia-gyro-divisor");
|
|
// Func<double, double> rpmLimitSimple = x =>
|
|
// {
|
|
// var m = Math.Abs(x); // rads
|
|
// return Math.Min(radsPerTickMax, x);
|
|
// };
|
|
|
|
// var vel = Math.Sign(correction) * (float)rpmLimitSimple(mag) / RPMtoRadsSec;
|
|
|
|
// stator.TargetVelocityRad = vel;
|
|
// stator.Enabled = true;
|
|
// stator.RotorLock = false;
|
|
//}
|
|
}
|
|
|
|
float GetRotorAngleStatorZeroBased(IMyMotorStator stator, bool hinge, Vector3D toTarget)
|
|
{
|
|
var planeNorm = hinge ? stator.WorldMatrix.Up : stator.WorldMatrix.Up;
|
|
var zeroAngeDir = hinge ? stator.WorldMatrix.Left : stator.WorldMatrix.Backward;
|
|
|
|
var proj = Vector3D.ProjectOnPlane(ref toTarget, ref planeNorm);
|
|
var angle = -(float)Math.Atan2(Vector3D.Dot(Vector3D.Cross(zeroAngeDir, proj), planeNorm),
|
|
Vector3D.Dot(zeroAngeDir, proj));
|
|
|
|
// 0-360
|
|
Func<float, float> roundAngle = a =>
|
|
{
|
|
while (a > 2 * (float)Math.PI)
|
|
a -= 2 * (float)Math.PI;
|
|
|
|
if (a < 0)
|
|
a = 2 * (float)Math.PI + a;
|
|
return a;
|
|
};
|
|
|
|
var diff = roundAngle(angle) - roundAngle(stator.Angle); // -6
|
|
var vel = (Math.Abs(diff) < Math.PI) ? diff : -diff; // v = 6
|
|
if (Math.Abs(vel) > Math.PI)
|
|
vel = -(Math.Abs(vel) - 2 * (float)Math.PI) * Math.Sign(vel);
|
|
|
|
return vel;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
List<IncomingMessage> batch = new List<IncomingMessage>();
|
|
List<MyIGCMessage> uniMsgs = new List<MyIGCMessage>();
|
|
void Main(string arg)
|
|
{
|
|
E.T++;
|
|
E.Dt = Math.Max(0.001, Runtime.TimeSinceLastRun.TotalSeconds);
|
|
|
|
// because ctr runs when PB comes into existance, even if turned off
|
|
if (!constructed)
|
|
{
|
|
E.Init(Echo, GridTerminalSystem, Me, IGC);
|
|
// because WC
|
|
if (E.T > 60)
|
|
{
|
|
Recycle();
|
|
constructed = true;
|
|
}
|
|
else
|
|
{
|
|
E.Echo("Waiting for init" + new string('.', E.T / 10));
|
|
return;
|
|
}
|
|
}
|
|
|
|
E.InfoOnTickStart?.Invoke();
|
|
E.LockOnTickStart?.Invoke();
|
|
E.Info?.Invoke(E.T + "");
|
|
UserCtrlListener.PrepareBeforeTick();
|
|
E.Echo($"TGP v.{Ver}");
|
|
E.Echo($"Selector: {GlobalSelector}");
|
|
E.Echo($"SelectedTarget: {Tsc.SelectedTrackedTarget?.Info.EntityId}");
|
|
|
|
TGatingComp.UpdateBefore();
|
|
|
|
batch.Clear();
|
|
|
|
try
|
|
{
|
|
uniMsgs.Clear();
|
|
while (IGC.UnicastListener.HasPendingMessage)
|
|
{
|
|
uniMsgs.Add(IGC.UnicastListener.AcceptMessage());
|
|
}
|
|
foreach (var m in uniMsgs)
|
|
{
|
|
metrics.RcvMsg++;
|
|
if (m.Tag == "apck.command")
|
|
batch.AddRange(ParseMessage(m.Data.ToString(), m.Source).ToList());
|
|
if (m.Tag == "apck-handshake")
|
|
{
|
|
IGC.SendUnicastMessage(m.Source, "apck-handshake-reply", "TGP");
|
|
APckCoreId = m.Source;
|
|
}
|
|
if (m.Tag == "apck-encounter")
|
|
{
|
|
var d = (MyTuple<long, Vector3D>)m.Data;
|
|
targeterRC.CheckEncounter(d.Item1, d.Item2);
|
|
}
|
|
}
|
|
|
|
if (pendingInitSequence && string.IsNullOrEmpty(arg))
|
|
{
|
|
pendingInitSequence = false;
|
|
var cmds = Me.CustomData.Trim('\n').Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).Where(s => !s.StartsWith("//")).Select(s => "[" + s + "]").ToList();
|
|
cmds.AddRange(Storage.Trim('\n').Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).Select(s => "[" + s + "]").ToList());
|
|
arg = string.Join(",", cmds);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(arg) && arg.Contains(":"))
|
|
{
|
|
metrics.RcvMsg++;
|
|
batch.AddRange(ParseMessage(arg).ToList());
|
|
}
|
|
|
|
if (batch.Count > 0)
|
|
{
|
|
foreach (var incomingMessage in batch)
|
|
{
|
|
string[] cmdParts = incomingMessage.Msg.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
|
|
if (cmdParts[0] == "toggle")
|
|
{
|
|
Toggle.C.Invert(cmdParts[1]);
|
|
}
|
|
if (cmdParts[0] == "command")
|
|
{
|
|
commandRegistry.RunCommand(cmdParts[1], cmdParts);
|
|
}
|
|
}
|
|
}
|
|
|
|
// update grid sense - gets GNP/tDesignator/Raycast
|
|
HandleGridSense();
|
|
E.Echo($"Targets available {TTPool.Targets.Count + STPool.Count} selected {processedTargets.Count}");
|
|
caster.Handle(uniMsgs);
|
|
|
|
TTM.HandleTick();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Runtime.UpdateFrequency = UpdateFrequency.None;
|
|
if (outputSvcPb != null)
|
|
IGC.SendUnicastMessage(outputSvcPb.EntityId, "persist-text",
|
|
new MyTuple<string, Vector2, float>(
|
|
$"TGP FAILURE see \"{MainCtrl.CustomName}\" CustomData",
|
|
new Vector2(0f, 0.4f),
|
|
0.8f
|
|
));
|
|
MainCtrl.CustomData = ex.ToString();
|
|
E.DebugLog(ex.ToString());
|
|
throw ex;
|
|
}
|
|
|
|
E.Echo($"Tracker turrets: {TTM.TrackerTurrets.Count}");
|
|
E.Echo($"CTC/turret designators: {targeterTurret.Count}");
|
|
|
|
E.Echo("ParseVectorsCount: " + metrics.ParseVectorsCount);
|
|
E.Echo("ScanBurstsCount: " + metrics.ScanBurstsCount);
|
|
|
|
RCaster.UpdateAfter(E.T);
|
|
STPool.UpdateAfter(E.T);
|
|
STPool.Sweep(MainCtrl, RCaster, Runtime, TTPool);
|
|
E.Echo($"Total range: {RCaster.Resource / 1000:f2} km");
|
|
E.Echo("Total cams: " + RCaster.CamsCount);
|
|
|
|
Scheduler.C.HandleTick();
|
|
E.EndOfTick();
|
|
|
|
E.Echo($"CurrentInstructionCount: {Runtime.CurrentInstructionCount}");
|
|
E.Echo("Processed in " + Runtime.LastRunTimeMs.ToString("f3") + " ms");
|
|
}
|
|
|
|
|
|
TargetCasterP2P caster;
|
|
public class TargetCasterP2P
|
|
{
|
|
IMyIntergridCommunicationSystem _igc;
|
|
IMyBroadcastListener _b;
|
|
IMyBroadcastListener _bWho;
|
|
IMyBroadcastListener _bWhoPredicate;
|
|
List<TargetLite> _targets;
|
|
|
|
public TargetCasterP2P(IMyIntergridCommunicationSystem igc, List<TargetLite> tgts)
|
|
{
|
|
_igc = igc;
|
|
_b = igc.RegisterBroadcastListener("apck.unicast.closed"); // key
|
|
_bWho = igc.RegisterBroadcastListener("apck.unicast.whohas"); // key, dataId
|
|
_bWhoPredicate = igc.RegisterBroadcastListener("apck.unicast.whohas+predicate"); // key, TargetSelection, pos, dir, radius, callback
|
|
_targets = tgts;
|
|
|
|
Init();
|
|
}
|
|
|
|
Dictionary<long, HashSet<long>> _subscriptions = new Dictionary<long, HashSet<long>>();
|
|
|
|
List<TargetLite> _selectionBuffer = new List<TargetLite>();
|
|
Random _r = new Random();
|
|
int _ctr;
|
|
|
|
List<MyTuple<MyTuple<string, long, byte, byte>, Vector3D, Vector3D, MatrixD, BoundingBoxD>> _broadRangeBuffer =
|
|
new List<MyTuple<MyTuple<string, long, byte, byte>, Vector3D, Vector3D, MatrixD, BoundingBoxD>>();
|
|
|
|
Dictionary<long, List<MyTuple<MyTuple<string, long, byte, byte>, Vector3D, Vector3D, MatrixD, BoundingBoxD>>> _subBatchBuffer =
|
|
new Dictionary<long, List<MyTuple<MyTuple<string, long, byte, byte>, Vector3D, Vector3D, MatrixD, BoundingBoxD>>>();
|
|
|
|
List<MyTuple<long, Vector3D>> _offersBuffer = new List<MyTuple<long, Vector3D>>();
|
|
|
|
void Init()
|
|
{
|
|
var tgp = new UniSubscribeProxy();
|
|
tgp.AddSub = (src, id) =>
|
|
{
|
|
if (!_subscriptions.ContainsKey(id))
|
|
_subscriptions.Add(id, new HashSet<long>());
|
|
_subscriptions[id].Add(src);
|
|
};
|
|
tgp.RemoveSub = (src, id) =>
|
|
{
|
|
if (id == 0)
|
|
{
|
|
E.DebugLog($"Removing all subscriptions for consumer {src}");
|
|
foreach (var s in _subscriptions)
|
|
s.Value.Remove(src);
|
|
}
|
|
else
|
|
{
|
|
E.DebugLog($"Removing consumer {src} for target {id}");
|
|
if (_subscriptions.ContainsKey(id))
|
|
_subscriptions[id].Remove(src);
|
|
}
|
|
};
|
|
tgp.WhoHas = (id) => _targets.Any(x => x.Id == id);
|
|
|
|
tgp.WhoHasFiltered = (typeF, s, p, dir, r) =>
|
|
{
|
|
var ts = (TargetSelection)s;
|
|
_selectionBuffer.Clear();
|
|
foreach (var t in _targets)
|
|
{
|
|
bool locPass = t.Position.HasValue && FilterLoc(p, dir, r, t.Position.Value);
|
|
if (locPass && (typeF == null || (t.Mdei.Value.Type.ToString() == typeF)))
|
|
_selectionBuffer.Add(t);
|
|
}
|
|
if (_selectionBuffer.Count > 0)
|
|
{
|
|
if (ts == TargetSelection.Closest)
|
|
return _selectionBuffer.OrderBy(x => (p - x.Position.Value).LengthSquared()).First().Id;
|
|
else
|
|
{
|
|
int i = 0;
|
|
if (ts == TargetSelection.Random)
|
|
i = _r.Next(_selectionBuffer.Count);
|
|
if (ts == TargetSelection.Loop)
|
|
i = _ctr++ % _selectionBuffer.Count;
|
|
return _selectionBuffer[i].Id;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
tgp.Send = () =>
|
|
{
|
|
_broadRangeBuffer.Clear();
|
|
foreach (var list in _subBatchBuffer.Values) // TODO: slight leak by not cleaning on unsub
|
|
list.Clear();
|
|
_offersBuffer.Clear();
|
|
|
|
foreach (var tt in _targets)
|
|
{
|
|
_offersBuffer.Add(new MyTuple<long, Vector3D>(tt.Id, tt.Position.Value));
|
|
if (_subscriptions.ContainsKey(tt.Id))
|
|
{
|
|
var subs = _subscriptions[tt.Id];
|
|
if (subs.Count > 0)
|
|
{
|
|
var dto = tt.GetIgcDto();
|
|
foreach (var s in subs)
|
|
{
|
|
if (!_subBatchBuffer.ContainsKey(s))
|
|
_subBatchBuffer.Add(s, new List<MyTuple<MyTuple<string, long, byte, byte>, Vector3D, Vector3D, MatrixD, BoundingBoxD>>());
|
|
_subBatchBuffer[s].Add(dto);
|
|
}
|
|
}
|
|
else
|
|
_subscriptions.Remove(tt.Id);
|
|
}
|
|
|
|
// TODO: avoid if no active listener available
|
|
// this is for Commander exclusively as it does not do target+ subscription yet
|
|
_broadRangeBuffer.Add(tt.GetIgcDto());
|
|
}
|
|
|
|
foreach (var pair in _subBatchBuffer)
|
|
{
|
|
if (pair.Value.Count > 1)
|
|
_igc.SendUnicastMessage(pair.Key, "tgp.local.gridsense.update", pair.Value.ToImmutableArray());
|
|
else if (pair.Value.Count > 0)
|
|
_igc.SendUnicastMessage(pair.Key, "tgp.local.gridsense.update", pair.Value[0]);
|
|
}
|
|
|
|
if (_offersBuffer.Count > 1)
|
|
_igc.SendBroadcastMessage("tgp.global.gridsense.offer", _offersBuffer.ToImmutableArray());
|
|
else
|
|
foreach (var targetOffer in _offersBuffer)
|
|
_igc.SendBroadcastMessage("tgp.global.gridsense.offer", targetOffer);
|
|
|
|
|
|
// TODO: avoid if no active listener available
|
|
// this is for Commander exclusively as it does not do target+ subscription yet
|
|
if (_broadRangeBuffer.Count > 0)
|
|
_igc.SendBroadcastMessage("tgp.global.gridsense.batch", _broadRangeBuffer.ToImmutableArray());
|
|
};
|
|
|
|
_proxies.Add("tgp.local.gridsense.update", tgp);
|
|
}
|
|
public void Handle(List<MyIGCMessage> msgs)
|
|
{
|
|
HandleProxies(msgs);
|
|
}
|
|
|
|
public class UniSubscribeProxy
|
|
{
|
|
public Action<long, long> RemoveSub;
|
|
public Func<long, bool> WhoHas;
|
|
public Func<string, byte, Vector3D, Vector3D, float, long> WhoHasFiltered;
|
|
public Action<long, long> AddSub;
|
|
public Action Send;
|
|
}
|
|
|
|
Dictionary<string, UniSubscribeProxy> _proxies = new Dictionary<string, UniSubscribeProxy>();
|
|
|
|
public static bool FilterLoc(Vector3D p, Vector3D dir, float r, Vector3D tP)
|
|
{
|
|
bool b = false;
|
|
if (dir != Vector3D.Zero)
|
|
{
|
|
if (Vector3D.Dot(dir, Vector3D.Normalize(tP - p)) > r)
|
|
b = true;
|
|
}
|
|
else
|
|
{
|
|
if (p != Vector3D.Zero)
|
|
{
|
|
if (Vector3D.DistanceSquared(tP, p) < r * r)
|
|
b = true;
|
|
}
|
|
else
|
|
b = true;
|
|
}
|
|
return b;
|
|
}
|
|
|
|
public void HandleProxies(List<MyIGCMessage> msgs)
|
|
{
|
|
while (_b.HasPendingMessage)
|
|
{
|
|
var m = _b.AcceptMessage();
|
|
var sKey = (string)m.Data;
|
|
// can be used in many emitters and the stuff is not guaranteed, while closer channel is shared
|
|
if (_proxies.ContainsKey(sKey))
|
|
_proxies[sKey].RemoveSub.Invoke(m.Source, 0);
|
|
}
|
|
|
|
while (_bWho.HasPendingMessage)
|
|
{
|
|
var m = _bWho.AcceptMessage();
|
|
var d = (MyTuple<string, long>)m.Data;
|
|
if (_proxies.ContainsKey(d.Item1) && _proxies[d.Item1].WhoHas.Invoke(d.Item2))
|
|
_igc.SendUnicastMessage(m.Source, "apck.unicast.ihave", d);
|
|
}
|
|
|
|
while (_bWhoPredicate.HasPendingMessage)
|
|
{
|
|
var m = _bWhoPredicate.AcceptMessage();
|
|
// channelKey, callback, typeFilter, selector, pos, dir, r
|
|
var d = (MyTuple<string, string, MyTuple<string, byte, Vector3D, Vector3D, float>>)m.Data;
|
|
if (_proxies.ContainsKey(d.Item1) && _proxies[d.Item1].WhoHasFiltered != null)
|
|
{
|
|
E.DebugLog($"{d.Item1}: whohas with predicate");
|
|
var id = _proxies[d.Item1].WhoHasFiltered.Invoke(d.Item3.Item1, d.Item3.Item2, d.Item3.Item3, d.Item3.Item4, d.Item3.Item5);
|
|
if (id > 0)
|
|
{
|
|
E.DebugLog($"Offering target {id} to {m.Source} with callback {d.Item2}");
|
|
_igc.SendUnicastMessage(m.Source, "apck.unicast.ihave+callback", new MyTuple<long, string>(id, d.Item2));
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var m in msgs)
|
|
{
|
|
if (m.Tag == "apck.unicast.t+.batch")
|
|
{
|
|
foreach (var sub in (ImmutableArray<MyTuple<string, long>>)m.Data)
|
|
UpdateSub(false, m.Source, sub);
|
|
}
|
|
else if (m.Tag.Contains("apck.unicast.t"))
|
|
{
|
|
var d = (MyTuple<string, long>)m.Data;
|
|
if (m.Tag == "apck.unicast.t+")
|
|
UpdateSub(false, m.Source, d);
|
|
if (m.Tag == "apck.unicast.t-")
|
|
UpdateSub(true, m.Source, d);
|
|
}
|
|
}
|
|
|
|
foreach (var x in _proxies)
|
|
x.Value.Send();
|
|
}
|
|
|
|
void UpdateSub(bool remove, long src, MyTuple<string, long> d)
|
|
{
|
|
E.DebugLog($"{src} {(remove ? "t-" : "t+")} request for the target id {d.Item2} ({d.Item1})");
|
|
var sKey = d.Item1;
|
|
var dataId = d.Item2;
|
|
if (_proxies.ContainsKey(sKey))
|
|
{
|
|
if (remove)
|
|
_proxies[sKey].RemoveSub.Invoke(src, dataId);
|
|
else
|
|
_proxies[sKey].AddSub.Invoke(src, dataId);
|
|
}
|
|
}
|
|
}
|
|
|
|
long lastEmittedMsgId;
|
|
Vector3D? _gsPrevPos;
|
|
int _gsPrevPosStamp;
|
|
|
|
HashSet<long> uniq_set = new HashSet<long>();
|
|
|
|
List<TargetLite> processedTargets = new List<TargetLite>();
|
|
|
|
List<ITgpTargeter> Targeters;
|
|
|
|
public interface ITgpTargeter
|
|
{
|
|
//bool HasTarget { get; }
|
|
//void Update(int tick);
|
|
//List<TargetLite> Results { get; }
|
|
string GetTypeName { get; }
|
|
|
|
}
|
|
|
|
void HandleGridSense()
|
|
{
|
|
uniq_set.Clear();
|
|
processedTargets.Clear();
|
|
|
|
if (Toggle.C.Check("pirate-scan") && (MainCtrl as IMyRemoteControl != null))
|
|
{
|
|
Vector3D pPos = Vector3D.Zero;
|
|
if (((IMyRemoteControl)MainCtrl).GetNearestPlayer(out pPos))
|
|
{
|
|
var result = new TargetLite
|
|
{
|
|
Id = 100,
|
|
Name = "sprt-gnp"
|
|
};
|
|
|
|
if (_gsPrevPos.HasValue)
|
|
result.Velocity = (pPos - _gsPrevPos.Value) / ((E.T - _gsPrevPosStamp) / 60f); // normalize time
|
|
else
|
|
result.Velocity = Vector3D.Zero;
|
|
|
|
_gsPrevPos = pPos;
|
|
_gsPrevPosStamp = E.T;
|
|
|
|
result.Position = pPos;
|
|
|
|
processedTargets.Add(result);
|
|
}
|
|
else
|
|
_gsPrevPos = null;
|
|
}
|
|
|
|
|
|
TTPool.ToRemove.Clear();
|
|
foreach (var t in TTPool.Targets.Values)
|
|
{
|
|
if (TGatingComp.ConsiderBanning(t) || (E.T - t.TickStamp > 300))
|
|
t.MarkedForRemoval = true;
|
|
if (t.MarkedForRemoval)
|
|
{
|
|
E.DebugLog($"Target {t.Info.EntityId} is being removed");
|
|
TTM.OnTargetRootRemoved(t);
|
|
TTPool.ToRemove.Add(t.Info.EntityId);
|
|
}
|
|
else
|
|
{
|
|
t.Integrate();
|
|
t.Approved = true;
|
|
TTM.OnTargetRootUpdated(t);
|
|
}
|
|
}
|
|
foreach (var id in TTPool.ToRemove)
|
|
{
|
|
TTPool.Targets.Remove(id);
|
|
}
|
|
|
|
targeterTurret.Update(E.T, TGatingComp); // OnTargetEncountered invocation for new targets
|
|
|
|
targeterWc?.Update(E.T, TGatingComp);
|
|
|
|
targeterRadar?.Update(E.T, TGatingComp);
|
|
|
|
TTPool.ToAdd.Clear();
|
|
foreach (var t in TTPool.Targets.Values)
|
|
{
|
|
if (t.TickStamp != E.T)
|
|
targeterRC.Update(t);
|
|
if (E.T - t.TickStamp > 20)
|
|
{
|
|
// rc recovery
|
|
var p = t.GetRecastPosition();
|
|
var cam = RCaster.GetCamForPos(p);
|
|
if (cam != null)
|
|
{
|
|
var res = RCaster.CastBurst(p, cam, (float)(p - cam.GetPosition()).Length(), (float)t.Info.BoundingBox.HalfExtents.Length(), t.Info.EntityId);
|
|
if (res.Count == 0)
|
|
t.MarkedForRemoval = true;
|
|
else
|
|
TTPool.ToAdd.AddRange(res);
|
|
metrics.ScanBurstsCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var cast in TTPool.ToAdd)
|
|
{
|
|
TTPool.AddTrackedTarget(cast, targeterRC.GetTypeName);
|
|
}
|
|
|
|
foreach (var t in Tsc.GetTargets(TTPool.Targets, Tom, GlobalSelector))
|
|
{
|
|
if (uniq_set.Add(t.Id)) // not needed?
|
|
processedTargets.Add(t);
|
|
}
|
|
|
|
STPool.PopulateTargets(processedTargets);
|
|
}
|
|
|
|
|
|
|
|
TrackedTargetsPool TTPool;
|
|
public class TrackedTargetsPool
|
|
{
|
|
public Dictionary<long, TrackedTarget> Targets = new Dictionary<long, TrackedTarget>();
|
|
public List<MyDetectedEntityInfo> ToAdd = new List<MyDetectedEntityInfo>();
|
|
public HashSet<long> ToRemove = new HashSet<long>();
|
|
TargetGatingComponent _tgc;
|
|
|
|
public TrackedTargetsPool(TargetGatingComponent tgc)
|
|
{
|
|
_tgc = tgc;
|
|
}
|
|
|
|
public TrackedTarget GetById(long id)
|
|
{
|
|
TrackedTarget val;
|
|
Targets.TryGetValue(id, out val);
|
|
return val;
|
|
}
|
|
|
|
public TrackedTarget AddTrackedTarget(MyDetectedEntityInfo mdei, string source)
|
|
{
|
|
var tt = GetById(mdei.EntityId);
|
|
if (tt == null)
|
|
{
|
|
if (!_tgc.ConsiderAddTarget(ref mdei))
|
|
return null;
|
|
|
|
// if the target Position is in empty space later then fallback to hit position
|
|
// 0.9f for burying a bit from HP into BB center
|
|
var baseOffset = Vector3D.Rotate((mdei.HitPosition.Value - mdei.Position) * 0.9f, MatrixD.Transpose(mdei.Orientation));
|
|
E.DebugLog($"Added target {mdei.EntityId}");
|
|
tt = new TrackedTarget(mdei, E.T, baseOffset, source);
|
|
Targets.Add(mdei.EntityId, tt);
|
|
// reset turret offset?
|
|
}
|
|
else
|
|
tt.Actualize(mdei, E.T);
|
|
|
|
return tt;
|
|
}
|
|
}
|
|
|
|
public class TrackedTarget
|
|
{
|
|
public MyDetectedEntityInfo Info { get; private set; }
|
|
public Vector3D PredictedPosition;
|
|
Vector3D _baseOffset;
|
|
public bool UsingOffset;
|
|
public int TickStamp;
|
|
public bool MarkedForRemoval;
|
|
public bool Approved;
|
|
public int SelectedOffsetIndex;
|
|
public float? ThreatScore;
|
|
public string Source { get; private set; }
|
|
|
|
public TrackedTarget(MyDetectedEntityInfo info, int tick, Vector3D baseOffset, string source)
|
|
{
|
|
_baseOffset = baseOffset;
|
|
Source = source;
|
|
Actualize(info, tick);
|
|
}
|
|
public void Actualize(MyDetectedEntityInfo info, int tick)
|
|
{
|
|
Info = info;
|
|
PredictedPosition = info.Position;
|
|
TickStamp = tick;
|
|
}
|
|
public Vector3D GetRecastPosition()
|
|
{
|
|
if (UsingOffset)
|
|
return PredictedPosition + Vector3D.Rotate(_baseOffset, Info.Orientation);
|
|
else
|
|
return PredictedPosition;
|
|
}
|
|
public void Integrate()
|
|
{
|
|
PredictedPosition += Info.Velocity / 60f;
|
|
}
|
|
|
|
Vector3D _wbPrevVel;
|
|
int _wbCtr;
|
|
public bool IsInertial(ref Vector3D nG)
|
|
{
|
|
if (Info.Velocity != Vector3D.Zero)
|
|
{
|
|
var acc = (Info.Velocity - _wbPrevVel) * 60;
|
|
if ((acc == Vector3D.Zero) || (nG == acc))
|
|
{
|
|
_wbCtr++;
|
|
}
|
|
if (_wbCtr > 10)
|
|
{
|
|
E.DebugLog($"{Source}/{Info.EntityId}: inert filter fails");
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_wbCtr = 0;
|
|
}
|
|
_wbPrevVel = Info.Velocity;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
StaticTargetsPool STPool = new StaticTargetsPool();
|
|
public class StaticTargetsPool
|
|
{
|
|
List<StaticTarget> Targets = new List<StaticTarget>();
|
|
long _idCtr = 9000;
|
|
public int Count => Targets.Count;
|
|
public void StaticCast(Raycaster caster, IMyShipController ctrl, int expiration)
|
|
{
|
|
Vector3D pt;
|
|
if (caster.GetFwCastPosition(null, ctrl, out pt, 500))
|
|
{
|
|
var cam = caster.GetCamForPos(pt);
|
|
if (cam != null)
|
|
{
|
|
var hit = cam.Raycast(pt);
|
|
if (hit.HitPosition.HasValue)
|
|
{
|
|
Targets.Add(new StaticTarget { Mdei = hit, Name = "stp", Pos = hit.HitPosition.Value, Id = _idCtr++, ExpiresIn = expiration });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void PopulateTargets(List<TargetLite> output)
|
|
{
|
|
foreach (var t in Targets)
|
|
{
|
|
output.Add(new TargetLite { Mdei = t.Mdei, Name = t.Name, Position = t.Pos, Id = t.Id });
|
|
}
|
|
}
|
|
|
|
public void UpdateAfter(int tick)
|
|
{
|
|
foreach (var t in Targets)
|
|
{
|
|
t.Integrate(tick);
|
|
}
|
|
|
|
Targets.RemoveAll(x => x.MarkedForRemoval);
|
|
}
|
|
|
|
int _col = -1;
|
|
int _row = 0;
|
|
public void Sweep(IMyShipController ctrl, Raycaster caster, IMyGridProgramRuntimeInfo info, TrackedTargetsPool ttPool)
|
|
{
|
|
if (Toggle.C.Check("sweep"))
|
|
{
|
|
_col = 0;
|
|
Toggle.C.Set("sweep", false);
|
|
}
|
|
if (_col != -1)
|
|
{
|
|
var wm = ctrl.WorldMatrix;
|
|
var bpos = wm.Translation + wm.Forward * 3000;
|
|
var x = wm.Left * (100 - _col) * 7;
|
|
|
|
while (info.CurrentInstructionCount < 40000)
|
|
{
|
|
var y = wm.Up * (_row - 50) * 7;
|
|
var pos = bpos + x + y;
|
|
// TODO: store last found index to cope with large cam numbers
|
|
var cam = caster.GetCamForPos(pos);
|
|
if (cam != null)
|
|
{
|
|
E.Draw(pos, "Circle", "ff00aa", 2, null);
|
|
|
|
var hit = cam.Raycast(pos);
|
|
if (hit.HitPosition.HasValue)
|
|
{
|
|
Targets.Add(new StaticTarget { Mdei = hit, Name = "stp", Pos = hit.HitPosition.Value, Id = _idCtr++ });
|
|
//ttPool.AddTrackedTarget(hit, "sweep");
|
|
}
|
|
}
|
|
_row++;
|
|
}
|
|
|
|
if (_row > 100)
|
|
{
|
|
_row = 0;
|
|
_col++;
|
|
}
|
|
if (_col > 199)
|
|
_col = -1;
|
|
}
|
|
}
|
|
|
|
class StaticTarget
|
|
{
|
|
public long Id;
|
|
public Vector3D Pos;
|
|
public MyDetectedEntityInfo Mdei;
|
|
public string Name;
|
|
public int ExpiresIn = -1;
|
|
public bool MarkedForRemoval;
|
|
public void Integrate(int tick)
|
|
{
|
|
if (ExpiresIn != -1)
|
|
{
|
|
ExpiresIn--;
|
|
if (ExpiresIn == 0)
|
|
MarkedForRemoval = true;
|
|
}
|
|
|
|
Pos += Mdei.Velocity / 60f;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
TargetSelectorComponent Tsc;
|
|
MultiTrackingSelector GlobalSelector = MultiTrackingSelector.Everything;
|
|
|
|
public class TargetSelectorComponent
|
|
{
|
|
int SelectedTargetIndex;
|
|
public TrackedTarget SelectedTrackedTarget;
|
|
List<TrackedTarget> _validTargets = new List<TrackedTarget>();
|
|
List<TargetLite> _results = new List<TargetLite>();
|
|
TargetOffsetRepo _tofRepo;
|
|
public TargetSelectorComponent(TargetOffsetRepo tofRepo)
|
|
{
|
|
_tofRepo = tofRepo;
|
|
}
|
|
public List<TargetLite> GetTargets(Dictionary<long, TrackedTarget> _trackedTargets, TargetOffsetManager _tom, MultiTrackingSelector selector)
|
|
{
|
|
SelectedTrackedTarget = null;
|
|
_results.Clear();
|
|
_validTargets.Clear();
|
|
//E.Echo($"TSC si: {SelectedTargetIndex}");
|
|
|
|
int i = 0;
|
|
foreach (var tt in _trackedTargets.Values)
|
|
{
|
|
if (tt.Approved)
|
|
_validTargets.Add(tt);
|
|
if (SelectedTargetIndex == i)
|
|
{
|
|
if (!tt.MarkedForRemoval)
|
|
SelectedTrackedTarget = tt;
|
|
else
|
|
SelectedTargetIndex = 0;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
switch (selector)
|
|
{
|
|
case MultiTrackingSelector.Everything:
|
|
foreach (var tt in _validTargets)
|
|
{
|
|
int offset_Id_suffix = 0;
|
|
foreach (var off in _tom.GetWorldOffsets(tt.Info.EntityId,
|
|
tt.Info.Orientation, tt.PredictedPosition))
|
|
{
|
|
offset_Id_suffix++;
|
|
var tl = new TargetLite
|
|
{
|
|
Mdei = tt.Info,
|
|
Id = tt.Info.EntityId + offset_Id_suffix,
|
|
Name = offset_Id_suffix == 1 ? "tgp" : null,
|
|
Position = off,
|
|
Velocity = tt.Info.Velocity
|
|
};
|
|
|
|
if (offset_Id_suffix == 1)
|
|
_tofRepo.AddMeta(ref tl, tt.Info.EntityId);
|
|
_results.Add(tl);
|
|
}
|
|
}
|
|
break;
|
|
case MultiTrackingSelector.AllTargets:
|
|
foreach (var tt in _validTargets)
|
|
{
|
|
var tl = new TargetLite
|
|
{
|
|
Mdei = tt.Info,
|
|
Id = tt.Info.EntityId,
|
|
Name = "tgp",
|
|
Position = tt.Info.Position,
|
|
Velocity = tt.Info.Velocity
|
|
};
|
|
_tofRepo.AddMeta(ref tl, tt.Info.EntityId);
|
|
_results.Add(tl);
|
|
}
|
|
break;
|
|
case MultiTrackingSelector.Selected:
|
|
if (_validTargets.Count > 0)
|
|
{
|
|
var offIndex = Math.Min(_validTargets.Count - 1, SelectedTargetIndex);
|
|
var t = _validTargets.ElementAt(offIndex);
|
|
var tl = new TargetLite
|
|
{
|
|
Mdei = t.Info,
|
|
Id = t.Info.EntityId,
|
|
Name = "tgp",
|
|
Position = t.Info.Position,
|
|
Velocity = t.Info.Velocity
|
|
};
|
|
|
|
tl.Position = _tom.GetOffset(tl.Id, t.SelectedOffsetIndex, t.Info.Orientation, t.PredictedPosition);
|
|
|
|
_tofRepo.AddMeta(ref tl, t.Info.EntityId);
|
|
_results.Add(tl);
|
|
}
|
|
break;
|
|
case MultiTrackingSelector.AllOffsets:
|
|
if (_validTargets.Count > 0)
|
|
{
|
|
var offIndex = Math.Min(_validTargets.Count - 1, SelectedTargetIndex);
|
|
var t = _validTargets.ElementAt(offIndex);
|
|
|
|
var tBase = new TargetLite
|
|
{
|
|
Mdei = t.Info,
|
|
Id = t.Info.EntityId,
|
|
Name = "tgp",
|
|
Position = t.GetRecastPosition(),
|
|
Velocity = t.Info.Velocity
|
|
};
|
|
_tofRepo.AddMeta(ref tBase, t.Info.EntityId);
|
|
_results.Add(tBase);
|
|
|
|
int offset_Id_suffix = 0;
|
|
foreach (var off in _tom.GetWorldOffsets(t.Info.EntityId,
|
|
t.Info.Orientation, t.PredictedPosition))
|
|
{
|
|
offset_Id_suffix++;
|
|
var tl = new TargetLite
|
|
{
|
|
Mdei = t.Info,
|
|
Id = t.Info.EntityId + offset_Id_suffix,
|
|
Position = off,
|
|
Velocity = t.Info.Velocity
|
|
};
|
|
_results.Add(tl);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return _results;
|
|
}
|
|
|
|
public void SetIndex(int i)
|
|
{
|
|
SelectedTargetIndex = i;
|
|
}
|
|
|
|
public void CycleTarget(Dictionary<long, TrackedTarget> _trackedTargets, bool reverse)
|
|
{
|
|
var count = _trackedTargets.Values.Count(x => !x.MarkedForRemoval);
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
if (i == SelectedTargetIndex)
|
|
{
|
|
if (reverse)
|
|
{
|
|
var newInd = i - 1;
|
|
if (newInd < 0)
|
|
newInd = count - 1;
|
|
SelectedTargetIndex = newInd;
|
|
}
|
|
else
|
|
{
|
|
var newInd = i + 1;
|
|
if (newInd >= count)
|
|
newInd = 0;
|
|
SelectedTargetIndex = newInd;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
SelectedTargetIndex = 0;
|
|
}
|
|
}
|
|
|
|
Raycaster RCaster;
|
|
public class Raycaster
|
|
{
|
|
List<IMyCameraBlock> _cams;
|
|
IMyCameraBlock CoaxialCam;
|
|
public double Resource { get; private set; }
|
|
double _resourcePrev;
|
|
public double ResourceDiff { get; private set; }
|
|
|
|
public int CamsCount => _cams.Count;
|
|
|
|
//float step = 5f;
|
|
int max_gen = 20;
|
|
int rc_round_count = 1;
|
|
|
|
public Raycaster(List<IMyCameraBlock> cams)
|
|
{
|
|
// cam gets charge at 2000 m/s rate by default
|
|
_cams = cams;
|
|
_cams.ForEach(x => x.EnableRaycast = true);
|
|
CoaxialCam = _cams.FirstOrDefault(x => x.Name.Contains("coaxial-cam"));
|
|
// total needed for scan burst
|
|
for (int g = 1; g < max_gen; g++)
|
|
{
|
|
rc_round_count += g * 6;
|
|
}
|
|
}
|
|
|
|
public IMyCameraBlock GetActiveCam()
|
|
{
|
|
for (int i = 0; i < _cams.Count; i++)
|
|
if (_cams[i].IsActive)
|
|
return _cams[i];
|
|
return null;
|
|
}
|
|
|
|
public IMyCameraBlock GetCamForPos(Vector3D pos)
|
|
{
|
|
var ac = GetActiveCam();
|
|
if (ac?.CanScan(pos) == true)
|
|
return ac;
|
|
for (int i = 0; i < _cams.Count; i++)
|
|
if (_cams[i].CanScan(pos))
|
|
return _cams[i];
|
|
return null;
|
|
}
|
|
|
|
public bool GetFwCastPosition(IMyEntity fwRef, IMyShipController elevationGetter, out Vector3D res, float surfaceAdjustment = 0)
|
|
{
|
|
fwRef = fwRef ?? GetActiveCam();
|
|
if (fwRef == null)
|
|
{
|
|
res = default(Vector3D);
|
|
return false;
|
|
}
|
|
|
|
var d = Variables.Get<float>("raycast-range");
|
|
res = fwRef.GetPosition() + fwRef.WorldMatrix.Forward * d;
|
|
|
|
if (surfaceAdjustment != 0)
|
|
{
|
|
double altitude;
|
|
Vector3D pPos;
|
|
if (elevationGetter.TryGetPlanetElevation(MyPlanetElevation.Surface, out altitude))
|
|
{
|
|
elevationGetter.TryGetPlanetPosition(out pPos);
|
|
var plToMeDistance = (elevationGetter.GetPosition() - pPos).Length();
|
|
// the idea is to clamp by sphere around planet, 500m above. Negative for hitting grids, positive for voxels
|
|
altitude += surfaceAdjustment;
|
|
var bs = new BoundingSphereD(pPos, plToMeDistance - altitude);
|
|
var inters = bs.Intersects(new RayD(fwRef.GetPosition(), fwRef.WorldMatrix.Forward));
|
|
if (inters.HasValue && (inters > 0) && inters < d) // zero means bs contains our ray origin
|
|
{
|
|
E.DebugLog($"Cast distance adjusted from {d:f2} to {inters.Value:f2} due to planet proximity");
|
|
d = (float)inters.Value;
|
|
}
|
|
res = fwRef.GetPosition() + fwRef.WorldMatrix.Forward * d;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
List<MyDetectedEntityInfo> _rcBurstBuffer = new List<MyDetectedEntityInfo>();
|
|
public List<MyDetectedEntityInfo> CastBurst(Vector3D refPos, IMyCameraBlock refCaster, float depth, float rsl, long searchForId = 0)
|
|
{
|
|
_rcBurstBuffer.Clear();
|
|
|
|
//int gen = 1;
|
|
int gen = 0;
|
|
int i = 0;
|
|
bool done = false;
|
|
foreach (var cam in _cams)
|
|
{
|
|
if (done)
|
|
{
|
|
break;
|
|
}
|
|
|
|
while (cam.CanScan(depth))
|
|
{
|
|
double angle = 60f / 180f * Math.PI * i / (gen == 0 ? 1 : gen);
|
|
var radInterval = rsl * 0.866f; // 2 cos(30) * R
|
|
var xy = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) * radInterval * gen;
|
|
var p = refPos + refCaster.WorldMatrix.Up * xy.X + refCaster.WorldMatrix.Left * xy.Y;
|
|
var r = cam.Raycast(p);
|
|
|
|
// TODO: cmdr, decaying msg/entity
|
|
//if (cam.CanScan(p))
|
|
// E.Draw(p, "Circle", "ff0000", 1, null);
|
|
//else
|
|
// break;
|
|
|
|
if (r.HitPosition.HasValue)
|
|
{
|
|
if (!ValidateRcNonVoxel(r.Type)) // interrupt for performance
|
|
{
|
|
done = true;
|
|
break;
|
|
}
|
|
if (ValidateRelations(r.Relationship))
|
|
_rcBurstBuffer.Add(r);
|
|
if (r.EntityId == searchForId)
|
|
{
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i++ > 6 * gen)
|
|
{
|
|
i = 0;
|
|
gen++;
|
|
if (gen > max_gen)
|
|
{
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
E.DebugLog($"CastBurst, searchForId {searchForId}, count: {_rcBurstBuffer.Count}");
|
|
return _rcBurstBuffer;
|
|
}
|
|
|
|
public bool TryActualize(long id, Vector3D pos, out MyDetectedEntityInfo mdei)
|
|
{
|
|
var cam = _cams.OrderByDescending(x => x.AvailableScanRange).FirstOrDefault(x => x.CanScan(pos)); // TODO: remove alloc, cache results
|
|
if (cam != null)
|
|
{
|
|
var r = cam.Raycast(pos + Vector3D.Normalize(pos - cam.GetPosition()) * 20f);
|
|
//E.DebugLog($"act {cam.CustomName}, emplty: {r.IsEmpty()}, ppos: GPS:rcT:{VectorOpsHelper.V3DtoBroadcastString(ppos)}:");
|
|
if (r.HitPosition.HasValue && r.EntityId == id)
|
|
{
|
|
//E.DebugLog($"rPos: GPS:rcT:{VectorOpsHelper.V3DtoBroadcastString(r.HitPosition.Value)}:");
|
|
mdei = r;
|
|
return true;
|
|
}
|
|
}
|
|
mdei = new MyDetectedEntityInfo();
|
|
return false;
|
|
}
|
|
|
|
public void UpdateAfter(int tick)
|
|
{
|
|
Resource = 0;
|
|
foreach (var c in _cams)
|
|
Resource += c.AvailableScanRange;
|
|
ResourceDiff = Resource - _resourcePrev;
|
|
_resourcePrev = Resource;
|
|
}
|
|
|
|
public bool CastTriangle(out MyTuple<Vector3D, Vector3D, Vector3D, Vector3D> res, float upDelta, float leftDelta, float range)
|
|
{
|
|
var activeCam = GetActiveCam() ?? CoaxialCam;
|
|
if (activeCam != null)
|
|
{
|
|
var info = Cast(activeCam, activeCam.GetPosition() + activeCam.WorldMatrix.Forward * range);
|
|
if (info.HasValue)
|
|
{
|
|
var dei = info.Value;
|
|
if (!dei.IsEmpty())
|
|
{
|
|
var castedSurfacePoint = dei.HitPosition.Value;
|
|
var p1 = castedSurfacePoint + activeCam.WorldMatrix.Up * upDelta + activeCam.WorldMatrix.Forward * 100;
|
|
var p2 = castedSurfacePoint + activeCam.WorldMatrix.Left * leftDelta + activeCam.WorldMatrix.Forward * 100;
|
|
|
|
if (activeCam.CanScan(p1))
|
|
{
|
|
var cast1 = activeCam.Raycast(p1);
|
|
if (!cast1.IsEmpty())
|
|
{
|
|
if (activeCam.CanScan(p2))
|
|
{
|
|
var cast2 = activeCam.Raycast(p2);
|
|
if (!cast2.IsEmpty())
|
|
{
|
|
var castedNormal = -Vector3D.Normalize(Vector3D.Cross(cast2.HitPosition.Value - castedSurfacePoint, cast1.HitPosition.Value - castedSurfacePoint));
|
|
res = new MyTuple<Vector3D, Vector3D, Vector3D, Vector3D>(castedSurfacePoint, cast1.HitPosition.Value, cast2.HitPosition.Value, castedNormal);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
res = new MyTuple<Vector3D, Vector3D, Vector3D, Vector3D>();
|
|
return false;
|
|
}
|
|
|
|
MyDetectedEntityInfo? Cast(IMyCameraBlock cam, Vector3D position)
|
|
{
|
|
if (cam.CanScan(position))
|
|
{
|
|
var info = cam.Raycast(position);
|
|
if (!info.IsEmpty())
|
|
return info;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void ProbeSurfaceHandler(string[] cmdString, Action<Vector3D, Vector3D> onSuccess)
|
|
{
|
|
var range = cmdString.Length > 2 ? float.Parse(cmdString[2]) : Variables.Get<float>("raycast-range");
|
|
|
|
var activeCam = GetActiveCam() ?? CoaxialCam;
|
|
if ((activeCam?.AvailableScanRange > range) == false)
|
|
{
|
|
E.DebugLog($"No active camera with sufficient range of {range}");
|
|
return;
|
|
}
|
|
MyTuple<Vector3D, Vector3D, Vector3D, Vector3D> tri;
|
|
if (CastTriangle(out tri, 10, 20, range))
|
|
{
|
|
var castedNormal = tri.Item4;
|
|
|
|
var toPoint = tri.Item1 - activeCam.WorldMatrix.Translation;
|
|
|
|
onSuccess.Invoke(tri.Item1, Vector3D.Normalize(castedNormal));
|
|
}
|
|
}
|
|
|
|
public static bool ValidateRcNonVoxel(MyDetectedEntityType type)
|
|
{
|
|
return type != MyDetectedEntityType.Planet && type != MyDetectedEntityType.Asteroid;
|
|
}
|
|
|
|
public static bool ValidateRelations(MyRelationsBetweenPlayerAndBlock rel)
|
|
{
|
|
return Toggle.C.Check("lock-friendlies")
|
|
|| rel == MyRelationsBetweenPlayerAndBlock.Enemies
|
|
|| rel == MyRelationsBetweenPlayerAndBlock.Neutral
|
|
|| rel == MyRelationsBetweenPlayerAndBlock.NoOwnership;
|
|
}
|
|
}
|
|
|
|
|
|
RaycastTrackerTargeter targeterRC;
|
|
public class RaycastTrackerTargeter : ITgpTargeter
|
|
{
|
|
TargetSelectorComponent _tsc;
|
|
|
|
TrackedTargetsPool _pool;
|
|
public string GetTypeName => "RaycastTrackerTargeter";
|
|
|
|
TargetOffsetManager _tom;
|
|
|
|
public Action<IMyCameraBlock, long, Vector3D> OnCamRcSuccess;
|
|
//public Action<TrackedTarget> OnTargetRootUpdated;
|
|
//public Action<TrackedTarget> OnTargetRootRemoved;
|
|
|
|
IMyShipController _ctrl;
|
|
|
|
Raycaster _caster;
|
|
|
|
public RaycastTrackerTargeter(Raycaster caster, IMyShipController ctrl, TargetOffsetManager tom, TargetSelectorComponent tsc,
|
|
TrackedTargetsPool pool)
|
|
{
|
|
_caster = caster;
|
|
|
|
_ctrl = ctrl;
|
|
_tom = tom;
|
|
|
|
_tsc = tsc;
|
|
_pool = pool;
|
|
}
|
|
|
|
public void CheckEncounter(long id, Vector3D p)
|
|
{
|
|
var tt = _pool.GetById(id);
|
|
if (tt == null)
|
|
{
|
|
var cam = _caster.GetCamForPos(p);
|
|
if (cam != null)
|
|
{
|
|
var baseCast = cam.Raycast(p);
|
|
if (baseCast.HitPosition.HasValue && (baseCast.EntityId == id))
|
|
{
|
|
OnCamRcSuccess?.Invoke(null, baseCast.EntityId, p);
|
|
_pool.AddTrackedTarget(baseCast, $"{this.GetTypeName}(EncounterLock)");
|
|
}
|
|
}
|
|
|
|
//var hitId = _caster.Cast(p, _pool, $"{this.GetTypeName}(EncounterLock)");
|
|
//if (hitId.HasValue && hitId.Value == id)
|
|
//{
|
|
// OnCamRcSuccess?.Invoke(null, id, p);
|
|
//}
|
|
}
|
|
}
|
|
|
|
public void Update(TrackedTarget tt)
|
|
{
|
|
var ppos = tt.GetRecastPosition();
|
|
MyDetectedEntityInfo mdei;
|
|
if (_caster.TryActualize(tt.Info.EntityId, ppos, out mdei))
|
|
{
|
|
tt.Actualize(mdei, E.T);
|
|
}
|
|
else
|
|
{
|
|
tt.UsingOffset = true; // failed Position cast, next time try first offset
|
|
}
|
|
}
|
|
|
|
public void ForwardCast(IMyEntity fwRef, float resolution)
|
|
{
|
|
Vector3D pt;
|
|
if (_caster.GetFwCastPosition(fwRef, _ctrl, out pt, -500))
|
|
{
|
|
var cam = _caster.GetCamForPos(pt);
|
|
if (cam != null)
|
|
{
|
|
//var hit = cam.Raycast(pt);
|
|
|
|
TrackedTarget added = null;
|
|
var baseCast = cam.Raycast(pt);
|
|
if (baseCast.HitPosition.HasValue && (baseCast.EntityId != cam.EntityId))
|
|
{
|
|
if (!Raycaster.ValidateRcNonVoxel(baseCast.Type) || !Raycaster.ValidateRelations(baseCast.Relationship))
|
|
{
|
|
E.DebugLog("Cast was successful but target type/relations check has failed");
|
|
return;
|
|
}
|
|
added = _pool.AddTrackedTarget(baseCast, this.GetTypeName);
|
|
var offsetIndex = _tom.ConsiderOffset(baseCast.EntityId, baseCast.Orientation, baseCast.Position, baseCast.HitPosition.Value, false);
|
|
if (offsetIndex > 0)
|
|
added.SelectedOffsetIndex = offsetIndex;
|
|
}
|
|
else if (Toggle.C.Check("raycast-burst"))
|
|
{
|
|
var depth = (float)(pt - cam.GetPosition()).Length() + 50;
|
|
var res = _caster.CastBurst(pt, cam, resolution, depth);
|
|
if (res.Count > 0)
|
|
{
|
|
foreach (var cast in res)
|
|
{
|
|
if (Raycaster.ValidateRcNonVoxel(cast.Type) && Raycaster.ValidateRelations(cast.Relationship))
|
|
{
|
|
added = _pool.AddTrackedTarget(cast, this.GetTypeName);
|
|
_tom.ConsiderOffset(cast.EntityId, cast.Orientation, cast.Position, cast.HitPosition.Value, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (added != null)
|
|
{
|
|
int i = 0;
|
|
foreach (var tt in _pool.Targets.Values)
|
|
{
|
|
if (tt == added)
|
|
{
|
|
_tsc.SetIndex(i);
|
|
}
|
|
i++;
|
|
}
|
|
OnCamRcSuccess?.Invoke(cam, added.Info.EntityId, pt);
|
|
_tom.SetRaycastSource();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TurretAiTargeter targeterTurret;
|
|
public class TurretAiTargeter : ITgpTargeter
|
|
{
|
|
TrackedTargetsPool _pool;
|
|
|
|
HashSet<DesignatorProxy> _designators = new HashSet<DesignatorProxy>();
|
|
|
|
public int Count => _designators.Count;
|
|
|
|
public string GetTypeName => "TurretAiTargeter";
|
|
|
|
public Action<long, Vector3D> OnTargetEncountered;
|
|
|
|
MyDetectedEntityInfo _mdei;
|
|
|
|
TargetOffsetManager _tom;
|
|
|
|
public long? ScanModeMinSize;
|
|
public long? ScanModeMinD;
|
|
public string ScanModeTurretTag;
|
|
|
|
public TurretAiTargeter(TargetOffsetManager tom, TrackedTargetsPool pool)
|
|
{
|
|
_tom = tom;
|
|
_pool = pool;
|
|
}
|
|
|
|
public void AddDesignators(List<IMyLargeTurretBase> turrets, List<IMyTurretControlBlock> controllers)
|
|
{
|
|
foreach (var c in turrets)
|
|
{
|
|
_designators.Add(new DesignatorProxy(c, _tom, this));
|
|
c.Range = 5000;
|
|
}
|
|
foreach (var c in controllers)
|
|
{
|
|
_designators.Add(new DesignatorProxy(c, _tom, this));
|
|
c.Range = 5000;
|
|
}
|
|
}
|
|
|
|
public void Update(int tick, TargetGatingComponent tgc)
|
|
{
|
|
foreach (var d in _designators)
|
|
{
|
|
if (d.Update(ref _mdei))
|
|
{
|
|
var tt = _pool.GetById(_mdei.EntityId);
|
|
if (tt == null)
|
|
{
|
|
if (!tgc.ConsiderAddTarget(ref _mdei))
|
|
{
|
|
d.Reset();
|
|
continue;
|
|
}
|
|
//VRageRender.MyRenderProxy.DebugDrawText3D(_mdei.HitPosition.Value, $"x", Color.Red, 1f, false, persistent: true);
|
|
tt = new TrackedTarget(_mdei, E.T, Vector3D.Zero, this.GetTypeName);
|
|
_pool.Targets.Add(_mdei.EntityId, tt);
|
|
OnTargetEncountered?.Invoke(_mdei.EntityId, _mdei.HitPosition.Value);
|
|
}
|
|
else
|
|
tt.Actualize(_mdei, E.T);
|
|
|
|
// not needed for turret targeter
|
|
//_tsc.SetIndex(_trackedTargets.IndexOf(tt));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void StopScan(long id)
|
|
{
|
|
foreach (var t in _designators)
|
|
{
|
|
t.StopScan();
|
|
}
|
|
}
|
|
|
|
class DesignatorProxy
|
|
{
|
|
IMyLargeTurretBase _d;
|
|
IMyTurretControlBlock _c;
|
|
TargetOffsetManager _tom;
|
|
bool _longRange;
|
|
|
|
int _offsetsFoundUnique;
|
|
int _offsetsFoundTotal;
|
|
|
|
int _scanModeDullLimit = 300;
|
|
int _scanModeLastAdditionStamp;
|
|
long _scanModeLastId;
|
|
bool _scanMode = true;
|
|
|
|
bool _currentScanComplete;
|
|
|
|
TurretAiTargeter _container;
|
|
|
|
public DesignatorProxy(IMyTurretControlBlock c, TargetOffsetManager tom, TurretAiTargeter container)
|
|
{
|
|
_c = c;
|
|
_tom = tom;
|
|
_container = container;
|
|
}
|
|
|
|
public DesignatorProxy(IMyLargeTurretBase t, TargetOffsetManager tom, TurretAiTargeter container)
|
|
{
|
|
_d = t;
|
|
_tom = tom;
|
|
_container = container;
|
|
}
|
|
|
|
public bool Update(ref MyDetectedEntityInfo mdei)
|
|
{
|
|
bool res = false;
|
|
var i = _d?.GetTargetedEntity() ?? _c.GetTargetedEntity();
|
|
if (!i.IsEmpty())
|
|
{
|
|
mdei = i;
|
|
//VRageRender.MyRenderProxy.DebugDrawText3D(mdei.HitPosition.Value, $"x", Color.Red, 1f, false, persistent: true);
|
|
res = true;
|
|
if (_scanModeLastId != mdei.EntityId)
|
|
{
|
|
_offsetsFoundUnique = 0;
|
|
_offsetsFoundTotal = 0;
|
|
_scanModeLastAdditionStamp = 0;
|
|
_currentScanComplete = false;
|
|
|
|
_scanMode = string.IsNullOrEmpty(_container.ScanModeTurretTag) || _d == null || _d.CustomName.Contains(_container.ScanModeTurretTag);
|
|
_scanMode &= !_container.ScanModeMinD.HasValue || _container.ScanModeMinD.Value < Vector3D.Distance(mdei.Position, _d?.GetPosition() ?? _c.GetPosition());
|
|
_scanMode &= !_container.ScanModeMinSize.HasValue || _container.ScanModeMinSize.Value < mdei.BoundingBox.Extents.Length();
|
|
|
|
E.DebugLog($"{_d?.CustomName ?? _c.CustomName} considering scan for {mdei.EntityId}: {_scanMode}");
|
|
}
|
|
_scanModeLastId = mdei.EntityId;
|
|
|
|
if (_scanMode)
|
|
{
|
|
_offsetsFoundTotal++;
|
|
if (_tom.ConsiderOffset(mdei.EntityId, mdei.Orientation, mdei.Position, i.HitPosition.Value, true) > 0)
|
|
{
|
|
_scanModeLastAdditionStamp = E.T;
|
|
_offsetsFoundUnique++;
|
|
}
|
|
|
|
Reset();
|
|
}
|
|
}
|
|
|
|
_scanMode &= !_currentScanComplete && ((_scanModeLastAdditionStamp == 0) || (E.T - _scanModeLastAdditionStamp < _scanModeDullLimit));
|
|
|
|
if (_scanMode)
|
|
E.Echo($"{_d?.CustomName ?? _c.CustomName} scanning");
|
|
|
|
if (_offsetsFoundTotal > 0)
|
|
{
|
|
E.Echo($"polls: {_offsetsFoundUnique}/{_offsetsFoundTotal}, lim: {E.T - _scanModeLastAdditionStamp}");
|
|
E.Echo($"rate: {(float)_offsetsFoundUnique / _offsetsFoundTotal:f2}");
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
if (_d != null)
|
|
_d.ResetTargetingToDefault();
|
|
//else
|
|
//_c.Range--;
|
|
}
|
|
|
|
public void StopScan()
|
|
{
|
|
_scanMode = false;
|
|
_currentScanComplete = true;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
if (_c != null)
|
|
return _c.GetHashCode();
|
|
return _d.GetHashCode();
|
|
}
|
|
}
|
|
}
|
|
|
|
WcTargeter targeterWc;
|
|
public class WcTargeter : ITgpTargeter
|
|
{
|
|
public string GetTypeName => "WcTargeter";
|
|
TrackedTargetsPool _pool;
|
|
TargetOffsetManager _tom;
|
|
Action<IMyTerminalBlock, IDictionary<long, MyDetectedEntityInfo>> _getSortedThreats;
|
|
IMyProgrammableBlock _me;
|
|
|
|
public WcTargeter(TrackedTargetsPool pool, TargetOffsetManager tom, Action<IMyTerminalBlock, IDictionary<long, MyDetectedEntityInfo>> provider, IMyProgrammableBlock me)
|
|
{
|
|
_pool = pool;
|
|
_getSortedThreats = provider;
|
|
_me = me;
|
|
_tom = tom;
|
|
}
|
|
|
|
Dictionary<long, MyDetectedEntityInfo> _buffer = new Dictionary<long, MyDetectedEntityInfo>();
|
|
//Dictionary<MyDetectedEntityInfo, float> _fakeTargetsProfiling;
|
|
public void Update(int tick, TargetGatingComponent tgc)
|
|
{
|
|
_buffer.Clear();
|
|
|
|
/*if (_fakeTargetsProfiling == null)
|
|
{
|
|
_fakeTargetsProfiling = new Dictionary<MyDetectedEntityInfo, float>();
|
|
var r = new Random();
|
|
for (int i = 0; i < 31; i++)
|
|
{
|
|
var pos = _me.GetPosition() + new Vector3(1000 + r.NextDouble() * 500,
|
|
1000 + r.NextDouble() * 500, 1000 + r.NextDouble() * 500);
|
|
var bb = BoundingBoxD.CreateFromSphere(new BoundingSphereD(pos, 20));
|
|
var data = new MyDetectedEntityInfo((long)r.Next(), "x", MyDetectedEntityType.LargeGrid, pos,
|
|
MatrixD.Identity, Vector3.Zero,
|
|
MyRelationsBetweenPlayerAndBlock.Enemies, bb, E.T);
|
|
_fakeTargetsProfiling.Add(data, 10);
|
|
}
|
|
}
|
|
foreach (var f in _fakeTargetsProfiling)
|
|
{
|
|
_buffer.Add(f.Key, f.Value);
|
|
}*/
|
|
|
|
try
|
|
{
|
|
_getSortedThreats(_me, _buffer);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
E.Fail(e.ToString());
|
|
}
|
|
|
|
foreach (var t in _buffer)
|
|
{
|
|
var mdei = t.Value;
|
|
if (mdei.EntityId == 0 || !ValidateRelations(mdei.Relationship))
|
|
continue;
|
|
var tt = _pool.GetById(mdei.EntityId);
|
|
if (tt == null)
|
|
{
|
|
if (!tgc.ConsiderAddTarget(ref mdei))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
_tom.ConsiderOffset(mdei.EntityId, mdei.Orientation, mdei.Position, mdei.HitPosition ?? mdei.Position, true);
|
|
//VRageRender.MyRenderProxy.DebugDrawText3D(_mdei.HitPosition.Value, $"x", Color.Red, 1f, false, persistent: true);
|
|
tt = new TrackedTarget(mdei, E.T, Vector3D.Zero, this.GetTypeName);
|
|
//tt.ThreatScore = t.Value;
|
|
tt.ThreatScore = 10;
|
|
_pool.Targets.Add(mdei.EntityId, tt);
|
|
//OnTargetEncountered?.Invoke(_mdei.EntityId, _mdei.HitPosition.Value);
|
|
}
|
|
else
|
|
{
|
|
//tt.ThreatScore = t.Value;
|
|
tt.ThreatScore = 10;
|
|
tt.Actualize(mdei, E.T);
|
|
}
|
|
}
|
|
}
|
|
|
|
HashSet<int> allowed = new HashSet<int> { (int)MyRelationsBetweenPlayerAndBlock.Enemies };
|
|
public void AllowWcRelation(string rel)
|
|
{
|
|
MyRelationsBetweenPlayerAndBlock val;
|
|
if (Enum.TryParse(rel, out val))
|
|
allowed.Add((int)val);
|
|
}
|
|
|
|
bool ValidateRelations(MyRelationsBetweenPlayerAndBlock rel)
|
|
{
|
|
return allowed.Contains((int)rel);
|
|
}
|
|
}
|
|
RadarTargeter targeterRadar;
|
|
public class RadarTargeter : ITgpTargeter{
|
|
public string GetTypeName => "RadarTargeter";
|
|
TrackedTargetsPool _pool;
|
|
TargetOffsetManager _tom;
|
|
IMyCubeGrid _grid;
|
|
PbNebRadarAPI _radarApi;
|
|
|
|
|
|
public RadarTargeter(TrackedTargetsPool pool, TargetOffsetManager tom, PbNebRadarAPI radarApi, IMyCubeGrid grid)
|
|
{
|
|
_pool = pool;
|
|
_radarApi = radarApi;
|
|
_grid = grid;
|
|
_tom = tom;
|
|
}
|
|
|
|
MemorySafeList<RadarEntry> _buffer = new MemorySafeList<RadarEntry>();
|
|
public void Update(int tick, TargetGatingComponent tgc)
|
|
{
|
|
_buffer.Clear();
|
|
try
|
|
{
|
|
_radarApi.GetAllRadarEntries(_grid, _buffer);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
E.Fail(e.ToString());
|
|
}
|
|
|
|
foreach (var entry in _buffer)
|
|
{
|
|
MyRelationsBetweenPlayerAndBlock relation = ConvertRelations(entry.relation);
|
|
BoundingBoxD box = new BoundingBoxD(Vector3D.One * 10, Vector3D.One * 10);
|
|
MyDetectedEntityInfo mdei = new MyDetectedEntityInfo(entry.EntityId, entry.Name, MyDetectedEntityType.Unknown, entry.Position, MatrixD.Identity, entry.Velocity, relation , box, DateTime.Now.Millisecond);
|
|
if (mdei.EntityId == 0 || !ValidateRelations(mdei.Relationship))
|
|
continue;
|
|
var tt = _pool.GetById(mdei.EntityId);
|
|
if (tt == null)
|
|
{
|
|
if (!tgc.ConsiderAddTarget(ref mdei))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
_tom.ConsiderOffset(mdei.EntityId, mdei.Orientation, mdei.Position, mdei.HitPosition ?? mdei.Position, true);
|
|
tt = new TrackedTarget(mdei, E.T, Vector3D.Zero, this.GetTypeName);
|
|
_pool.Targets.Add(mdei.EntityId, tt);
|
|
}
|
|
else if(tt.Source == "RadarTargeter")
|
|
{
|
|
tt.ThreatScore = 10;
|
|
tt.Actualize(mdei, E.T);
|
|
}
|
|
}
|
|
}
|
|
|
|
HashSet<int> allowed = new HashSet<int> { (int)MyRelationsBetweenPlayerAndBlock.Enemies };
|
|
public void AllowRadarRelation(string rel)
|
|
{
|
|
MyRelationsBetweenPlayerAndBlock val;
|
|
if (Enum.TryParse(rel, out val))
|
|
allowed.Add((int)val);
|
|
}
|
|
|
|
bool ValidateRelations(MyRelationsBetweenPlayerAndBlock rel)
|
|
{
|
|
return allowed.Contains((int)rel);
|
|
}
|
|
}
|
|
|
|
TargetGatingComponent TGatingComp;
|
|
public class TargetGatingComponent
|
|
{
|
|
HashSet<long> _bannedPermanent = new HashSet<long>();
|
|
HashSet<long> _bannedShort = new HashSet<long>();
|
|
IMyShipController _rc;
|
|
Vector3D _nG;
|
|
|
|
public bool DenyInertial;
|
|
public float DenySmallerThan;
|
|
public float DenyMissingSphere;
|
|
public float DenyWcThreatLowerThan;
|
|
|
|
int _tickStampBanCleanup;
|
|
int CLEANUP_INTERVAL = 300;
|
|
|
|
public TargetGatingComponent(IMyShipController rc)
|
|
{
|
|
_rc = rc;
|
|
}
|
|
|
|
public void UpdateBefore()
|
|
{
|
|
if (DenyInertial || DenyMissingSphere > 0)
|
|
_nG = _rc.GetNaturalGravity();
|
|
if ((_bannedShort.Count > 0) && (E.T - _tickStampBanCleanup > CLEANUP_INTERVAL))
|
|
{
|
|
_tickStampBanCleanup = E.T;
|
|
_bannedShort.Clear();
|
|
}
|
|
}
|
|
|
|
public bool ConsiderAddTarget(ref MyDetectedEntityInfo info)
|
|
{
|
|
return !_bannedPermanent.Contains(info.EntityId) && !_bannedShort.Contains(info.EntityId);
|
|
}
|
|
|
|
public void BanPermanent(long id)
|
|
{
|
|
_bannedPermanent.Add(id);
|
|
}
|
|
|
|
public void ClearBans()
|
|
{
|
|
_bannedPermanent.Clear();
|
|
_bannedShort.Clear();
|
|
}
|
|
|
|
public bool ConsiderBanning(TrackedTarget t)
|
|
{
|
|
string reason = null;
|
|
if (DenySmallerThan > 0 && t.Info.BoundingBox.Extents.Length() < DenySmallerThan)
|
|
{
|
|
reason = "DenySmallerThan";
|
|
_bannedPermanent.Add(t.Info.EntityId);
|
|
}
|
|
else if (DenyWcThreatLowerThan > 0 && t.ThreatScore < DenyWcThreatLowerThan)
|
|
{
|
|
reason = "DenyWcThreatLowerThan";
|
|
_bannedPermanent.Add(t.Info.EntityId);
|
|
}
|
|
else if (DenyInertial && t.IsInertial(ref _nG))
|
|
{
|
|
reason = "DenyInertial";
|
|
_bannedShort.Add(t.Info.EntityId);
|
|
}
|
|
else if (DenyMissingSphere > 0 && t.IsInertial(ref _nG) && !IsGoingToViolateMySpace(t.Info, _rc.CubeGrid.GetPosition()))
|
|
{
|
|
reason = "DenyMissingSphere";
|
|
_bannedShort.Add(t.Info.EntityId);
|
|
}
|
|
|
|
if (reason != null)
|
|
{
|
|
E.DebugLog($"{t.Source}.TargetGatingComponent: banned {t.Info.EntityId}, reason: {reason}");
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
bool IsGoingToViolateMySpace(MyDetectedEntityInfo mdei, Vector3D gridTrans)
|
|
{
|
|
var bs = new BoundingSphereD(gridTrans, DenyMissingSphere);
|
|
return (bs.Intersects(new RayD(mdei.Position, Vector3D.Normalize(mdei.Velocity))) != null);
|
|
}
|
|
}
|
|
|
|
|
|
TargetOffsetRepo TORepo;
|
|
public class TargetOffsetRepo
|
|
{
|
|
Action<string> _saveToStorage;
|
|
TargetOffsetManager _tom;
|
|
public Action<long, List<Vector3D>> OnDefinitionMatched;
|
|
|
|
public TargetOffsetRepo(TargetOffsetManager tom, Action<string> saveToStorage)
|
|
{
|
|
_saveToStorage = saveToStorage;
|
|
_tom = tom;
|
|
}
|
|
|
|
public void LoadFromStorage(string storage)
|
|
{
|
|
E.DebugLog(storage);
|
|
}
|
|
|
|
public void AddMeta(ref TargetLite tl, long rootId)
|
|
{
|
|
if (_knownDefs.Count > 0)
|
|
{
|
|
if (_identifiedTargets.ContainsKey(rootId))
|
|
tl.Name = _identifiedTargets[rootId].Name;
|
|
}
|
|
}
|
|
|
|
public void ConsiderOffset(long rootId, Vector3D offset)
|
|
{
|
|
foreach (var def in _knownDefs.Values)
|
|
{
|
|
foreach (var off in def.Offsets)
|
|
{
|
|
if (Vector3D.Distance(offset, off) < 0.1)
|
|
{
|
|
_identifiedTargets[rootId] = def;
|
|
OnDefinitionMatched?.Invoke(rootId, def.Offsets);
|
|
//E.DebugLog("match!");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SetTargetDef(string[] parts)
|
|
{
|
|
var tdef = CreateOrUpdate(parts);
|
|
tdef.Offsets = new List<Vector3D>();
|
|
int startInd = 4;
|
|
for (int offsetZero = startInd; offsetZero + 2 < parts.Length; offsetZero += 3)
|
|
{
|
|
var newOffset = new Vector3D(double.Parse(parts[offsetZero]), double.Parse(parts[offsetZero + 1]), double.Parse(parts[offsetZero + 2]));
|
|
tdef.Offsets.Add(newOffset);
|
|
}
|
|
E.DebugLog($"Updated target definition '{tdef.Name}', offsets: {tdef.Offsets.Count}");
|
|
}
|
|
|
|
// command:set-target-def:id:Name=xxx,Description=yyy:{0}:{1}:{2}:{0}:{1}:{2}
|
|
public void SaveCurrent(string[] parts, long? targetId)
|
|
{
|
|
if (targetId.HasValue)
|
|
{
|
|
var offs = _tom.GetOffsetsForEntity(targetId.Value, true);
|
|
if (offs != null)
|
|
{
|
|
var tdef = CreateOrUpdate(parts);
|
|
tdef.Offsets = offs;
|
|
var str = Serialize();
|
|
E.DebugLog(str);
|
|
_saveToStorage(str);
|
|
}
|
|
}
|
|
}
|
|
|
|
TargetDef CreateOrUpdate(string[] parts)
|
|
{
|
|
var id = parts[2];
|
|
if (!_knownDefs.ContainsKey(id))
|
|
_knownDefs.Add(id, new TargetDef());
|
|
var tdef = _knownDefs[id];
|
|
Dictionary<string, string> vals;
|
|
if (ParseCmdTail(3, parts, out vals))
|
|
{
|
|
tdef.Name = ParseValue<string>(vals, "Name");
|
|
tdef.Desc = ParseValue<string>(vals, "Description");
|
|
}
|
|
return tdef;
|
|
}
|
|
|
|
class TargetDef
|
|
{
|
|
public List<Vector3D> Offsets;
|
|
public string Name;
|
|
public string Desc;
|
|
}
|
|
|
|
Dictionary<string, TargetDef> _knownDefs = new Dictionary<string, TargetDef>();
|
|
Dictionary<long, TargetDef> _identifiedTargets = new Dictionary<long, TargetDef>();
|
|
|
|
|
|
string Serialize()
|
|
{
|
|
var lines = new List<string>();
|
|
Func<double, double> round = x => Math.Round(x, 2);
|
|
foreach (var def in _knownDefs)
|
|
{
|
|
var offs = string.Join(":", def.Value.Offsets.Select(o => VectorOpsHelper.V3DtoStringRounded(round, o)));
|
|
lines.Add($"command:set-target-def:{def.Key}:Name={def.Value.Name},Description={def.Value.Desc}:{offs}");
|
|
}
|
|
|
|
return string.Join("\n", lines);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
TargetOffsetManager Tom = new TargetOffsetManager();
|
|
public class TargetOffsetManager
|
|
{
|
|
Dictionary<long, List<Vector3D>> _raycastOffsets = new Dictionary<long, List<Vector3D>>();
|
|
Dictionary<long, List<Vector3D>> _turretAiOffsets = new Dictionary<long, List<Vector3D>>();
|
|
Dictionary<long, List<Vector3D>> _currentSrc;
|
|
//int lastClearTimestamp;
|
|
|
|
public Action<long, Vector3D> OnAddedOffset;
|
|
|
|
public TargetOffsetManager()
|
|
{
|
|
_currentSrc = _turretAiOffsets;
|
|
}
|
|
|
|
public List<Vector3D> GetOffsetsForEntity(long id, bool? isTurret = null)
|
|
{
|
|
var src = _currentSrc;
|
|
if (isTurret.HasValue)
|
|
src = isTurret.Value ? _turretAiOffsets : _raycastOffsets;
|
|
if (src.ContainsKey(id))
|
|
return src[id];
|
|
else return null;
|
|
}
|
|
|
|
public void ReplaceOffsetsForEntity(long id, List<Vector3D> offsets)
|
|
{
|
|
if (!_turretAiOffsets.ContainsKey(id))
|
|
_turretAiOffsets.Add(id, new List<Vector3D>());
|
|
if (!_raycastOffsets.ContainsKey(id))
|
|
_raycastOffsets.Add(id, new List<Vector3D>());
|
|
_turretAiOffsets[id] = offsets;
|
|
_raycastOffsets[id] = offsets;
|
|
}
|
|
|
|
public int ConsiderOffset(long id, MatrixD entityRot, Vector3D entityPos, Vector3D pt, bool isTurret)
|
|
{
|
|
List<Vector3D> offs;
|
|
if (isTurret)
|
|
{
|
|
if (!_turretAiOffsets.ContainsKey(id))
|
|
_turretAiOffsets.Add(id, new List<Vector3D>());
|
|
offs = _turretAiOffsets[id];
|
|
}
|
|
else
|
|
{
|
|
if (!_raycastOffsets.ContainsKey(id))
|
|
_raycastOffsets.Add(id, new List<Vector3D>());
|
|
offs = _raycastOffsets[id];
|
|
}
|
|
|
|
var offset = Vector3D.TransformNormal(pt - entityPos, MatrixD.Transpose(entityRot));
|
|
if (!isTurret || !offs.Any(x => (offset - x).LengthSquared() < Variables.Get<float>("squared-offset-filter")))
|
|
{
|
|
offs.Add(offset);
|
|
//SelectedOffsetIndex = offs.IndexOf(offset);
|
|
OnAddedOffset?.Invoke(id, offset);
|
|
return offs.Count - 1;
|
|
}
|
|
else
|
|
{
|
|
// getting the same for a while, clear
|
|
//if (isTurret && (E.T - lastClearTimestamp > 1200))
|
|
//{
|
|
// lastClearTimestamp = E.T;
|
|
// offs.Clear();
|
|
//}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public void ClearTurretOffsets(long id)
|
|
{
|
|
if (_turretAiOffsets.ContainsKey(id))
|
|
_turretAiOffsets[id].Clear();
|
|
}
|
|
|
|
List<Vector3D> _tmpGetOffsets = new List<Vector3D>();
|
|
public List<Vector3D> GetWorldOffsets(long id, MatrixD entityRot, Vector3D entityPos)
|
|
{
|
|
_tmpGetOffsets.Clear();
|
|
if (_turretAiOffsets.ContainsKey(id))
|
|
foreach (var off in _turretAiOffsets[id])
|
|
_tmpGetOffsets.Add(Vector3D.TransformNormal(off, entityRot) + entityPos);
|
|
if (_raycastOffsets.ContainsKey(id))
|
|
foreach (var off in _raycastOffsets[id])
|
|
_tmpGetOffsets.Add(Vector3D.TransformNormal(off, entityRot) + entityPos);
|
|
return _tmpGetOffsets;
|
|
}
|
|
public Vector3D GetOffset(long id, int selectedOffsetIndex, MatrixD entityRot, Vector3D entityPos)
|
|
{
|
|
if (_currentSrc.ContainsKey(id) && _currentSrc[id].Count > 0)
|
|
{
|
|
var offIndex = Math.Min(_currentSrc[id].Count - 1, selectedOffsetIndex);
|
|
return Vector3D.TransformNormal(_currentSrc[id][offIndex], entityRot) + entityPos;
|
|
}
|
|
else
|
|
return entityPos;
|
|
}
|
|
public void RemoveOffset(long id, int selectedOffsetIndex)
|
|
{
|
|
if (_currentSrc.ContainsKey(id) && _currentSrc[id].Any())
|
|
{
|
|
var offIndex = Math.Min(_currentSrc[id].Count - 1, selectedOffsetIndex);
|
|
_currentSrc[id].RemoveAt(offIndex);
|
|
}
|
|
}
|
|
public int CycleOffset(long id, int selectedOffsetIndex)
|
|
{
|
|
if (_currentSrc.ContainsKey(id))
|
|
{
|
|
var coll = _currentSrc.First(x => x.Key == id).Value;
|
|
for (int i = 0; i < coll.Count; i++)
|
|
{
|
|
if (i == selectedOffsetIndex)
|
|
{
|
|
var newInd = i + 1;
|
|
if (newInd >= coll.Count)
|
|
newInd = 0;
|
|
return newInd;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
public void CycleSource()
|
|
{
|
|
if (_currentSrc == _turretAiOffsets)
|
|
_currentSrc = _raycastOffsets;
|
|
else
|
|
_currentSrc = _turretAiOffsets;
|
|
}
|
|
public void SetRaycastSource()
|
|
{
|
|
_currentSrc = _raycastOffsets;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public struct TargetLite
|
|
{
|
|
public long Id;
|
|
public string Name;
|
|
public Vector3D? Position;
|
|
public Vector3D? Velocity;
|
|
public MyDetectedEntityInfo? Mdei;
|
|
|
|
public MyTuple<MyTuple<string, long, byte, byte>, Vector3D, Vector3D, MatrixD, BoundingBoxD> GetIgcDto()
|
|
{
|
|
var mask = 0 | (Velocity.HasValue ? 1 : 0) | (Mdei.HasValue ? 2 : 0) | (Mdei.HasValue ? 4 : 0);
|
|
var x = new MyTuple<MyTuple<string, long, byte, byte>, Vector3D, Vector3D, MatrixD, BoundingBoxD>(
|
|
new MyTuple<string, long, byte, byte>(Name, Id, (byte)(Mdei?.Type ?? MyDetectedEntityType.LargeGrid), (byte)mask),
|
|
Position.Value,
|
|
Velocity ?? Vector3D.Zero,
|
|
Mdei?.Orientation ?? MatrixD.Identity,
|
|
Mdei?.BoundingBox ?? new BoundingBoxD()
|
|
);
|
|
return x;
|
|
}
|
|
}
|
|
|
|
public class TargetTelemetry
|
|
{
|
|
public long TickStamp;
|
|
int clock;
|
|
public string Name;
|
|
public string VectorCommandKey;
|
|
public long EntityId;
|
|
public Vector3D? Position { get; private set; }
|
|
public Vector3D? Velocity;
|
|
public Vector3D? Acceleration;
|
|
public MatrixD? OrientationUnit;
|
|
public BoundingBoxD? BoundingBox;
|
|
public long SrcId;
|
|
public MyDetectedEntityType? Type { get; set; }
|
|
public TargetTelemetry(int clock, string name, string setvecCommandName)
|
|
{
|
|
Name = name;
|
|
this.clock = clock;
|
|
VectorCommandKey = setvecCommandName;
|
|
}
|
|
public void SetPosition(Vector3D pos)
|
|
{
|
|
Position = pos;
|
|
TickStamp = E.T;
|
|
}
|
|
|
|
public enum TeleMetaFlags : byte
|
|
{
|
|
HasVelocity = 1,
|
|
HasOrientation = 2,
|
|
HasBB = 4
|
|
}
|
|
|
|
bool HasFlag(TeleMetaFlags packed, TeleMetaFlags flag)
|
|
{
|
|
return (packed & flag) == flag;
|
|
}
|
|
|
|
public void ParseIgc(long srcId, MyTuple<MyTuple<string, long, byte, byte>, Vector3D, Vector3D, MatrixD, BoundingBoxD> igcDto, int localTick)
|
|
{
|
|
var meta = igcDto.Item1;
|
|
Name = meta.Item1;
|
|
EntityId = meta.Item2;
|
|
Type = (MyDetectedEntityType)meta.Item3;
|
|
TeleMetaFlags tm = (TeleMetaFlags)meta.Item4;
|
|
var elapsed = localTick - TickStamp;
|
|
var p = igcDto.Item2;
|
|
if (HasFlag(tm, TeleMetaFlags.HasVelocity))
|
|
{
|
|
var newVel = igcDto.Item3;
|
|
if (!Velocity.HasValue)
|
|
Velocity = newVel;
|
|
if (elapsed > 0)
|
|
Acceleration = (newVel - Velocity.Value) * 60 / elapsed;
|
|
Velocity = newVel;
|
|
p += newVel * E.Dt;
|
|
}
|
|
SetPosition(p);
|
|
if (HasFlag(tm, TeleMetaFlags.HasOrientation))
|
|
OrientationUnit = igcDto.Item4;
|
|
if (HasFlag(tm, TeleMetaFlags.HasBB))
|
|
BoundingBox = igcDto.Item5;
|
|
SrcId = srcId;
|
|
metrics.ParseVectorsCount++;
|
|
}
|
|
|
|
public static TargetTelemetry FromIgc(long srcId, string apCKcmd, MyTuple<MyTuple<string, long, byte, byte>, Vector3D, Vector3D, MatrixD, BoundingBoxD> igcDto, int localTick)
|
|
{
|
|
var t = new TargetTelemetry(1, igcDto.Item1.Item1, apCKcmd);
|
|
t.ParseIgc(srcId, igcDto, E.T);
|
|
return t;
|
|
}
|
|
|
|
public MyTuple<MyTuple<string, long, byte, byte>, Vector3D, Vector3D, MatrixD, BoundingBoxD> GetIgcDto()
|
|
{
|
|
var mask = 0 | (Velocity.HasValue ? 1 : 0) | (OrientationUnit.HasValue ? 2 : 0) | (BoundingBox.HasValue ? 4 : 0);
|
|
var x = new MyTuple<MyTuple<string, long, byte, byte>, Vector3D, Vector3D, MatrixD, BoundingBoxD>(
|
|
new MyTuple<string, long, byte, byte>(Name, EntityId, (byte)MyDetectedEntityType.LargeGrid, (byte)mask),
|
|
Position.Value,
|
|
Velocity ?? Vector3D.Zero,
|
|
OrientationUnit ?? MatrixD.Identity,
|
|
BoundingBox ?? new BoundingBoxD()
|
|
);
|
|
return x;
|
|
}
|
|
|
|
public void Invalidate()
|
|
{
|
|
Position = null;
|
|
Velocity = null;
|
|
OrientationUnit = null;
|
|
BoundingBox = null;
|
|
TickStamp = 0;
|
|
}
|
|
}
|
|
|
|
public static Metrics metrics;
|
|
public struct Metrics
|
|
{
|
|
public int ParseVectorsCount;
|
|
public int ScanBurstsCount;
|
|
public int RcvMsg;
|
|
}
|
|
|
|
public static class VectorOpsHelper
|
|
{
|
|
public static string V3DtoBroadcastString(params Vector3D[] vectors)
|
|
{
|
|
return string.Join(":", vectors.Select(v => string.Format("{0}:{1}:{2}", v.X, v.Y, v.Z)));
|
|
}
|
|
|
|
public static string V3DtoStringRounded(Func<double, double> rounding, params Vector3D[] vectors)
|
|
{
|
|
return string.Join(":", vectors.Select(v => string.Format("{0}:{1}:{2}", rounding(v.X), rounding(v.Y), rounding(v.Z))));
|
|
}
|
|
|
|
public static Vector3D GetAnglesToPointTrig(Vector3D targetPosition, IMyTerminalBlock pivotBlock)
|
|
{
|
|
Vector3D targetNorm = Vector3D.Normalize(targetPosition - pivotBlock.GetPosition());
|
|
double yawCorrectionRads = Math.Acos(Vector3D.Dot(pivotBlock.WorldMatrix.Left, targetNorm));
|
|
double targetYawGrads = yawCorrectionRads * 180 / Math.PI - 90;
|
|
double targetPitchGrads = Math.Acos(Vector3D.Dot(pivotBlock.WorldMatrix.Up, targetNorm)) * 180 / Math.PI - 90;
|
|
return new Vector3D(targetYawGrads, -targetPitchGrads, 0);
|
|
}
|
|
|
|
public static Vector3D GetAnglesToPoint(Vector3D targetPosition, IMyTerminalBlock pivotBlock, MatrixD fwGyroDefault)
|
|
{
|
|
// only for turret!
|
|
var desM = MatrixD.CreateFromDir(Vector3D.Normalize(targetPosition - pivotBlock.WorldMatrix.Translation), pivotBlock.WorldMatrix.Up);
|
|
var myFrameRot = pivotBlock.WorldMatrix.GetOrientation();
|
|
var gatp = GetAnglesToPointMrot(desM, myFrameRot, fwGyroDefault);
|
|
return new Vector3D(-gatp.X, gatp.Y, gatp.Z);
|
|
}
|
|
|
|
public static Vector3D GetAnglesToPointMrot(MatrixD desiredRot, MatrixD myFrameRot, MatrixD fwGyroDefault)
|
|
{
|
|
var trans = desiredRot * MatrixD.Invert(myFrameRot);
|
|
Vector3D a;
|
|
MatrixD.GetEulerAnglesXYZ(ref trans, out a);
|
|
a = Vector3D.TransformNormal(a, fwGyroDefault * MatrixD.Invert(myFrameRot));
|
|
return a * 180 / Math.PI;
|
|
}
|
|
|
|
}
|
|
|
|
public class IncomingMessage
|
|
{
|
|
public string Msg { get; set; }
|
|
public long From { get; set; }
|
|
}
|
|
|
|
IEnumerable<IncomingMessage> ParseMessage(string msg, long sender)
|
|
{
|
|
var items = msg.Split(new[] { "],[" }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim('[', ']')).ToList();
|
|
foreach (var item in items)
|
|
{
|
|
yield return new IncomingMessage { Msg = item, From = sender };
|
|
}
|
|
}
|
|
|
|
IEnumerable<IncomingMessage> ParseMessage(string msg)
|
|
{
|
|
var items = msg.Split(new[] { "],[" }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim('[', ']')).ToList();
|
|
foreach (var item in items)
|
|
{
|
|
//string msg = "{recipient-types:Drone+Missile, recipient-ids: 12313+10, sender-id:H }command:handshake";
|
|
if (item.StartsWith("{"))
|
|
{
|
|
string[] parts = item.Split('}');
|
|
string body = parts[1];
|
|
string headers = parts[0].TrimStart('{');
|
|
string from = "";
|
|
yield return new IncomingMessage { Msg = body, From = long.Parse(from) };
|
|
}
|
|
else
|
|
{
|
|
yield return new IncomingMessage { Msg = item };
|
|
}
|
|
}
|
|
}
|
|
|
|
public void UnicastCommandAutoPillock(string cmd)
|
|
{
|
|
if (APckCoreId.HasValue)
|
|
{
|
|
if (IGC.IsEndpointReachable(APckCoreId.Value))
|
|
{
|
|
E.DebugLog($"Sending apck command '{cmd}'");
|
|
IGC.SendUnicastMessage(APckCoreId.Value, "apck.command", cmd);
|
|
}
|
|
else
|
|
{
|
|
E.DebugLog($"APck {APckCoreId.Value} is not reachable");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
E.DebugLog("APck core PB not found");
|
|
}
|
|
}
|
|
|
|
class Scheduler
|
|
{
|
|
static Scheduler inst = new Scheduler();
|
|
Scheduler() { }
|
|
|
|
public static Scheduler C
|
|
{
|
|
get
|
|
{
|
|
inst.delayForNextCmd = 0;
|
|
inst.repeatCondition = null;
|
|
return inst;
|
|
}
|
|
}
|
|
|
|
class DelayedCommand
|
|
{
|
|
public DateTime TimeStamp;
|
|
//public string Command;
|
|
public Action Command;
|
|
public Func<bool> repeatCondition;
|
|
public long delay;
|
|
}
|
|
|
|
Queue<DelayedCommand> q = new Queue<DelayedCommand>();
|
|
long delayForNextCmd;
|
|
Func<bool> repeatCondition;
|
|
|
|
public Scheduler After(int ms)
|
|
{
|
|
this.delayForNextCmd += ms;
|
|
return this;
|
|
}
|
|
|
|
public Scheduler RunCmd(Action cmd)
|
|
{
|
|
q.Enqueue(new DelayedCommand { TimeStamp = DateTime.Now.AddMilliseconds(delayForNextCmd), Command = cmd, repeatCondition = repeatCondition, delay = delayForNextCmd });
|
|
return this;
|
|
}
|
|
|
|
public Scheduler RepeatWhile(Func<bool> repeatCondition)
|
|
{
|
|
this.repeatCondition = repeatCondition;
|
|
return this;
|
|
}
|
|
|
|
public void HandleTick()
|
|
{
|
|
if (q.Count > 0)
|
|
{
|
|
E.Echo("Scheduled actions count:" + q.Count);
|
|
var c = q.Peek();
|
|
if (c.TimeStamp < DateTime.Now)
|
|
{
|
|
if (c.repeatCondition != null)
|
|
{
|
|
if (c.repeatCondition.Invoke())
|
|
{
|
|
c.Command.Invoke();
|
|
c.TimeStamp = DateTime.Now.AddMilliseconds(c.delay);
|
|
}
|
|
else
|
|
{
|
|
q.Dequeue();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
c.Command.Invoke();
|
|
q.Dequeue();
|
|
}
|
|
|
|
//sendFeedback("Executing " + c.Command, "", true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
InputListener UserCtrlListener;
|
|
public class InputListener
|
|
{
|
|
public Vector2 Cumulative = new Vector2();
|
|
List<IMyShipController> ctrls;
|
|
Func<int> tickGetter;
|
|
public IMyShipController GetControlledCockpit()
|
|
{
|
|
return _activeController;
|
|
}
|
|
|
|
public IMyShipController GetLastNonRemoteController()
|
|
{
|
|
return _lastActiveController;
|
|
}
|
|
|
|
IMyShipController _activeController;
|
|
IMyShipController _lastActiveController;
|
|
Vector3 _moveInd;
|
|
Vector2 _rotInd;
|
|
float _rollInd;
|
|
|
|
public void PrepareBeforeTick()
|
|
{
|
|
foreach (var x in h)
|
|
x.Update();
|
|
_activeController = ctrls.Where(c => c.IsUnderControl).FirstOrDefault();
|
|
|
|
if (_activeController != null)
|
|
{
|
|
_moveInd = _activeController.MoveIndicator;
|
|
_moveInd.X = -_moveInd.X;
|
|
//_moveInd.Z = -_moveInd.Z;
|
|
if (!(_activeController is IMyRemoteControl))
|
|
_lastActiveController = _activeController;
|
|
}
|
|
else
|
|
_moveInd = Vector3.Zero;
|
|
|
|
_rollInd = _activeController?.RollIndicator ?? 0f;
|
|
_rotInd = _activeController?.RotationIndicator ?? Vector2.Zero;
|
|
}
|
|
|
|
public Vector3 GetVector()
|
|
{
|
|
return _moveInd;
|
|
}
|
|
public Vector3D GetUserCtrlVector(ref MatrixD fwRef)
|
|
{
|
|
Vector3 res = new Vector3();
|
|
if (Toggle.C.Check("ignore-user-thruster"))
|
|
return res;
|
|
if ((_activeController != null) && (_activeController.MoveIndicator != Vector3.Zero))
|
|
return Vector3D.TransformNormal((Vector3D)_activeController.MoveIndicator, fwRef * MatrixD.Transpose(_activeController.WorldMatrix));
|
|
return res;
|
|
}
|
|
public Vector2 GetRot()
|
|
{
|
|
return _rotInd;
|
|
}
|
|
public float GetRoll()
|
|
{
|
|
return _rollInd;
|
|
}
|
|
|
|
class InputHistory
|
|
{
|
|
public string KeyName;
|
|
public int LastKeyDownStamp;
|
|
public int State { get; private set; }
|
|
public int PendingState;
|
|
public void Update()
|
|
{
|
|
//if (State != PendingState)
|
|
//E.DebugLog($"Key state [{KeyName}]: {State} -> {PendingState}");
|
|
State = PendingState;
|
|
}
|
|
}
|
|
|
|
List<InputHistory> h;
|
|
public InputListener(List<IMyShipController> ctrlToConsider, Func<int> tickGetter)
|
|
{
|
|
ctrls = ctrlToConsider;
|
|
this.tickGetter = tickGetter;
|
|
|
|
h = new List<InputHistory>();
|
|
h.Add(new InputHistory { KeyName = "spacebar" });
|
|
h.Add(new InputHistory { KeyName = "c" });
|
|
h.Add(new InputHistory { KeyName = "e" });
|
|
h.Add(new InputHistory { KeyName = "q" });
|
|
|
|
h.Add(new InputHistory { KeyName = "w" });
|
|
h.Add(new InputHistory { KeyName = "s" });
|
|
h.Add(new InputHistory { KeyName = "a" });
|
|
h.Add(new InputHistory { KeyName = "d" });
|
|
}
|
|
|
|
public bool CheckKeyDown(string keyName)
|
|
{
|
|
if (_activeController != null)
|
|
{
|
|
bool isKeyDown = false;
|
|
if ((keyName == "spacebar") && (_moveInd.Y > 0))
|
|
isKeyDown = true;
|
|
if ((keyName == "c") && (_moveInd.Y < 0))
|
|
isKeyDown = true;
|
|
if ((keyName == "e") && (_rollInd > 0))
|
|
isKeyDown = true;
|
|
if ((keyName == "q") && (_rollInd < 0))
|
|
isKeyDown = true;
|
|
if ((keyName == "w") && (_moveInd.Z < 0))
|
|
isKeyDown = true;
|
|
if ((keyName == "s") && (_moveInd.Z > 0))
|
|
isKeyDown = true;
|
|
if ((keyName == "a") && (_moveInd.X > 0))
|
|
isKeyDown = true;
|
|
if ((keyName == "d") && (_moveInd.X < 0))
|
|
isKeyDown = true;
|
|
|
|
return isKeyDown;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool KeyReleased(string keyName)
|
|
{
|
|
var cState = h.First(h => h.KeyName == keyName);
|
|
if (CheckKeyDown(keyName))
|
|
{
|
|
if (cState.State == 0)
|
|
{
|
|
cState.PendingState = 1;
|
|
cState.LastKeyDownStamp = tickGetter();
|
|
}
|
|
if (cState.State == 2)
|
|
{
|
|
cState.PendingState = 0;
|
|
}
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if ((cState.State == 1) || (cState.State == 2))
|
|
{
|
|
cState.PendingState = 0;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
public bool CheckDoubleTap(string keyName)
|
|
{
|
|
return UpdateKeyState(keyName, tickGetter(), CheckKeyDown(keyName));
|
|
}
|
|
|
|
bool UpdateKeyState(string keyName, int currentTick, bool keyDown)
|
|
{
|
|
var cState = h.First(h => h.KeyName == keyName);
|
|
if (keyDown)
|
|
{
|
|
if (cState.State == 0)
|
|
{
|
|
cState.PendingState = 1;
|
|
cState.LastKeyDownStamp = currentTick;
|
|
}
|
|
if (cState.State == 2) // was released less than 30 ticks ago
|
|
{
|
|
cState.PendingState = 0;
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (currentTick - cState.LastKeyDownStamp < 30)
|
|
{
|
|
if (cState.State == 1)
|
|
{
|
|
cState.PendingState = 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cState.PendingState = 0;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
List<MyTuple<string, Vector3D, ImmutableArray<string>>> prjs = new List<MyTuple<string, Vector3D, ImmutableArray<string>>>();
|
|
void EmitProjection(string tag, Vector3D p, params string[] s)
|
|
{
|
|
prjs.Add(new MyTuple<string, Vector3D, ImmutableArray<string>>(tag, p, s.ToImmutableArray()));
|
|
}
|
|
|
|
void EmitFlush(long addr)
|
|
{
|
|
IGC.SendUnicastMessage(addr, "hud.tgp.proj", prjs.ToImmutableArray());
|
|
prjs.Clear();
|
|
}
|
|
|
|
void EmitHudText(long a, string t, Vector2 p, float size)
|
|
{
|
|
IGC.SendUnicastMessage(a, "draw-text",
|
|
new MyTuple<string, Vector2, float>(
|
|
t,
|
|
p,
|
|
size
|
|
));
|
|
}
|
|
|
|
static bool ParseCmdTail(int ind, string[] parts, out Dictionary<string, string> vals)
|
|
{
|
|
if ((parts.Length > ind) && parts[3].Contains("="))
|
|
{
|
|
vals = parts[ind].Split(',').ToDictionary(s => s.Split('=')[0], s => s.Split('=')[1]);
|
|
return true;
|
|
}
|
|
vals = null;
|
|
return false;
|
|
}
|
|
|
|
static T ParseValue<T>(Dictionary<string, string> values, string key)
|
|
{
|
|
string res;
|
|
if ((values != null) && values.TryGetValue(key, out res) && !string.IsNullOrEmpty(res))
|
|
{
|
|
if (typeof(T) == typeof(string))
|
|
return (T)(object)res;
|
|
else if (typeof(T) == typeof(int?))
|
|
return (T)(object)int.Parse(res);
|
|
else if (typeof(T) == typeof(long?))
|
|
return (T)(object)long.Parse(res);
|
|
else if (typeof(T) == typeof(float?))
|
|
return (T)(object)float.Parse(res);
|
|
}
|
|
return default(T);
|
|
}
|
|
|
|
public static class E
|
|
{
|
|
static string debugTag = "";
|
|
static Action<string> e;
|
|
static IMyTextSurface p;
|
|
static IMyTextPanel ech;
|
|
static IMyTextSurface l;
|
|
static IMyIntergridCommunicationSystem _i;
|
|
public static int T;
|
|
public static double Dt;
|
|
public static int ErrCtr;
|
|
public static void Init(Action<string> echo, IMyGridTerminalSystem g, IMyProgrammableBlock me, IMyIntergridCommunicationSystem i)
|
|
{
|
|
e = echo;
|
|
p = me.GetSurface(0);
|
|
p.ContentType = ContentType.TEXT_AND_IMAGE;
|
|
p.WriteText("");
|
|
ech = g.GetBlockWithName("LCD Echo") as IMyTextPanel;
|
|
_i = i;
|
|
}
|
|
public static void Echo(string s)
|
|
{
|
|
if ((debugTag == "") || s.Contains(debugTag))
|
|
e(s);
|
|
if (ech != null)
|
|
DebugToPanel(s);
|
|
}
|
|
|
|
static string buff = "";
|
|
public static void DebugToPanel(string s)
|
|
{
|
|
buff += s + "\n";
|
|
}
|
|
static List<string> linesToLog = new List<string>();
|
|
public static void DebugLog(string s)
|
|
{
|
|
p.WriteText($"{T}: {s}\n", true);
|
|
if (l != null)
|
|
{
|
|
linesToLog.Add(s);
|
|
}
|
|
}
|
|
public static void Fail(string s)
|
|
{
|
|
ErrCtr++;
|
|
DebugLog(s);
|
|
}
|
|
public static void ClearLog()
|
|
{
|
|
l?.WriteText("");
|
|
linesToLog.Clear();
|
|
}
|
|
public static void AddLogger(IMyTextSurface s)
|
|
{
|
|
l = s;
|
|
}
|
|
public static void EndOfTick()
|
|
{
|
|
if (!string.IsNullOrEmpty(buff))
|
|
{
|
|
ech?.WriteText(buff);
|
|
buff = "";
|
|
}
|
|
if (linesToLog.Count > 0)
|
|
{
|
|
if (l != null)
|
|
{
|
|
linesToLog.Reverse();
|
|
var t = string.Join("\n", linesToLog) + "\n" + l.GetText();
|
|
var u = LOGGER_MAX_CHARS;
|
|
if (t.Length > u)
|
|
t = t.Substring(0, u - 1);
|
|
l.WriteText($"{T:f2}: {t}");
|
|
}
|
|
linesToLog.Clear();
|
|
}
|
|
if (_drCalls.Count > 0)
|
|
{
|
|
//_i.SendBroadcastMessage("cmdr.persist-projection.batch", _drCalls.ToImmutableArray());
|
|
_i.SendBroadcastMessage("cmdr.draw-projection.batch", _drCalls.ToImmutableArray());
|
|
_drCalls.Clear();
|
|
}
|
|
}
|
|
|
|
|
|
static List<MyTuple<string, Vector2, Vector3D, Vector4, string>> _drCalls = new List<MyTuple<string, Vector2, Vector3D, Vector4, string>>();
|
|
public static void Draw(Vector3D p, string spr, string cs, int size, string lbl)
|
|
{
|
|
var rgb = cs.Trim('#');
|
|
var c = new Color(GetFromHex(rgb, 0), GetFromHex(rgb, 2), GetFromHex(rgb, 4));
|
|
_drCalls.Add(new MyTuple<string, Vector2, Vector3D, Vector4, string>(spr, Vector2.One * size, p, c.ToVector4(), lbl));
|
|
}
|
|
static int GetFromHex(string x, int i)
|
|
{
|
|
return int.Parse(x.Substring(i, 2), System.Globalization.NumberStyles.HexNumber);
|
|
}
|
|
|
|
public static Action<string> Info;
|
|
public static Action InfoOnTickStart;
|
|
public static Action<string> Lock;
|
|
public static Action LockOnTickStart;
|
|
}
|
|
|
|
public class PbNebRadarAPI
|
|
{
|
|
public const string ModAPIVersion = "v1.1";
|
|
public const string PbTerminalId = "NRadar_PbAPI";
|
|
public bool Load(Sandbox.ModAPI.Ingame.IMyProgrammableBlock pbBlock)
|
|
{
|
|
IsReady = false;
|
|
var dict = pbBlock.GetProperty(PbTerminalId)?.As<IReadOnlyDictionary<string, Delegate>>().GetValue(pbBlock);
|
|
|
|
if (dict == null)
|
|
return false;
|
|
|
|
try
|
|
{
|
|
ApiAssign(dict);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw e;
|
|
}
|
|
_TupleRadarEntryDumpList = new MemorySafeList<MyTuple<ushort, string, MyTuple<Vector3D, Vector3, float, float>, long, byte, byte>>();
|
|
_TupleJammingEntryV2DumpList = new MemorySafeList<MyTuple<Vector3D, float>>();
|
|
_TuplePassiveRadarEntryDumpList = new MemorySafeList<MyTuple<byte, float, float, long, Vector3D, Vector3D>>();
|
|
|
|
IsReady = true;
|
|
|
|
return true;
|
|
}
|
|
public bool IsReady
|
|
{
|
|
get; private set;
|
|
}
|
|
public void GetAllRadarEntries(VRage.Game.ModAPI.Ingame.IMyCubeGrid grid, MemorySafeList<RadarEntry> returnList)
|
|
{
|
|
_getAllRadarEntries.Invoke(grid, _TupleRadarEntryDumpList);
|
|
foreach (var entry in _TupleRadarEntryDumpList)
|
|
{
|
|
returnList.Add(new RadarEntry(entry));
|
|
}
|
|
_TupleRadarEntryDumpList.Clear();
|
|
}
|
|
private MemorySafeList<MyTuple<ushort, string, MyTuple<Vector3D, Vector3, float, float>, long, byte, byte>> _TupleRadarEntryDumpList;
|
|
private MemorySafeList<MyTuple<Vector3D, float>> _TupleJammingEntryV2DumpList;
|
|
private MemorySafeList<MyTuple<byte, float, float, long, Vector3D, Vector3D>> _TuplePassiveRadarEntryDumpList;
|
|
|
|
private Action<VRage.Game.ModAPI.Ingame.IMyCubeGrid, MemorySafeList<MyTuple<ushort, string, MyTuple<Vector3D, Vector3, float, float>, long, byte, byte>>> _getAllRadarEntries;
|
|
private void ApiAssign(IReadOnlyDictionary<string, Delegate> delegates)
|
|
{
|
|
AssignMethod(delegates, "GetAllRadarEntriesPb", ref _getAllRadarEntries);
|
|
}
|
|
protected void AssignMethod<T>(IReadOnlyDictionary<string, Delegate> delegates, string name, ref T field) where T : class
|
|
{
|
|
if (delegates == null)
|
|
{
|
|
field = null;
|
|
return;
|
|
}
|
|
|
|
Delegate del;
|
|
if (!delegates.TryGetValue(name, out del))
|
|
throw new Exception($"PbNebRadarAPI ERROR: Couldn't find {name} delegate of type {typeof(T)}");
|
|
|
|
field = del as T;
|
|
|
|
if (field == null)
|
|
throw new Exception($"PbNebRadarAPI ERROR: Delegate {name} is not type {typeof(T)}, instead it's: {del.GetType()}");
|
|
}
|
|
}
|
|
public enum Relation : byte
|
|
{
|
|
Neutral,
|
|
Enemy,
|
|
Allied
|
|
}
|
|
[Flags]
|
|
public enum StateFlags : byte
|
|
{
|
|
None = 0,
|
|
Jumping = 1 << 0,
|
|
Delete = 1 << 7,
|
|
}
|
|
public struct RadarEntry
|
|
{
|
|
public Relation relation;
|
|
public StateFlags RadarFlags;
|
|
public ushort TrackNumber;
|
|
public float PositionError;
|
|
public float VelocityError;
|
|
public string Name;
|
|
public long EntityId;
|
|
public Vector3D Position;
|
|
public Vector3 Velocity;
|
|
public bool IsLocked => PositionError + VelocityError == 0;
|
|
|
|
public RadarEntry(MyTuple<ushort, string, MyTuple<Vector3D, Vector3, float, float>, long, byte, byte> data)
|
|
{
|
|
TrackNumber = data.Item1;
|
|
Name = data.Item2;
|
|
Position = data.Item3.Item1;
|
|
Velocity = data.Item3.Item2;
|
|
PositionError = data.Item3.Item3;
|
|
VelocityError = data.Item3.Item4;
|
|
EntityId = data.Item4;
|
|
relation = (Relation)data.Item5;
|
|
RadarFlags = (StateFlags)data.Item6;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"[{TrackNumber}]: {Name} [{relation}] / s={Position} (E={PositionError}) / v={Velocity} (E={VelocityError}) / {RadarFlags}";
|
|
}
|
|
}
|
|
[Flags]
|
|
public enum PassiveStateFlags : byte
|
|
{
|
|
None = 0,
|
|
Position = 1 << 0,
|
|
Delete = 1 << 7
|
|
}
|
|
|
|
public static MyRelationsBetweenPlayerAndBlock ConvertRelations(Relation relation)
|
|
{
|
|
switch (relation)
|
|
{
|
|
case Relation.Neutral:
|
|
return MyRelationsBetweenPlayerAndBlock.Neutral;
|
|
case Relation.Allied:
|
|
return MyRelationsBetweenPlayerAndBlock.Friends;
|
|
case Relation.Enemy:
|
|
return MyRelationsBetweenPlayerAndBlock.Enemies;
|
|
default:
|
|
return MyRelationsBetweenPlayerAndBlock.NoOwnership;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|