#Starlancer AIFix v3.13.2 | EnemyEscape v3.0.0
1 messages ยท Page 7 of 1
the emit delegate stuff
is my understanding correct then?
mrov didn't highlight the ldloc_1 
๐ฅบ
feels weird to me to have self be the last argument but I'm sure that's because they emit the ldarg.0 immediately before the call
personally I would just emit the ldarg.0 myself to make it the first one lol
they do yeah
wait who are you talking about? did you look at the MonoMod source?
oh sorry, the example code I shared had
c.Emit(OpCodes.Ldarg_0); // load argument 0 'this' onto stack
right before the EmitDelegate
wot
@twilit drift you didn't load arg 0 in the code you linked 
is it automatic or not??
Just for you to look at yourself in full, this is the example I'm talking about
https://lethal.wiki/dev/fundamentals/patching-code/monomod-examples#my-first-ilhook
yea lol
if I'm understanding things correctly (probably not) then I just need to load arg.0 and i4.0 on the stack, not emit a ldsfld, and from there let the EmitDelegate handle the method?
๐
idk if it works, but I loaded up with no error this time
๐๐
what's the constant 0 for?
can you post a diff of what you're trying to do to the vanilla method?
from what I saw earlier, you only had a self argument to your delegate
tbh I'm not sure how, I see nothing in EmitDelegate on GitHub that indicates it tries to load this
I am not sure as well
what's loc 1?
I'm planning to change it to a harmonytranspiler instead of ILmanipulator
(where's the comment about what the load is for
)
๐ณ
// // to be completely honest, I have no idea what I'm doing
// // https://github.com/SylviBlossom/LC-SimpleWeatherDisplay/blob/2d252b92dcd4d8ef259b8072d9339ff5ccdc4d0b/src/Plugin.cs#L127-L153
// // this is just this, but repurposed
here
also looking at EmitDelegate it seems like I was right not to want to use something like that
it stores a reference to the delegate in a list to keep it alive for the duration of the program, whereas if you just emit a call yourself you can avoid any indirection
oh I missed that
man, it might help if I turn warning logs back on in bepinex
prior to this I was trying to test something I needed loginfo for, and meaningless vanilla warnings (failed to init audio spatializer) were spamming
so I just wanted peace
mrov needs lotsa spaghetti!!!!
true those ones are very annoying
I won't ever turn off warnings though, I don't trust myself to remember to turn them on again
I don't mind copying logs into npp and scanning them
understandable lol
okay, well I load in with no IL error, but my code isn't firing
I was really hoping to see logger.LogWarning("Baboon hawk is trying to carry scrap back to the nest!"); flooding in ; w ;
I guess? xD
@twilit drift I understand why it works now, ldloc.1 isn't loading the string, it's loading this but within a generated Enumerator.MoveNext()
silly enumerators
oh gosh
what do your codes look like now?
I'm not surprised I didn't understand what I was doing ๐
also why are you planning on changing to a transpiler?
it's better for the stuff I'm doing?
well, I haven't directly compared them, but I would imagine ILCursor is a nicer experience than CodeMatcher
I'm gonna screenshot it so I don't get thrown in jail again
you can emit the same code with ILCursor that you can with transpilers
I know, but I've played with CodeMatcher and it was easier to understand then that
oh? huh
to me it seems like the basics of IL are super unintuitive (alphabet soup nonsense) but I feel like I'm getting somewhere
I would've expected this not to pass validation
ยฏ_(ใ)_/ยฏ
you only have one argument to your delegate but you're emitting two instructions that push to the stack
I'm not sure if MonoMod validates the IL, but if it was doing so I would've expected it would not be very happy about that
well removing the ldc didn't throw any errors so... idk
I'm just trying to figure out the process lol
I guess having an extra value on the stack isn't necessarily fatal unless the VM doesn't like it when ret happens with a non-empty stack
and I don't know if that's valid or not
well it's gone now anyways
yeah, I'm mainly trying to think through whether it should've detected that after the patch ran
wait
you don't even have those in the order it would need to be in, that should crash
so maybe that's one point for Harmony
I'm pretty sure it would've seen that the call is trying to pop a value of the wrong type off the stack and complained
yeah, looks like a transpiler explodes when I do that
so maybe you should switch over to a transpiler and use CodeMatcher instead
it'll be much easier for you to start out if it actually tells you when your code doesn't make sense
it's not the best at telling you why, but you'll at least see an error on startup instead of it happening when your function is finally called
the CodeMatcher functionality seems quite similar to what you're working with right now as well
so
you had
ldarg.0
ldc.i4.0
call delegate void(BaboonBirdAI)
if you go through that in order, it would:
- push BaboonBirdAI this
- push i32 0
- pop BaboonBirdAI arg0, receive i32 instead
it can detect that the call is trying to pop a BaboonBirdAI instance and getting an i32 instead, and say "mmm I think this isn't quite right
"
but it didn't
transpilers would
huh, why didn't MM explode
so they are better
or at least return an error
it's because transpilers specifically validate the IL they generate after they finish
ah
it may also apply to ILManipulators but I'm not sure
well even after removing the ldc it still doesn't work
yeah
if it hit your code you would've gotten a weird error
did you print a message when it runs your patch so you know that it's actually running it?
Print the il to see if it's actually changing at the end
not the delegate, but the code that generates the IL
:p
is it possible for it not to change it?
if the ILCursor is invalid and you try to emit, does it eat the error?
I'm gaming, what
lol
would it be possible for an ILCursor to not modify things if it's told to emit? I assume it wouldn't eat errors
LC
I put it with GameNetworkManager, so if it wasn't loading then none of my methods should be running on an enemy, but I went ahead and added a log beside it
is that printed inside your ILContext function? if not, then it's not really a good test
oh I can put a normal log in there? I assumed it had to be all IL stuff only
you're sending a delegate to MonoMod and then assuming it's running it
it's just any other function, yeah
you could do all kinds of messed up stuff in there
Ayoo what happened to playing the update together 
@faint copper hoping this is what you meant lol
ah yes
are you finally gonna update it
the enemyescape mod
you should also be able to log the ILCursor instance to get information about the code generated, I believe
hopefully with enough IL context to be able to see if it worked
although now what I wonder is, have you given it time in-game to actually have a little guy pick up an item?
you'd have to scatter a lot of items around for them to find one I think
I'm will say this once. I will update it when the update is ready.
I always make sure it picks up an item
ah ok hmm
last test I made it real quick and spawned an item for it to pick up lol
did you check that the behavior state corresponds to the one you're injecting into?
lemme doublecheck
on next run it would be good to get the final IL you generate by printing the ILCursor and then check with UnityExplorer whether the baboon hawk is in the correct state
Imperium shows me a neat little box above the enemy with the info
How would I print the ILCursor? Just log $"{ILCursor}"?
print your local variable instance of it, but yes
logger.LogInfo(c);```
indeed
also... aren't you injecting into behavior state 0 and not behavior state 1 where it tries to navigate to the camp? can you show me the code you're trying to inject into in context?
When's the lethal escape update
what's wrong with the current update anyways lol
are people just complaining for no reason?
nothing's "wrong" with it necessarily
Are there people complaining?
not you, but [this person](#1206494982521753620 message) has asked a similar question at least once before iirc
if not more than twice already
the actual code from the hawk's DoAIInterval?
yes
Though tbf users are usually split up into two categories, the constant askers, and the constant supporters who are low key askers
it's supposed to be in behaviour state 1 when it's carrying the scrap to the camp
I haven't gotten that much of that, but I guess most of my mods are for nerds
but my code shouldn't care for the purpose of what we're trying to do rn
Me neither since I'm not popular :3 but I've seen some mod creators get asked daily lol
oh wait I forgot you had it checking if baboonCampPosition was loaded, never mind me
but yeah lemme know when you have that printout of the generated IL
IL_0000: ldarg.0```
wot
if the answer is that it's at the start then you should move it to the end
we need to see it after you make changes
understoodable
It's okay, il hooks killed me today too
I just hope that it's gonna print out more than one instruction
I couldn't even manage to do the simple il hook I was doing lol
the MonoMod docs say that it should print "surrounding instructions" so I'm not sure why you only saw one
Whatd you print specifically
[Info :Starlancer EnemyEscape] // ILCursor: System.Void DMD<BaboonBirdAI::DoAIInterval>?-812375320::BaboonBirdAI::DoAIInterval(BaboonBirdAI), 369, None
IL_0000: call System.Void MonoMod.Cil.RuntimeILReferenceBag/FastDelegateInvokers::Invoke<BaboonBirdAI>(T1,MonoMod.Cil.RuntimeILReferenceBag/FastDelegateInvokers/Action`1<T1>)
IL_0485: ldarg.0```
okay I'll try that
wow I guess "surrounding instructions" is one instruction following the current one lol
nope here we go, one sec
Yep he's got it lol
it's gonna be a big print for this method which is why I wanted ILCursor to work
I usually just screenshotted the relevant parts
Otherwise it just looks like a huge mess
But idk what you're looking for specifically
I wanna let Zaggy decide what's relevant
yea I just felt like presenting the whole log rather than risk somehow leaving something relevant out
That's fair
Good luck zaggy
(Its not too hard just annoying to read at first lol)
I'm glad I have a second monitor
it's fine
And here I was being told I learned nothing from the il hooking I did today ๐ฅบ
does GotoNext() put you at the index of the first predicate instead of after the last??
I'm gonna say yes because I think i know what you're talking about but idk what predicate is
the code should be at IL_0485, which right before that is some IL_0000 stuff which I'm assuming is supposed to be me?
I had to do moveType.after and then - index by 1
predicate is the lambda that returns a bool to determine whether an instruction is what you're looking for
Ah icic
wait I think I goofed, lemme check something
yeah I'm gonna be honest the fact that the MonoMod docs don't say where the cursor ends up for a predicate array with length > 1 is kinda annoying
I'm gonna assume that it puts it at the start and you need to += predicates.Length; which isn't exactly ideal since that means you have to toss a magic number or local array in there
it looks like your Index-- is going here and then deleting this instruction:
Yea I was dumb and meant to get rid of that attempted removal
well
the removal is fine if you pop twice three times
actually, since it expects it to push a value two pops should do
lol
regardless, removing the removal didn't change any of the surrounding code (except for not removing 0483)
yeah it wouldn't
you were deleting the branch instruction I showed above
if you want to keep using ILCursor, you would need to c.Index += [number of predicates]
but... I'm gonna be honest, it seems like CodeMatcher's MatchEndForward function would be a lot smoother for this
I actually had thought that ILCursor had similar functionality, but the docs don't mention anything that I can find
if you want to use CodeMatcher, you can just give your function a [HarmonyTranspiler][HarmonyPatch(Type, string)] and then construct the CodeMatcher(IEnumerable<CodeInstruction>, ILGenerator) from your transpiler's parameters of the same type
then harmony.PatchAll(Type) as usual for prefixes and postfixes
At this point I might jump ship yea @_@
I remember trying to use CodeMatcher and something was going wrong tho
I'm sure we can figure it out
and I think it was somehow mrov's fault?
uh oh
thrown under the bus yet again
at this point mrov might as well just become the bus's skid plates
because i was using different harmony version because i was targeting net48
oopsie
yea that
so does that mean you already know the solution?
nope
what does your csproj look like?
I think that's what lead to me going to MM
sec
@faint copper
lemme just paste it
or not, it's too long lol
there we go
I'm a fool that's the old one
aspodfijasopdfij
try this for your packages and remove the references to BepInEx, Harmony, HarmonyX dlls (and the MonoMod ones if you don't need them)
<ItemGroup>
<PackageReference Include="Mono.Cecil" Version="0.11.5.0"/>
<PackageReference Include="BepInEx.BaseLib" Version="5.4.21.0"/>
<PackageReference Include="BepInEx.Core" Version="5.4.21.0"/>
<PackageReference Include="BepInEx.AssemblyPublicizer.MSBuild" Version="0.4.1" PrivateAssets="all"/>
</ItemGroup>
ope one of the lines changed color when I didn't mean it to lol
no warnings or errors!
and a much cleaner dependency list lol
If I ever hit the lottery @faint copper , you're getting at least ten dollars of it
I'll buy a half a jalapeno burger with it
๐คญ now to figure out what I'm doing here
Okay, so it seems like opcode matching is still pretty simple, but for ldsfld I'm not sure of the syntax. The example I'm looking at on BepInEx's github wiki has this:
new CodeMatch(i => i.opcode == OpCodes.Ldfld && ((FieldInfo)i.operand).Name == "test"))```
To which I assume I can swap Ldfld with Ldsfld, and FieldInfo with BaboonBirdAI
but then I run into not being able to directly reference baboonCampPosition like I was previously
FieldInfo is a type that represents the field that the instruction is loading
what you could use is i => i.LoadsField(AccessTools.Field(typeof(BaboonBirdAI), nameof(BaboonBirdAI.baboonCampPosition)))
or better yet, put the field in a variable so that you're not requesting the field from AccessTools repeatedly:
FieldInfo baboonCampPositionField = AccessTools.Field(typeof(BaboonBirdAI), nameof(BaboonBirdAI.baboonCampPosition));
matcher.MatchEndForward([i => i.LoadsField(baboonCampPositionField)]);
you can also get the field by doing typeof(BaboonBirdAI).GetField(nameof(BaboonBirdAI.baboonCampPosition)), and there is a similar method for getting methods on a type
Would I store the variable within the code instruction?
what do you mean?
like should it be declared within the class or the transpiler itself?
the transpiler is fine, you would only need to put it outside the function if you're going to use it elsewhere
kk
btw you've got MatchEndForward, but mine is MatchForward and I have to declare false/true for start/end
and actually, it looks like CodeMatch can take an opcode and operand argument, so you can do this instead:
matcher.MatchEndForward([new CodeMatch(OpCodes.Ldsfld, typeof(BaboonBirdAI).GetField(nameof(BaboonBirdAI.baboonCampPosition))
)]);
this will pass the FieldInfo instance into the CodeMatch so that it's cached and then it will just compare after that instance each time
if you were to do a predicate instead like i => i.LoadsField([..]) it would evaluate [..] every time, which if you get the field in there can be costly
hopefully that makes sense
I think so, that's what I was trying to do for a bit but I didn't know the syntax of it :P
Alright, so once I've matched the code I want to transpile, do I make an OpCode.call? Also, it's still there from the tutorial example here, but what is InstructionEnumeration()?
IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
return new CodeMatcher(instructions)
.MatchForward(false,
new CodeMatch(OpCodes.Ldarg_0),
new CodeMatch(OpCodes.Ldsfld, typeof(BaboonBirdAI).GetField(nameof(BaboonBirdAI.baboonCampPosition))),
new CodeMatch(OpCodes.Ldc_I4_0))
.InstructionEnumeration();
}```
you said false means start and true means end, didn't you?
if so, you'll want to pass it true
and InstructionEnumeration() just returns the IEnumerable<CodeInstruction> of the modified method body
to make your call, you would want to call Insert(CodeInstruction[]) and pass it an array of CodeInstructions that do what you want
that would go after you find the injection point with MatchForward()
so i.e.
.Insert(new CodeInstruction(OpCodes.Ldarg_0),
CodeInstruction.Call(typeof(YourType), "YourMethodNameOnYourType", new Type[] { typeof(ParameterType1) }))
the bool is useEnd so yea true should be end
I don't know the full context, but wouldn't GotoNext's MoveType.After argument do the job?
the ILCursor is not on an instruction, it is between instructions, similar to a text cursor. So, the first instruction if the previous instruction, and the next is the next instruction
v50
Ah, I missed that looking at the documentation, it probably would
Yeah, I should really work on improving the (unofficial) documentation on lethal.wiki. And perhaps talk with the MonoMod devs about it too, as it could be nice to have better official documentation
Oh, I was looking at the MonoMod docs actually
the table of contents doesn't let you scroll through it so none of the GotoNext methods appear there lol
yeah, that sucks :(
as far as the ILCursor, the documentation also doesn't really make that clear, but I would've thought of its current index as referring to the instruction in the list at its index rather than between them
I suppose it makes sense, but I think it would be nice if it printed more than two instructions, or at least if the documentation made it clear that's what it meant
well, that's why you should generally just print the ILContext il instead
sure, but then you get 10 times as many instructions as you actually need
(in functions that are large obviously)
I'm not sure what the use case for printing two instructions at the position of the ILCursor is
seems like it should be configurable by an argument or just do a wider range
true. How does HarmonyX deal with printing the position of the CodeMatcher or whatever?
But well, I guess it is for seeing where the cursor is, and that's it
that seems so situational to the point of being useless, but maybe I'm wrong
I guess it prints the offset..
It's useful if you don't know how the cursor works I guess
right
I am wondering while you're here though, am I right that MonoMod IL manipulators (or whatever you would call them) don't validate the generated instructions? it seems like earlier Audio Knight was generating some invalid IL and it wasn't printing any errors despite that
So this .Insert() will tell it to call SetDesToPos after matching the call that I want to modify? How would I feed it my little loop like I was trying to use with EmitDelegate?
.Insert(new CodeInstruction(OpCodes.Ldarg_0,
CodeInstruction.Call(typeof(EnemyAI), nameof(EnemyAI.SetDestinationToPosition))))
.InstructionEnumeration();```
I would've expected the code to cause undefined behavior or exceptions when it ran due to popping values of the wrong type off the stack
but transpilers seem to validate that before it gets to running it
They should immediately throw something like invalid IL code if the IL code is invalid, like if you don't pop when you should and stuff
ye
do you know if they both use some standard way of validating or their own differing implementations of that?
I guess feeding the arguments to methods isn't validated then?
No idea about the validation stuff
I tried it on one of my transpilers and it caught it, but maybe it depends
i got errors for doign that tho
oh actually, Harmony borrows EmitDelegate from MonoMod ๐
HarmonyX borrows quite a lot of stuff from MonoMod with IL stuff
well, at least the ILContext/ILCursor
it would, yeah, although there you don't have a Vector3 on the top of the stack for SetDestinationToPosition to use
monomod supremacy mentioned
I would tend to recommend declaring a static method and calling it instead of using that
up to you though, it does make the code a little prettier to use that indirection
Why though?
because from what I saw of the code, it has to statically store a reference to the delegates that are used so they don't get GCed, then make a separate function call just to get it later when it needs to be called
huh
it's probably not super inefficient since it's in an indexable list but I'm a bit of a purist wrt my code injection
if I can avoid overhead I do
saving the precious nanoseconds or something ๐
yeah
premature optimization is the root of all evil but I can't help it
it probably adds up but if other people are doing it, I'm a drop in the ocean
idk, people doing inefficient stuff is what makes performance worse, like getting some gameobject in an Update method or something lol
I'll test if the pretty version works first, since I have it already written out ๐คญ
fair enough
whoops...
I won't fault you for that too much
hamunii has 0 faith smh
i was kidding
though i still do something a bit weird in update
couldn't that be an animation?
at least the sizing up part
you want me to animate the forest giant old bird driftwood etc?
I also haven't looked at how MonoMod's mmhook works, but I'm a little concerned that adding wrappers for a bunch of functions adds a lot of overhead
since those are more common it would be more of an issue than any IL manipulation
MMHOOKs use a thing called the HookEndpointManager afaik for managing the hooks
Okay, so now I need to register the transpiler, yea?
in Harmony you just tell it to patch from your annotated class
Oh yea
Harmony.PatchAll(typeof(PatchClass))
But you probably shouldn't be concerned about that, since just applying and undoing hooks is expensive in general
so if there is any perfomance cost with using MMHOOK, it's nothing compared to that ^
but applying/undoing would only be done once, right?
my concern is about the cost at the call site, since a fair amount of mods I think use MMHOOK to hook into Update() methods
Harmony didn't scream at me ๐
it looks like HookEndpointManager just holds a couple of dictionaries, not sure if I'm missing some references to those dictionaries in the rest of the MonoMod codebase though 
Yeah, you should only do it once. And you should probably have a boolean to determine whether or not your should hook should run your stuff if you need to disable it sometimes or enable it other times
There is some performance lost with detouring, but not much
OKAY so its not overriding it, but my thing is being called!!!!!
quite new :3
So I guess maybe ILHooks are more performant? dunno
Alright, now that I've got the code actually firing, I'm not sure why the destination isn't getting properly overridden
also I'm renaming self to hawk to that I don't get put in jail again if I share the code
the naughty word included self?
coilheads are immune
I assume it only detours methods that mods add delegates to the hooks of? But even with that in mind, I feel like the number of Update() hooks could still be problematic given how attractive the easy approach is for a lot of mods, I'd be interested to know just how much overhead it is
I would think so, although using a delegate in an update function probably would still introduce some
just in case, I'll dm it to you lol
lol bet
I'm having trouble figuring out where the hooks are actually injected, even looking at a decomp of MMHOOK
not done there, see monomod.runtimedetour
this is what mmhook uses https://github.com/MonoMod/MonoMod/blob/master/MonoMod.RuntimeDetour/HookGen/HookEndpointManager.cs
right, I was in that file and I didn't see any noteworthy references to the dictionaries it stores
oh wait, does the magic happen in the Hook constructor? lemme check
there is definitely stuff in the constructors
@faint copper I've tried a few iterations of my code, but it seems like I need to completely replace the original call for this to work
you can add branches if you need to skip vanilla code
but...I really think this would be easier if you just injected into SetDestinationToPosition...
if you do it in SetDestinationToPosition, you can just modify the destination as you see fit since it's an argument instead of a static field
and it'll take care of more AI than just the baboon hawks
oh, that's not what I meant
you should change the destination there, not skip the function
I'm unsure what to change the destination to tho
well, if an AI wants to path to a location that is outside of its current domain, it needs to find an exit, yes?
you were trying to do something similar in the baboon hawk's DoAIInterval I believe
ah, so maybe something like this?
bool nodeInOtherArea;
if (isOutside)
}
//loop through inside nodes and if any of them are within, let's say 20 units of Vector3.Distance(), set nodeInOtherArea = true and break the loop
}
if (nodeInOtherArea)
{
//loop through outside teleports, pick the closest one, set position to it instead of the original position passed to SDTP
}
else
{
//keep original position
}```
for checking which area you're in, you'd be better off checking the nodes of both areas to see which one has a node closest to the enemy, but otherwise yeah
there's no real guarantee that a node will be within 20 meters, but you can be more sure that the closest one will be in the area that the enemy is in
if you cache an array of Vector3 for each area then you can check that very quickly
oh fair, yea checking the closest node's tag would be way better
I would keep a separate array of Vector3 for each area so that you can just loop through them really fast
I could certainly make an array in StarlancerAIFix when I run my FindNodes method and cache it
contiguous memory helps a lot for stuff like this
exactly
you already are caching the node objects, so you could probably get away with caching their locations as well
at least assuming we aren't going to see mods moving AI nodes around (which would be very strange imo)
that would be incredibly weird lol
would it be better to make a dictionary for this?
a dictionary? how would you use a dictionary?
so that I could cache a node's position with its tag?
I'm not 100% sure of the logistics lol
Vector3[] outsideNodePositions;
Vector3[] insideNodePositions;
with this, you don't need to care about tags
tags are inefficient
yea u right
getting anything from the Unity runtime is slow
.transform is slow, .position is slow, .rotation is slow, .gameObject is slow, etc
if you're doing those in a loop 200 times, it adds up
if you have an array of Vector3, all that gets put in your CPU cache and then it just blazes through the calculations
if you use a Dictionary, you're splitting those up into separate buckets which can result in blank spaces that have to be checked, or even indirection, which isn't great for CPU cache
(speaking about iterating a Dictionary that is, but getting values from every key in the Dictionary is also similarly inefficient)
if you absolutely needed to keep track of the tags (like if there were an unknown number of AI node tags), you would be better off doing either
(Vector3, Tag)[] nodePositionsAndTags;
or
Vector3[] nodePositions;
Tag[] nodeTags;
not sure which would be more efficient though
Understood ๐ซก I'll keep it simple then lol
for (int i = 0; i < insideAINodes.Length; i++)
{
insideNodePositions[i] = insideAINodes[i].transform.position;
}```

Alright, the original code is:
public bool SetDestinationToPosition(Vector3 position, bool checkForPath = false)
{
if (checkForPath)
{
position = RoundManager.Instance.GetNavMeshPosition(position, RoundManager.Instance.navHit, 1.75f);
path1 = new NavMeshPath();
if (!agent.CalculatePath(position, path1))
{
return false;
}
if (Vector3.Distance(path1.corners[path1.corners.Length - 1], RoundManager.Instance.GetNavMeshPosition(position, RoundManager.Instance.navHit, 2.7f)) > 1.55f)
{
return false;
}
}
moveTowardsDestination = true;
movingTowardsTargetPlayer = false;
destination = RoundManager.Instance.GetNavMeshPosition(position, RoundManager.Instance.navHit, -1f);
return true;
}```
Would I want to insert my code before, and if it determines the position is in another area it returns true earlier than the original code and thus takes precedence?
(I'd also need to toggle those two bools toward the end)
Also, am I able/would I need to ref the original position and checkForPath?
Just for clarity, I wouldn't need to check which area the enemy is in, just what area the path leads to
that's why I was initally talking about "within a distance"
I would just modify the position parameter and then let it run its course
you still can't really know that the destination will be near an AI node, though it is likely that it often will be I suppose, so you could optimize it by checking if it's in a certain fairly small distance and exiting the loops early if so
Alternatively, I could check the node nearest the position and see which nodePositions array it exists in
I think there's a method for that at least
that sounds like what I suggested but without the early exit
nonononono
that one will calculate a path
you don't need to check paths for this, that will be way slower than iterating over every node's position in a Vector3 array
kk, I'm just trying to think of a more accurate way to determine it than a magic number distance
the magic number distance would just be a shortcut, there's nothing particularly inaccurate about it unless the interior literally clips into the moon
hopefully, eventually
but even if it doesn't, if those AI nodes are closer than 10 meters from each other then we probably have bigger problems than an interior being on the same height as the moon
true lol
also I do need to check for a valid path to the teleport, the poor thumper was stuttering hard when I tried to force him up a ladder he couldn't climb
it certainly might make sense to do that, yeah
on most maps that would be 2-4 paths, I suppose, so it shouldn't be a huge deal
not ideal if there's another solution, though
true, but it needs to have a valid path if I wanna set it as a destination
yea but vanilla code doesn't really account for enemies being both inside and outside, except for Masked and snakes
if you wanted to, you could check NavMeshAgent.pathStatus to react to a path being invalid
true, but if it works in the majority of cases then there may be other solutions for the niche problems
So instead of
NavMesh.CalculatePath(enemy.transform.position, teleport.transform.position, enemy.agent.areaMask, pathToTeleport);
if (pathToTeleport.status != NavMeshPathStatus.PathComplete && Vector3.Distance(enemy.transform.position, teleport.transform.position) < closestTeleportDistance)
{
position = teleport.transform.position;
}```
I would set it and then check if the path was invalid without calculating it myself?
`if (enemy.agent.path.status != NavMeshPathStatus.PathComplete) { }`
it's asynchronous, so not immediately
pathPending has to be false
this would be more a thing to check on the next AI interval
You have some great stuff cooking over here. Can't wait for it to release
Ah okay. I have to go to dinner and movie, so I'll be back later to hammer away lol
I'm 99% sure it'll be worth the wait!
When this code is triggered by an attempt to path to another area.
You'll need to record the original destination.
Slip a call to the rest of the mod to schedule the teleport after reaching the exit.
And restore the original destination pathing after the teleport happens.
With those steps, the areas would become fully seamless
I don't think storing the original destination should be necessary, since the behavior state repeatedly sets the destination
if the AI is forced to update immediately after a teleport, which I believe he already made them do, then they should immediately start moving to wherever they wanted to go outside
or inside in the reverse scenario
Is that specific to the behavior of current enemies, or something that is done by the base enemyAI class?
it's just a common pattern
we could definitely verify that it's consistent, but I don't think that would be an issue
Seems inefficient to spam pathing attempts, when the destination is stationary. The first pathing could be the only pathing
I kinda doubt it is
I'm sure that the Unity runtime tracks whether the destination or the navmesh are modified
it would be kinda silly if it didn't
even if not, it's a vanilla thing, it's not like we can easily change that
Hmm, do you mean that calling it again with the same destination would reuse the pathing from the last call?
I have assumed that enemyAI would call SetDestination on the event. And then wait for the agent to reach the destination.
If the enemyAI class stores the desired destination and calls SetDestination on every update. Then yes we don't need to store it.
But if that is done by a behavior of each enemy, and not the class, then future/ modded enemies could break that assumption
SetDestinationToPosition is a method of the EnemyAI class
Or type, whatever the terminology is lol
Oh I guess this is missing from the codeblock. It's calling that on the event, then position gets saved to enemyAI.destination
Then yeah we don't need to save it.
||Are outside enemies supposed to travel inside with EnemyEscape? xD||
Ye
You can disable it per enemy in the config
Is there something causing a problem?
||Ah, nah I just saw a dog inside on Lunx's stream and was confused xD||
yeah, I would expect it to avoid that work
the only time I know of that it clearly doesn't is if an invalid target location is set, because when that happens, they reset their target to some default location
Sorry, I think I'm missing something, the function recognizes if the position being fed to it is the same as the previous position?
not the vanilla function, the Unity runtime
if you were to tell an agent to go to the same place twice, I would expect it to not cancel any existing pathing
ah
neat
that would line up with the blob's debug nav-line ceasing its flickering after I tell it to go somewhere I guess
@faint copper @copper yew @twilit drift idk what time zones everyone is in, so none of y'all might be up/available, but if I want to insert my code at the beginning of the method, would I just:
return new CodeMatcher(instructions)
.MatchForward([useEnd = false],
new CodeMatch(OpCodes.Ldarg_2)
Or is there some other logic I have to grab for this?
if you're just running code before the original, couldn't you just do a prefix patch
I don't know anymore ; w ;
depends
return false prefixes still run other patches applied to that function
which sometimes can be problematic
I will say I don't find the Dog going inside to be balanced
XD
Like fr I don't know what the right answer is. I've been focused on learning transpiling bc originally I was gonna insert the code into the BaboonBirdAI.DoAIInterval(), but Zaggy suggested just preventing issues in EnemyAI.SetDestinationToPosition() in the first place, which I agree with, but we never discussed switching from transpiling
I don't think I'd really want to return false, I would be just be adding some of my own logic (if that's how prefixing can work, which idk)
it is
I was about to say that's your problem, but then I remembered I made a "balanced" preset, so I guess it is kinda my problem
Haha
Earlier I went into a Fire Exit and there was a dog that went inside that was instantly aggro'd and it attacked me
XD
I was like "Bruh all I did was walk inside why was that dog so angry?"
โ ๏ธ
Fair point, I'll change the default to 0 bc it is a one-hit-kill after all
Haha yeah
If I prefix it, I need to end it with return true, yea?
if your never returning false the function can just be void
Are you planning to add the functionality for some enemies like Hoarding Bugs and Brackens to be able to spawn outside btw?
Wasn't planning on it really, but I guess it could be within the scope. I'd probably default that to off if I implemented it
but that's a future consideration
If I can get this working, I can start tidying up for release lol
Prefixing might actually be better in this case since I could ref and manipulate the initial variable calls too, which CodeInstruction wouldn't let me do
Running into a new error this time. The only code that's new is the caching of the positions, and the IL error points to:
// for (int i = 0; i < outsideAINodes.Length; i++)```
Is FGOWT too slow for it to correctly get a length in time?
so the game doesn't calculate the length until after the if statement is done? I thought that it'd be usable after assigning the new value to the array
or means if any of the three are true it runs
which means it checks all three
and if that first one is null..
I do and don't understand. The code runs top-down right? So it shouldn't be null when it gets to the loop
but if it is null
your checking it's length and first index inside of it
in the actual if statement
i think
im doubting myself a little now
let me do a quick log test
is outsidenodePositions null?
yep that was it
added outsideNodePositions = new Vector3[outsideAINodes.Length]; to the code and it was able to log all the positions
Code still isn't where I want it, but no more errors at least ๐
a prefix would be fine here, you can just modify the position as a ref parameter to your prefix
if you use a transpiler, I think that CodeMatcher would just start at the beginning of the function, so you can immediately begin inserting instructions without calling MatchForward
copy that
Like many programmers, I feel like my current code should be doing what I want, but somehow the initial position is still briefly getting thrown in as an actual position assignment
[Warning:Starlancer EnemyEscape] Original Position: (-35.09, -2.43, -11.97) //The position passed by the call to SDTP (baboonCampPosition)
[Warning:Starlancer EnemyEscape] Target position is outside, checking for reachable teleport.
[Warning:Starlancer EnemyEscape] Teleport found, changing destination to (-26.26, -224.23, 46.90).
[Warning:Starlancer EnemyEscape] Final Position: (-26.26, -224.23, 46.90) //The position of the teleport found by my code```
wait, where? the logs you show here seem like they look about right for what I think it should be doing
The hawk is still trying briefly to go towards the camp while its inside, which makes it path away from the teleport for half a sec, path towards for half a sec, ad infinitum
so it's like the destination is still being set to the camp somehow
hmm
also that's my point ๐ญ
yeah your phrasing didn't really make it clear what was going on
fair lol
now might be a good time to install your mod into the asset rip project to see the AI debug gizmos
but if you look the decomp, it uses EnemyAI.destination in the EnemyAI.DoAIInterval() function in the base class, which means that it sets the destination before your code takes effect every interval
oh dammit
which means that if anything sets the destination to somewhere else in the meantime that'll take effect instead
it doesn't appear that anything else assigns to destination which would interfere with it, so it should work
you should see if the destination is being set to somewhere else
alrighty
any specific gizmo that you're thinking of in the debug thing? bc I'm using Imperium which gives me a full navmeshpath drawn out and tells me the behaviour state
as in it shows the agent's path? or does it listen for some EnemyAI function and generate a separate path to display?
if you were in the editor it would execute this
public virtual void OnDrawGizmos()
{
if (base.IsOwner && debugEnemyAI)
{
Gizmos.DrawSphere(destination, 0.5f);
Gizmos.DrawLine(base.transform.position, destination);
}
}
I don't know what Imperium uses for that so I couldn't tell you whether that is a useful debugging tool
@faint copperbtw NavMeshAIPath or whatevs its called provides .corners[] which is an array of positions used for the path
which you can use for a mroe accurate debug path
yeah, certainly
that could be used to debug what the actual agent is doing, but that above code is vanilla so it would have to be injected in there
it also lets me visualize all the nodes and stuff
Yea I'm gonna try setting the agent.destination in my thing as well. I wasn't expecting the final destination to change in this, since I didn't set it, but I figured I'd log it anyways lol
[Warning:Starlancer EnemyEscape] Original position argument: (-54.48, -3.85, -6.51)
[Warning:Starlancer EnemyEscape] Original destination: (-54.48, -3.85, -6.51)
[Warning:Starlancer EnemyEscape] Target position is outside, checking for reachable teleport.
[Warning:Starlancer EnemyEscape] Teleport found, changing destination to (2.11, -211.73, 65.89).
[Warning:Starlancer EnemyEscape] Final Position argument: (2.11, -211.73, 65.89)
[Warning:Starlancer EnemyEscape] Final destination: (-54.48, -3.85, -6.51)```
I didn't mean agent.destination, I meant EnemyAI.destination
it'd be good to figure out why the AI isn't going where you expect, since it should be going to your overridden location after 200ms
the reason I say you should look at it in the editor is then you can see specifically whether EnemyAI.destination is changing
unless you want to look at the Imperium source code to figure out what path it's visualizing exactly
what I would want to verify is that EnemyAI.destination remains what you set it to until the next call to EnemyAI.DoAIInterval
that's actually what I did instead, but still no luck, so I guess I'll try the editor
[Warning:Starlancer EnemyEscape] Original position argument: (22.27, -2.96, 37.38)
[Warning:Starlancer EnemyEscape] Original destination argument: (-113.19, 3.04, -17.65)
[Warning:Starlancer EnemyEscape] Final Position argument: (22.27, -2.96, 37.38)
[Warning:Starlancer EnemyEscape] Final destination argument: (-113.19, 3.04, -17.65)```
wait what? then what were you doing before?
how would I detect that specifically?
I mean instead of setting agent.destination lol
you're being very confusing with the order in which you tried things and what exactly they did
pls post code for what you had before and/or now
yea sorry, I'm throwing things at the wall and seeing what sticks lol
it's very difficult to guess what's going on without knowing what precisely you're doing
there are so many ways it could do what you said without knowing that it's written the way I expect
okay well the context of that last block is that the hawk was on its way to the main entrance teleport when it decided to switch to state 1 and then the position argument and enemy.destination ended up mismatching, so the code that was affecting the pathing was this
edit: This is in my escape component Update() triggered on a 1s interval
This block was created by this:
logger.LogWarning("SetDestinationToPositionPrefix is trying to do something!");
logger.LogWarning($"Original position argument: {position}");
logger.LogWarning($"Original destination argument: {__instance.destination}");
bool destinationInOtherArea = false;
bool teleportFound = false;
NavMeshPath pathToTeleport = new NavMeshPath();
if (!__instance.isOutside)
{
foreach (Vector3 node in AIFix.insideNodePositions)
{
if (Vector3.Distance(node, position) < 10)
{
destinationInOtherArea = false;
break;
}
else { destinationInOtherArea = true; }
}
if (destinationInOtherArea)
{
logger.LogWarning("Target position is outside, checking for reachable teleport.");
float closestTeleportDistance = float.PositiveInfinity;
foreach (EntranceTeleport teleport in insideTeleports)
{
NavMesh.CalculatePath(__instance.transform.position, teleport.transform.position, __instance.agent.areaMask, pathToTeleport);
if (pathToTeleport.status != NavMeshPathStatus.PathComplete && Vector3.Distance(__instance.transform.position, teleport.transform.position) < closestTeleportDistance)
{
checkForPath = false;
teleportFound = true;
position = teleport.transform.position;
__instance.destination = RoundManager.Instance.GetNavMeshPosition(position, RoundManager.Instance.navHit, -1f);
}
}
if (teleportFound) { logger.LogWarning($"Teleport found, changing destination to {position}."); }
}
}
logger.LogWarning($"Final Position argument: {position}");
logger.LogWarning($"Final destination argument: {__instance.destination}");```
where are the methods' declarations?
Sorry, edited that first thing to say it was my escape component's Update() on a 1s interval
Second thing is the SDTP prefix
can you paste the prefix declaration?
[HarmonyPatch(typeof(EnemyAI), nameof(EnemyAI.SetDestinationToPosition))]
[HarmonyPrefix]
private static void SetDestinationToPositionPrefix(EnemyAI __instance, Vector3 position, bool checkForPath)```
you didn't make your position parameter ref, it won't modify the value in the caller
I tried that before, but I'll try it again
alright reduce me to atoms
it's sort of working
I swear I tried the ref thing before, ya gotta believe me D:
it might've had some other issue keeping it from working then
it definitely has to be ref, if it isn't ref it just modifies your local argument and then tosses it
alright well the screwiness I currently have I know the reason for, so I'll report back in a few
there's still some optimizations to be done as well, but for now my goal is functioning
I wanna say your check for whether the node is inside could sometimes fail since the destination could end up being further than 10 meters from a node, why not check which set of nodes contains the closest node as well as exiting early if one is within that distance?
not immediately urgent obviously but it would probably be good to keep that in mind
yee, good idea
ZAGGY
ZAGGYYYY
VIDEO INCOMING IN A MIN
@faint copper @twilit drift @copper yew @tulip vault
I could cry rn
peak
you're a bad person
/s
huge thanks to all 4 of you, I couldn't have gotten this far without y'all
I'm gonna go to bed now, and in the morning I'll do a brief test of the other enemies and make sure I didn't break any of them horrendously. From there I'll optimize/tidy up the code and then update \[T]/
and yes I have committed this to my local repo
Amazing dish chef, cooking well done โ
oooo very nice! glad it's working
good work! next stop: actual commit messages 
Although zaggy,, I thought we wanted to keep the original destination in EnemyAI.destination so that the enemy remembered where it was going even when it's not spamming EnemyAI.setDestinationToPosition()
And instead change the call that is in EnemyAI.DoAllInterval so that calling agent.SetDestination() would lead to the door
LOL NICEEE
Or is there yet another location that saves our original destination?
the logic that set the destination will re-run and set the same destination in almost all if not all cases, like I mentioned before, so that shouldn't be necessary
the AI interval function runs on an interval, it doesn't stop after it sets a destination
Oh so the call to SetDestinationToPosition() comes from doAllInterval()?
alright.
SetDestinationToPosition() seemed like the kind of function that an enemy mod would call once to order a pathing. And then doAllInterval would use the stored destination from EnemyAI to direct the agent
Which if there is a modded enemy that does it that way. It would get lost after returning to the intended area
yeah, it's certainly possible for mods to do things differently
SetDestinationToPosition doesn't seem to be intended to be spammed every interval.
I don't mean that it's too costly.
But it just sets the EnemyAI.destination
You'd expect that to be done to handle an event in the behavior. Like picking up scrap -> go to nest.
Then the EnemyAI can handle going there without reminders on every interval. Cuz the destination was stored in EnemyAI.destination
there's no reason not to set it every frame, let alone every interval
it doesn't matter what it could be used for, it matters what it is used for
I don't know why Zeekers made it work this way, but it does
maybe there were issues with desync
I know there is no reason to avoid doing that.
obviously we could store the previous destination to make an attempt to make it foolproof, but making a component for something to store that when we can't even test it seems a little pointless
But a modded enemy could do it properly. Even if Zeekers didn't
if there's no bug, we can't verify the fix
it's not "properly" if the agent already does no work when setting the destination to the same location
perhaps it would work fine to do it that way, but I don't see any big problem with how it works now
It's properly, because repeating the call is not required
for one thing, that would mean you would have to write a bunch of code to verify that the asynchronously generated path was valid, and if it isn't, either keep spamming it or try a different one
I've observed agents that have no valid destination continue moving, which means they have a chance of getting unstuck
I'm just predicting a bug when a custom enemy doesn't spam that method. That's all
but I don't know that there's any way to recover if you detect an issue with pathing, because there is probably a root cause that needs to be addressed before writing a bunch of code in the hopes that it will allow AI to leave a stuck state that they probably entered for unforseen reasons
what I'm saying is, why should we predict a bug and fix it when we can't even verify that it will work?
the baboon hawk immediately started pathing to its nest after leaving the interior, so there is no way to know if that happened because of a "bug fix" or because it already worked
if the default settings of the mod don't make any modded enemies path between zones then it's not necessarily "supported" to enable that
so until someone reports such an issue, it shouldn't be a concern
also @gleaming robin not sure if I missed it in the code before, but are you checking whether the enemies have an escape chance before letting them change zones?
This is a potential bug. And that's all it is. What are we unable to verify?
Cause you appear to be saying that we are unable to determine what calls what.
This is a moot point really, if we wanted to get this bug demonstrated, all it would take is an enemy that doesn't repeat that call.
if this bug is a hypothetical, we can't test it
what do you mean "determine what calls what"? I've already looked at the references to the function and I know that the majority are called within DoAIInterval
if you want to make a modded enemy that breaks that assumption so that we can test a way that preserves the destination, then that would be cool
all I'm saying is that reworking to fix some hypothetical bug is both a waste of time and a way to introduce more issues
Is the decompiled code for LC available somewhere?
it's available in a decompiler, of which there are several
you just need ILSpy or DNSpy
So that I don't need to setup the modding environment to look at that stuff
most people don't use an asset rip for that
LET'S GOOOOOOOOOO ๐ฅ ๐ฅ ๐ฅ ๐ฅ ๐ฅ ๐ฅ ๐ฅ ๐ฅ ๐ฅ
you did it!!!!!!!
o.o
They just hide the loot xD
make yippees and baboons fight for their loot nests ๐คญ๐คญ
hmm, probably something for FairAI to look into.
I wonder if in vanilla, a lootbug would get mad at a baboon for taking items from nest.
They'd try to grab eachothers loot if encountered, and probably bug out
But won't attack eachother
I don't think zeekers intended for outside going inside and other way around
Idk if im just crazy but i swear i witnessed them attacking eachother when testing affliction. But then again i might be totally wrong about that xD
I'm only guessing here but I'd be very surprised if zeekers implemented that lol
Agreed
It might just be that collision = damage
Ill try to force it by testing just to confirm
Ye probably
Ahh I was not, but that's an easy fix.
if (chanceToEscape > 0) {}
hmm, shouldn't chance to escape be applied only when randomly deciding to go to the other area?
When they have a reason to path to the other area, failing this check would just break the ai.
and if chanceToEscape is 0 indeed then they shouldn't be in the wrong area to begin with...
Ah but they could've been spawned into the wrong area by the moon author
Maybe if they get spawned into the wrong area, then their nest is also in the wrong area?
so they don't try to change area anymore
That's a fair point
I may have misunderstood the question, but my own statement still stands that I need to implement a check in case an enemy happens to wander close enough to the entrance/exit
Btw since you're including it in this thread are you making it part of ai fix?
Sorry, still waking up, I forgot I already have a conditional to destroy the escape component after creation if chanceToEscape == 0
do old birds attack outside inside enemies?
I don't think so. That's something I wanna do for the next big AIFix update tho
Alright so there's still a thing or two that needs ironing out, but I verified that the Hawk behavior is at least mostly consistent, AND a hoarding bug that leaves and finds scrap outside will absolutely take it back into the facility
with enemies properly pathfinding in and out, will you make brackens able to do this when nobody's inside?
I'd have to hook into it directly, so idk if it'll be this next update, but yea I was planning on it.
very epic
its already good
enough
please update it
for some reason the individual enemy chance configs are gone
gone, or missing descriptions? Cuz I've had that happen to me
u left the game when it was creating the config
thats why
or its a thunderstore issue
only one pack of errors, which is i think incompatibilities between terminalplus and LLL
refresh the config page
i've launched the game like 10 times while it's like this and done full rounds
weird, for me it worked
๐ค
pal you need to stop
did that happen after deleting the config? BepInEx configs are not supposed to clear out unused config options
also, a log would be useful, I believe the hook to register them was in GameNetworkManager.Start() or something similarly required for the game to function
randomly the descriptions for the configs were cleared, which usually means the setting doesn't exist anymore, so i tried deleting the config, which regenerated it with only that
gotcha, that makes sense
would've been good to know first of all if the game was actually functioning, and second of all whether the enemies' configs are registered when they spawned
wait outdoor thumpers is actually kinda crazy on maps like this LMAOO
because I believe Audio Knight put in a way for the enemy configs to be bound when they spawned as well (I hope)
Correct, I have two entry points into the bindings
hmm, will enemyescape allow masked to exit through fire exits aswell instead of just main?
They can't?
they can only go through main
also what in the world
when did the configs start existing
and why does only the flowerman one exist now
also i believe i've found an incompatibility between this and snatchinbracken, if the bracken drags you inside, the game still thinks the player is outside so you get outside lighting and the clock and stuff
does this mod effect enemy spawn rate?
Ahh that would make sense
Not at all
that's likely a combination of an issue with that mod and CullFactory unfortunately
wait
what
they'll need to override SetEnemyOutside and update the flag of the player when dragging them
i don't have cullfactory
the sunlight clips through the walls
hmm lemme check something
wow, I didn't know the sun turned off when you entered the interior
but I suppose that makes sense considering that it has a non-shadowed light for indirect lighting contribution
this still stands though, the flag needs to be set in SetEnemyOutside to account for this
Sorry, is that something I need to do, or SB?
Snatchin Bracken does
you can't know that the bracken is teleporting a player, unless you make a soft dependency on their mod
I will say convincing a bracken to leave the facility is harder than it would seem. I might have to transpile it lol
later tho
trying to make it randomly do so you mean?
or I guess you mentioned making it leave if no players are inside
definitely seems like something you'd have to do a little IL injection for, yeah
Yea it's glued to its favorite spot while no player is inside. I tried changing the favorite spot to a teleport, but that didn't seem to do anything. I'll work at it more later. For now...
โ๏ธ โฌ๏ธ
hmm, are you sure it was trying to go to its favorite spot and not just finding the farthest node from the entrance? I forget which it does in that situation but it does like to do that
Starlancer AIFix v3.6.0 | EnemyEscape v2.0.0
-
StarlancerAIFix v3.6.0
- When finding the AI nodes in the level, AIFix now also caches their locations. This was done primarily for EnemyEscape, but it offers a non-zero performance boost for AIFix as well.
-
StarlancerEnemyEscape v2.0.0
- Almost a complete overhaul from v1.0.0
- Instead of only allowing enemies a chance to use the EntranceTeleports if they happen to wander close enough, a system has been implemented to make an enemy path directly to an EntranceTeleport and warp to its match on the other half of the level. The range at which they may search for a teleport and the cooldown for an attempted search are configurable per enemy.
- "Chance To Escape" now represents the chance for them to initiate pathing to the teleport.
- Implemented a HarmonyPrefix to EnemyAI.SetDestinationToPosition() in order to prevent enemies from getting stuck if the position they want to path to is on the other half of the level.
Source code is now available :3c
https://github.com/AudioKnight/Starlancer
AIFix's source code is so tiny in comparison to EnemyEscape ๐
Don't Touch Me is compatible now btw. I don't know if it can actually move, but at least it doesn't break EnemyEscape now ๐คญ
@spice kindle @tired ore @edgy topaz
yippieeeeeee

Yippie
Thanks
I hope it doesn't break anything else
Ooh wait, does that mean monsters can actually chase you in/out of the facility now?
Last I checked I think he did it based on their behaviour, which is when they're not chasing you, but I could be wrong it's been a while + I can imagine them chasing, you leaving, losing aggro, then they leave, see u again, chase again
Xu is mostly correct. If an enemy is mauling you to death, it won't leave. But if you go outside and its ai continues to target you (or your position) it'll come after you
The size of this changelog truly underscores the 4 days of work I put into v2
Wait ur doing modded monster compat?
Mod enemies are automatically registered, but they default to 0% chance to escape
every enemy is registered by my mod, just some like the lassoman and red pill unimplemented vanilla enemies are blacklisted
even though in my code I was silly and called it a whitelist lol
well on the user end it's just setting the escape chance to 0 or not 0 ๐คญ
the interior/exterior range and cooldown time are also configurable per enemy
Ooooo
read the readme ๐คญ
my config is still broken with 2.0.0 ๐ฅน give me my config back
also, how does chance to escape work? like, when does it roll the chance?
When I saw it being discuss, it was a dice roll once every cooldown of 10 seconds when it qualifies
every 10 seconds is actually pretty frequently
pathing range and cooldown is configurable per enemy
try deleting your config
I've tested multiple times and it should actually re-check the config each load
idk why yours is busted
i have ๐ฅบ
go into your profile, go into the bepinex folder, into the config folder, and make sure it's absolutely gone
actually
what it looks like is something is preventing it from registering
are you getting an error after you click into the main menu?
or do you have a fast startup mod on?
nope
testing right now
[16:22:52.0191936] [Info :Starlancer EnemyEscape] Starlancer is allowing enemies to roam.```
these are the only two logs that mention enemyescape
nothing errors after clicking Online?
send me your profile code
i see why
a enemy need to escape only once to the full config appear
nope, it registers all enemies after clicking Online
then idk
018f0bcc-efdb-b682-008e-00ba9f8fe128
note: i manually imported the beta v50 version of lethallevelloader, which isn't there in the code
I've tested it with LLL v50 and it worked
well
lemme test it with a deleted config and make sure it regens
I just made sure it didn't crash ๐คญ
@gleaming robin
yo
the hoarding bug isn't giving any damage to non-hosts
(only the host is with the mod)
hm, that's an issue i know lethalescape had (hoarding bugs only deal damage to host)
why does only the host have it? I never said anything about it being server-side only
because lethal escape is host only
and this is really similiar
that's a bold assumption to make
well, you can add support to serverside
k, i'm gonna stay with lethal escape then
okay bye
it modifies enemy ai (i think), so that's impossible
no
lethal escape is serverside
and it takes the ai ownership
k
you think?
ehhh yes and no, it does set them outside, which is why AIFix never really touched it, but I can't say for sure exactly if/how it modifies things
the mod is semi-serverside
I thought it was host only
they see the hoarding bug outside
it just doesn't give damage
regardless, unless a mod author says explicitly that a mod works when only the host has it, I wouldn't assume that it is. It's a 30kb file, there is no harm in everyone having it
nah bro, then i'd use lethal escape
cya
and I'm rather tired of you making demands and telling people you know what my mod does as factual information
Send him to the backrooms
I try to be very patient, but my patience has limits
Understandable
Im naturally not a patient person but Iโve been trying really hard
Iโve been doing pretty good
Simply asking questions about mods so I can know itโs progress
It helps a lot
Youโre doin great Audio Knight
Donโt worry bout them haters
woah? I see you reacting, piss off now
What we don't tolerate around here is unneeded rudeness to devs like Knight
Yeah
Here if you need me 
bailiff, whack his pp
WHY DID THAT WORK
@ruby bobcat WALL TURR- I mean, your moons break my mod
i can only guess it's because of the fancy subspecies or whatever (i think rosie's moons has those), but i think those are just texture changes to map prefabs
I think it must be her custom enemy types which are modifications of vanilla enemies?
ye
at least we found the issue
why would a texture change to a prefab break it though
it's something in the scriptable object I think, but we'll wait for her before further speculation
ohh i see how it works, every 10 seconds while they're in a RANGE of the entrance, not always
that makes alot more sense
oh wait no cooldown time depends on enemy
it can vary!
Yep! Fully configurable
@turbid wave I forgot to tag you, but you'll be happy to hear that it's all config'd
default 0 chance for mouthdogs to go inside, cowardice
only because they're a one-hit-kill lol
but that's the beauty of a config, you can jack it up to 100 and give it 9999 units of pathfinding range
absolutely doing that for bracken
actually if a bracken is chasing someone but in range of teleport detection, will it go for the player or the teleport?
bracken doesn't do random pathfinding yet
player
you should read the readme for the known issues btw
(everyone should read the readme)
It's in the name
?
Read me!
oh
yee
I'll have to transpile the bracken code to get it to allow EnemyEscape to tell it what to do
it's real stubborn
what! does this mean
exactly what it says on the tin
it can still potentially follow you out
but like I said, it's stubborn
oh if it rolls the chance even with 9999 range, it'll just not go there?
is that what it means?
currently, my code just ignores Bracken for the random pathfinding, before the diceroll, to not waste any computational effort on it
bc even when I tell it to do something, it doesn't
then transpile it! i want the bracken to be a fiend
Soon!
now!!!!
I just spent 4 days on this, lemme rest ๐ญ

also from what i've heard, hoarding bugs and baboon hawks, if they go inside/outside, they bring stuff to their respective nests?
yep
so it goes
.
vid there of a hawk doing such
i like how he sleeps at the end of the clip, job well done
will you perhaps consider modifying the snare flea code to make them actually work outside?
Wdym? In what way do they not work?
wait, isn't the description for the preset wrong here? @gleaming robin isn't it supposed to only take priority over values set to -1?
also if the author of LethalEscape said only the host needed it that's putting a lot of faith in the networking code of LC lmao
all enemies that I've looked at in the code deal damage on the client side based on the target they have chosen there, so if the target is mismatched between host and client they become useless
I guess the wording is a little unclear, but my meaning here is that values manually set take priority
Lol no worries. The description of each enemy config escape chance says it again in what might be slightly clearer terms
Yeah, I've been reading everything on this thread, so I've seen that as soon as you implemented it. Now I just need to make my own preset and wait for other mods to release for v50.
Oh one thing that didn't end up being configurable. I wanted to give enemies a higher chance of return than that of escape. And currently there is a single value for both.
You deserve a rest tho. Job well done with this mod. You can just stash that suggestion for later.
That should be easy to implement, I'll look into adding it with the next update :3
can you make it so that the AI is just naturally better? thanks
[19:12:13.6619438] [Error : Unity Log] NullReferenceException: Object reference not set to an instance of an object
Stack trace:
EnemyEscape.StarlancerEscapeComponent.BindRegisteredEnemies () (at <a5a07d1df715484e849d3d540a9d2ca0>:IL_0016)
(wrapper dynamic-method) GameNetworkManager.DMDGameNetworkManager::Start(GameNetworkManager)
(wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.TrampolineGameNetworkManager::Start?-1330587096(GameNetworkManager)
LethalLib.Modules.NetworkPrefabs.GameNetworkManager_Start (On.GameNetworkManager+orig_Start orig, GameNetworkManager self) (at <c68aa40cbbae4bd69cd2fcd50c8f1ae1>:IL_0000)
(wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.HookGameNetworkManager::Start?1654550456(GameNetworkManager)
(wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.TrampolineGameNetworkManager::Start?-367031872(GameNetworkManager)
LethalBestiary.Modules.NetworkPrefabs.GameNetworkManager_Start (On.GameNetworkManager+orig_Start orig, GameNetworkManager self) (at <0d184de3cadb4a4a9b8ad443576d9910>:IL_0000)
(wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.HookGameNetworkManager::Start?942834480(GameNetworkManager)
[19:12:13.6619438] [Error : Unity Log] NullReferenceException: Object reference not set to an instance of an object
Stack trace:
EnemyEscape.StarlancerEscapeComponent.BindRegisteredEnemies () (at <a5a07d1df715484e849d3d540a9d2ca0>:IL_0016)
(wrapper dynamic-method) GameNetworkManager.DMDGameNetworkManager::Start(GameNetworkManager)
(wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.TrampolineGameNetworkManager::Start?-1330587096(GameNetworkManager)
LethalLib.Modules.NetworkPrefabs.GameNetworkManager_Start (On.GameNetworkManager+orig_Start orig, GameNetworkManager self) (at <c68aa40cbbae4bd69cd2fcd50c8f1ae1>:IL_0000)
(wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.HookGameNetworkManager::Start?1654550456(GameNetworkManager)
(wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.TrampolineGameNetworkManager::Start?-367031872(GameNetworkManager)
LethalBestiary.Modules.NetworkPrefabs.GameNetworkManager_Start (On.GameNetworkManager+orig_Start orig, GameNetworkManager self) (at <0d184de3cadb4a4a9b8ad443576d9910>:IL_0000)
(wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.HookGameNetworkManager::Start?942834480(GameNetworkManager)
?! How wtf
Like what even broke?
Oh wait
V50?
Are you running Rosie's moons?
ye
yes


