View Issue Details

IDProjectCategoryLast Update
0023281AI War 2Bug - GameplayJun 11, 2020 12:11 pm
ReporterZeusAlmighty Assigned ToChris_McElligottPark  
Status resolvedResolutionfixed 
Product VersionBeta 2.063 Fixes and Tweaks 
Fixed in VersionBeta 2.065 To Infinity And... No, Knock It Off 
Summary0023281: Hacking not triggering AI response
DescriptionI have not seen a hacking response to any of the typical targets, whether hacking an ARS or the super terminal

Here are a couple example saves
TagsNo tags attached.

Activities

ZeusAlmighty

Jun 10, 2020 9:31 pm

manager  

BadgerBadger

Jun 10, 2020 11:02 pm

manager   ~0057269

Last edited: Jun 10, 2020 11:03 pm

Hey Chris, this is a weird one. For context, what's happening is this. When the AI is going to respond to your hack, it checks for a "hacking strength multiplier" based on the number of hacking points you've spent against that AI. This value is in the AIDifficulty xml:
       hacking_wave_multiplier_1="0.5"
       hacking_wave_multiplier_2="1.0"
       hacking_wave_multiplier_3="1.5"
       hacking_wave_multiplier_4="2.0"
       hacking_wave_multiplier_5="2.5"

It turns out that when we're reading this code in (which we do thusly)
            Data.Fill( "hacking_level_1", ref tempHackingDifficultyLevel, !ArcenXML.ReadingPartialRecord );
            Data.Fill( "hacking_wave_multiplier_1", ref tempHackingDifficultyMultiplier, !ArcenXML.ReadingPartialRecord );
            TypeDataObject.HackingDifficultyMultiplier[0] = tempHackingDifficultyMultiplier;
            TypeDataObject.HackingDifficultyLevel[0] = tempHackingDifficultyLevel;
            Data.Fill( "hacking_level_2", ref tempHackingDifficultyLevel, !ArcenXML.ReadingPartialRecord );
            Data.Fill( "hacking_wave_multiplier_2", ref tempHackingDifficultyMultiplier, !ArcenXML.ReadingPartialRecord );
            TypeDataObject.HackingDifficultyMultiplier[1] = tempHackingDifficultyMultiplier;
            TypeDataObject.HackingDifficultyLevel[1] = tempHackingDifficultyLevel;
....

We seem to be only reading in 0s instead of the actual data. I'm worried that this is because we are now using is_partial_records for AI Difficulty changes in the expansions.

I've checked some defensive code for this in, which will flag this problem thusly:
6/10/2020 8:57:58 PM Post-Proton Surge (0.0097s)
6/10/2020 8:57:58 PM Calibrate Weapons Arrays (0.0220s)
6/10/2020 8:57:58 PM Stimulate Modulation (0.0354s)
6/10/2020 8:57:58 PM Hacking difficulty multiplier for difficulty 1 level 0 is 0
6/10/2020 8:57:58 PM Hacking difficulty multiplier for difficulty 1 level 1 is 0
6/10/2020 8:57:58 PM Hacking difficulty multiplier for difficulty 1 level 2 is 0
6/10/2020 8:57:58 PM Hacking difficulty multiplier for difficulty 1 level 3 is 0
6/10/2020 8:57:58 PM Hacking difficulty multiplier for difficulty 1 level 4 is 0
6/10/2020 8:57:58 PM Hacking difficulty multiplier for difficulty 1 level 5 is 0
6/10/2020 8:57:58 PM Hacking difficulty multiplier for difficulty 1 level 6 is 0
6/10/2020 8:57:58 PM
Error while initializing a table Arcen.AIW2.External.AIDifficultyTable: System.Exception: Could not parse the hacking difficulty

Chris_McElligottPark

Jun 11, 2020 10:57 am

administrator   ~0057273

I think that this is based on the expansions, yes. Basically this is because of how you're initializing things to temporary variables. This pattern will break with partial records:

FInt tempHackingDifficultyMultiplier = FInt.Zero;
Data.Fill( "hacking_wave_multiplier_1", ref tempHackingDifficultyMultiplier, !ArcenXML.ReadingPartialRecord );
TypeDataObject.HackingDifficultyMultiplier[0] = tempHackingDifficultyMultiplier;

The reason that this breaks is that Data.Filln says "read into the variable you pass in, and leave it the same if it is not found." In this particular case, on the partial record you are NOT using the prior value (whatever is set in TypeDataObject.HackingDifficultyMultiplier[0]), but instead you're using tempHackingDifficultyMultiplier = FInt.Zero;

So it will wind up overwriting everything with zero, based on the code above. Even worse, if any difficulty level did have an overridden number, then tempHackingDifficultyMultiplier would stay as that until another number was read. So they might all be 0 until difficulty 5, where you set it to be 6.3 or something, and then it's 6.3 from then on.

The only way to solve this is to use the Fill method how it was intended, which is to pass in the existing value and not a default value like zero. Otherwise if someone legitimately puts in a zero that they want to read, you can't tell the difference.

I imagine that you're using temporary variables like this because you can't pass in an array entry by ref? There's a good reason that doesn't work in C#, but I can see where that would be problematic and wordy code-wise here. I'm going to make a new method that allows for us to fill specific array indices in arrays that are passed in.

BadgerBadger

Jun 11, 2020 11:13 am

manager   ~0057274

Yeah, the inability to pass array entries by ref is why I was taking this approach

Chris_McElligottPark

Jun 11, 2020 12:11 pm

administrator   ~0057276

Badger, I haven't tested the outputs of any of these, but they should be fine. If you have a quick way of verifying that already, that would be great to use. The large bulk of new code is already done, though:

* Added a few dozen new "FillArrayIndex" methods into our ArcenXML class.
** Normally we use a method called Fill, which takes in a variable by ref and assigns to it only if there is actual new data. This keeps things so that they work with is_partial records which are missing lots of values (by design) since they only want to change some values. This would primarily apply to mods, or to some extent DLC. Very occasionally to variant units.
** At any rate, for things that are in an array or a list, you can't pass them in by ref -- so the natural instinct of the end programmer/modder is to make a temporary variable and pass that in. BUT, if they do that without initializing the temproary variable to the existing value of the array at a given index, they'll wind up getting a default value (zero, null, whatever) back in places where a partial record is used. Aka, mods and DLC won't be able to use partial records without breaking something else.
** This new set of methods let you pass in the array or list itself (not by ref since that is implicit on arrays or lists), and the index in it that you want to assign to. This keeps the general pattern of Fill, and gets rid of the need to use a temporary variable, and thus makes the code shorter on the end coder location as well as making it work with is_partial records for DLC or mods.
** Thanks to Badger and zeusalmighty for discovering this problem.

* Fixed three methods in the Mercenary class that were using Fill() with a temporary variable and a string, and then assigning it in. This would not work with partial records. Now it uses the proper FillEnum method, and thus will work with partial records. Since these are not in an array, it doesn't need to use the sub-methods.

* There are a a dozen or so places in GameEntityTypeData where we are using working variables to read in an attribute via the fill method, but these are all already okay for one of two reasons:
** Either they check to make sure that the attribute exists in the current xml file before doing any parsing at all, so they never overwrite with default values.
** Or they read no matter what, but then check to make sure their value is not the default value before overwriting the existing value.
** These are all funky pieces of data, usually lookups into another table or something that requires a modifier before being assigned to the direct value, hence why they deviate from the usual patterns established elsewhere. They should not be emulated unless absolutely required.

* On the AI difficulty table, the following xml is now read in via a loop and using the new fillarrayindex methods, making it safe for is_partial_record.
** income_for_extragalactic_war_tier
** hacking_wave_multiplier_
** The ArcenSparseLookup<> for HackingDifficultyMultiplier was also changed to a List<> to be compatible with this methodology, as well as more efficient in general.
** hacking_level_
*** The ArcenSparseLookup<> for HackingDifficultyLevel was also changed to a List<> to be compatible with this methodology, as well as more efficient in general.
** aip_for_mark_
*** The markless version of this was also renamed to mark_0 in the xml so it could be read linearly.
** These do need to be tested to make sure they're what we want, but they should be good.

* Added a new FillDictionaryKey<> set of methods for most of the data types (strings and numbers) in ArcenXML, so that we can fill dictionary entries safely with these directly, while maintaining is_partial_record compatibility.
** The dark zenith costs and outputs (DLC2) have been converted to use this.
** This also needs to be tested to make sure it works, but it should.

Thanks!

Issue History

Date Modified Username Field Change
Jun 10, 2020 9:31 pm ZeusAlmighty New Issue
Jun 10, 2020 9:31 pm ZeusAlmighty File Added: bug_no hacking response.save
Jun 10, 2020 9:31 pm ZeusAlmighty File Added: no hacking response_bug.save
Jun 10, 2020 11:02 pm BadgerBadger Note Added: 0057269
Jun 10, 2020 11:02 pm BadgerBadger Assigned To => Chris_McElligottPark
Jun 10, 2020 11:02 pm BadgerBadger Status new => assigned
Jun 10, 2020 11:03 pm BadgerBadger Note Edited: 0057269
Jun 11, 2020 10:57 am Chris_McElligottPark Note Added: 0057273
Jun 11, 2020 11:13 am BadgerBadger Note Added: 0057274
Jun 11, 2020 12:11 pm Chris_McElligottPark Status assigned => resolved
Jun 11, 2020 12:11 pm Chris_McElligottPark Resolution open => fixed
Jun 11, 2020 12:11 pm Chris_McElligottPark Fixed in Version => Beta 2.065 To Infinity And... No, Knock It Off
Jun 11, 2020 12:11 pm Chris_McElligottPark Note Added: 0057276