Determining Activated Modules on Targets

(Note: I am new to C# and Sanderling, so please be merciful on my syntax errors)

I am curious if it is possible to determine what module or drone is being used in Sanderling.MemoryMeasurementParsed.Value.Target.Activated. The ID that’s generated appears to be a random or one-up value that I can’t parse, and doesn’t correlate to the modules/drones icons, their IDs, or anything else I can find. I can tell when I have multiples, obviously, since there are multiple entries, but I was wondering how we could determine what we have activated on each (e.g., high slot 1: cruise missile launcher ii or fighter squadron 1 and 3, etc.).

A much more simple Follow-up question: having used the API explorer, I can see values that I’d want to get, but I’m not sure how to reference it in the code. Let’s use an Overview entry or Target where IsSelected == True as an example. Do I need to iterate through a structure to find that? Can this be done with a lambda like: var SelectedTarget = Sanderling?.MemoryMeasurementParsed?.Value?.Target?.FirstOrDefault().Entry?.Where(entry => entry?.IsSelected() ?? false)

I’m paraphrasing code from the A-Bot there, not sure if that’s right…

To obtain information about drones and modules assigned to a target, use the ‘Assigned’ property on the ‘IShipUITarget’ representing the target.
(See Sanderling/ShipUi.cs at 2e86dc7d474b0fdb14f9d67fdf4a3cba2b4dbe2e · Arcitectus/Sanderling · GitHub)

In the sequence you get from ‘Assigned’, there is an element for each icon under the target.

The Id of the IconTexture can be used to correlate with textures used in module tooltips. This is a way to make a connection between module and assignment to a target.

To read the actual connection from module to target, an extension to the memory reading code would be needed.

Yes, for targets, iterate over Sanderling.MemoryMeasurementParsed.Value.Target.
To implement this iteration, you can use a lambda like in the following code:

var selectedTarget = Sanderling.MemoryMeasurementParsed.Value.Target?.FirstOrDefault(target => target?.IsSelected ?? false);

var selectedOverviewEntry = Sanderling.MemoryMeasurementParsed.Value.WindowOverview?.FirstOrDefault()?.ListView?.Entry?.FirstOrDefault(entry => entry?.IsSelected ?? false);

Sorry, where is this information found? I tried looking at ShipUI.Modules (don’t have it in front of me, paraphrasing), the IDs for the assigned would change each time (3 ungrouped missile launchers all had different IDs). Can you show me in the Explorer where the two connected data points are?

There’s no need to read the actual connection, as long as I can use logic to tie them together one way or another.

Can you explain to me the difference b/t MemoryMeasurementParsed vs. MemoryMeasurementAtTime vs. MemoryMeasurementAccu?

Paths in the API Explorer

In the API explorer, you should be able to expand the following paths:

On the target side

  • MemoryMeasurementParsed.Value?.Target
  • choose target you want to compare from list.
  • ?.Assigned
  • choose assignment you want to compare from list.
  • ?.IconTexture?.Id

On the module tooltip side

  • MemoryMeasurementParsed?.Value?.ModuleButtonTooltip?.Sprite
  • I would simply compare to all of the sprites here.
  • ?.Texture0Id?.Id

To clarify, I add an example image below containing a module tooltip. In such a case, you should find four sprites in the Sprite property, for the four icons on the left side of the tooltip:

The idea here is that one of those icons you see in the module tooltip will be used in the assignment. Can you confirm this?

The approach outlined above is just a way to rule out some connections, based on the icons. After this filtering, you might still be left with multiple possibilities.

So if you want to cover all possible scenarios, I don’t think the current implementation of the API has all the data you need.

About MemoryMeasurementAtTime: I do not see this anywhere in the API, where do you see this?

The MemoryMeasurementParsed contains only data from a single memory measurement.
In contrast, MemoryMeasurementAccu is an accumulation of data from multiple measurements. It retains the modules tooltips which have been seen at different times. So you can find the tooltips of multiple modules in there even when you cannot have multiple of those tooltips on the screen at the same time.

This is used, for example in the sample mining script to compute the subset of modules which are mining modules. The code for this is at Sanderling/Mine.ore.cs at 59be48d7c04d9cb28072242a9732c20a67c69866 · Arcitectus/Sanderling · GitHub

Ah ok, I understand now. I was looking at Module’s icon before. So, in order to populate that, do I need to make the tooltips appear while getting the measurement? For drones, I guess I would just need to differentiate it from any weapon systems that I have tooltips for… Do you know if those values are static for that process’s lifetime? I could make a function that makes the necessary tool tips appear on the first undock or something.

I will be able to confirm which of those tooltips corresponds in ~6 hours.

I found MemoryMeasurementAtTime in the A-Bot’s combat.cs script: var memoryMeasurementAtTime = bot?.MemoryMeasurementAtTime;

Yes, exactly. This also has been implemented already. You can see a function doing this in the sample mining script at Sanderling/Mine.ore.cs at 59be48d7c04d9cb28072242a9732c20a67c69866 · Arcitectus/Sanderling · GitHub

I don’t know. I doubt it.
Also related: The scripts and bots I remember discard the stored tooltips when the ship appears to be docked. You can see the mission running bot and sample mining script looking at module tooltips every time it undocks.

That should be the same as MemoryMeasurementParsed in the Sanderling exes API explorer.

Would something like this work?

var currentTarShield = measurement?.Value.Target?.FirstOrDefault(target => target?.IsSelected ?? false).Hitpoints.Shield.ToString();

or do I need to do this:

var currentTar = measurement?.Value.Target?.FirstOrDefault(target => target?.IsSelected ?? false);

var currentTarShield = currentTar.Hitpoints.Shield.ToString();

Actually, when I do this, I get a NullReferenceException, saying I’m trying to reference an instance of an object that doesn’t exist? Any idea what I’m doing wrong here?

2nd Question: Can you/would you be willing to build Sanderling in .NET Framework 4.5.1? Do you have any older versions that would be? I ask because I’m limited to Win7 without SP1, and I can’t compile my own code because of dependencies on Sanderling that were built on 4.6.1. I would be grateful for any help!

Edit: what I find incredibly confusing is that I can run 17.05.03 just fine, but if I try to compile the code from a lightly modified Sanderling.Sample.Read source, it won’t compile at 4.5.1 or 4.5. Sanderling.dll, protobuf-net, Sanderling.MemoryReading, BotSharp, and Process.Measurement were all built against 4.6 or 4.6.1. I don’t understand how 17.05.03’s Sanderling.exe could work on a Win7 VM with only 4.5.1 installed?

Besides the NullReferenceException, this works.

The NullReferenceException is a common problem with C#. In the case you showed, this exception could be thrown for example when there are no targets.

To work around the NullReferenceException, use the following code instead:

var currentTarShield = measurement?.Value?.Target?.FirstOrDefault(target => target?.IsSelected ?? false)?.Hitpoints?.Shield?.ToString();

As you see in the code, I have replaced the dots in member access expressions by the two characters ?.

By the way, microsoft is working on mitigating the problem with a future version of C#. For details on their plan, see the video (at 5:37) at:

Ah, ok, I understand. Thank you for the help! Do you have any insight into my other issue with compiling in .NET Framework 4.5, or is that something I’d have to resolve with Arcitectus?

I have no experience with a scenario of compiling in .NET Framework 4.5. Arcitectus was just another account I used earlier.

I resolved those issues by changing my OS. However… it seems like in my Win7 SP1 VM, Sanderling can’t actually take a memory measurement: many minutes go by and the measurement never appears. Have you tested this in a virtual environment before?

I do not have experience with Sanderling in a Win7 SP1 VM, I have not tested this.

So I found the recommended RDP-based solution for multiple instances of Sanderling. If we wanted to run bots for more than ~8-12 hours (say, two groups for 10-12 hours each), it’d appear to CCP that the system was on and running 23/7, since it all comes off the same IP/MAC/CPUID/etc. Is there same way to mitigate this, or do you just not run bots off of the same computer for more than 12 hours a day? Have you tested Sanderling in any virtual environment?

@AbbadonDespoiler in past I tested A-Bot on vmware, but was a different version, me and @Viir were worked on mission bot, so my test it’s not to consider, too many patch from the test and too many change.

@Viir @Master

While you’re indulging me, I had a question about how in C# to access an index directly. Consider this code:

var Squadron1GunsActive = measurement?.Value?.ShipUi?.SquadronsUI?.SetSquadron?.FirstOrDefault(squadron => squadron?.Squadron?.SquadronNumber == 1)?.SetAbilityIcon?..ToString();

I know that the Guns are under Index [2] (index[0] is the Rockets, thanks to the Quantity of 12), is there a way to just invoke SetAbilityIcon[2] in this construct? Would I just do SetAbilityIcon[2]?.RampActive.ToString()?


As far as I see, SetAbilityIcon is of type IEnumerable<ISquadronAbilityIcon>.
To access an element of an IEnumerable<> by index, you can use the method ElementAt

If you also want to cover cases where there could be fewer elements in the sequence, use ElementAtOrDefault to avoid an ArgumentOutOfRangeException.

To map from your example code, it would look like this instead:



I have already made some function on activate/deactivate the various attacks of squadrons on the targhet, you wrong the “approach” on read the “RampActive” value, the value is boolean.

Btw if you wait max 1-2 days I will finished the combattask of squadrons, then I will give to you the various functions and the point where to add them.

About the special attack I have in mind to execute only on ship >= the battleships, so I will had an checking function on the actually targhet. But I need time to fix something on my repository.

@Viir Thank you! While I’m indulging in your generosity, how would I obtain the index from a FirstOrDefault with a Lambda? For example, I want to find the index in the Overview Listview for a certain name or ID.

@Master thank you as well! So, I’m not using Sanderling to “do” anything, just read info from memory every 500ms or so and then use AHK to act on it, so 99% of the C# code I need is just how to retrieve information. That said, seeing how you obtain the info you need for those functions would be useful.

@AbbadonDespoiler this is an example of my function:

yield return bot.SquadronAction(SquadronsMainAttack, memoryMeasurement?.ShipUi?.SquadronsUI); 

internal IBotTask SquadronAction(VirtualKeyCode SquadronKeyAction, ISquadronsUI squadronsUI)
			return new ActiveSquadronActionF { ActionKey = SquadronKeyAction, SquadronUI = squadronsUI };

public class ActiveSquadronActionF : IBotTask
		public Bot bot;

		public Interface.MemoryStruct.ISquadronsUI SquadronUI;

		public IEnumerable<IBotTask> Component => null;

		public VirtualKeyCode ActionKey;
		public IEnumerable<MotionParam> Effects
				if (ActionKey != VirtualKeyCode.F1)
					yield return ActionKey.KeyboardPress();
					var squadrons = SquadronUI?.SetSquadron;
					if (squadrons != null)
						var array = (IList<Interface.MemoryStruct.ISquadronUI>)squadrons;

						var firstyAbilityArray = (IList<Interface.MemoryStruct.ISquadronAbilityIcon>)array[0].SetAbilityIcon;
						var secondAbilityArray = (IList<Interface.MemoryStruct.ISquadronAbilityIcon>)array[1].SetAbilityIcon;
						var thirdAbilityArray = (IList<Interface.MemoryStruct.ISquadronAbilityIcon>)array[2].SetAbilityIcon;

						if ((firstyAbilityArray != null && firstyAbilityArray[2].RampActive == true) && (secondAbilityArray != null && secondAbilityArray[2].RampActive == true) && (thirdAbilityArray != null && thirdAbilityArray[2].RampActive == true))
							yield break;
							yield return ActionKey.KeyboardPress();

							if ((firstyAbilityArray != null && firstyAbilityArray[2].RampActive == true) || (secondAbilityArray != null && secondAbilityArray[2].RampActive == true) || (thirdAbilityArray != null && thirdAbilityArray[2].RampActive == true))
								yield break;
								var SquadronBonus = VirtualKeyCode.F2;
								yield return SquadronBonus.KeyboardPress();

The logical is that:

If the virtualkey is different then F1 mean Im activating the speed bonus or the special attack, those 2 activation for me not need any kind of check, so if I go to ask to activate F2 / F3, the bot active it without any check.

But… if the virtualkey is F1 mean Im asking to activate the main attack of the squadrons, this mean I need to check if the attack is already active.

so I chekc whit this condition if the squadrons are already shooting

if ((firstyAbilityArray != null && firstyAbilityArray[2].RampActive == true) && (secondAbilityArray != null && secondAbilityArray[2].RampActive == true) && (thirdAbilityArray != null && thirdAbilityArray[2].RampActive == true))

If all of them are shooting I break the function, else I continue to activating the main attack, then I check if the speed bonus is already active or no, whit this condition:

if ((firstyAbilityArray != null && firstyAbilityArray[1].RampActive == true) || (secondAbilityArray != null && secondAbilityArray[1].RampActive == true) || (thirdAbilityArray != null && thirdAbilityArray[1].RampActive == true))

If all squadrons have the speed bonus active I break the yielde, else I active it too

Maybe is not what you want do, but here you can see in action how I read etc…