OK, cleaned things up a bit and here's what I've got. Untested, but hopefully things are close enough that you can at least see what I was going for and fix problems as needed/desired.
-
I took some redundant stuff and moved it into a function or into loops. This is critical if you want to keep a handle on code of any complexity...it's hard enough sorting out the spaghetti of nested if/then logic and loops without having to scroll up and down trying to remember "ok, that's where it does blah...i've already looked at that".
Don't ever be afraid to make a new function, even if it's just a few lines. I can't stress this enough. If it's anything the least bit complex, or something you're doing more than once, Function. If there's the least bit of doubt, Function.
-
Next I boxed up the whole blob into a HandlePet function, which I call from a "while 1" loop inside of main. This is the same as saying "while TRUE" or loop-forever. Of course the only way to stop the script now will be to explicitly endscript, which is less than ideal, but we can patch that up in a future lesson
You're usually going to want your main function to look sparse like this unless the whole thing is just a few commands, especially when doing this type of looping. Now if we decide we want to do more than just handle pets, say add a buffing routine or talk to the "outside world" via command-line arguments or user interface pieces, we have an obvious place to put that handling.
-
Now that things are "cleaner", I can see a lot more of the if/if/while/if skeleton of the script, and how things are nested. Here's a "picture" if you will of just the skeleton by itself.
Code:
if ${Me.HavePet}
if ${Me.InGroup}
if ${Me.InCombat}
if !${Me.Target.IsDead} && ${Me.Pet.ToPawn.CombatState} == 0
while !${Me.Target.IsDead} && ${Me.Pet.ToPawn.CombatState} == 1
if ${Math.Calc[${Math.Distance[${Me.Pet.ToPawn.Location.X},${Me.Pet.ToPawn.Location.Y},${Me.Target.Location.X},${Me.Target.Location.Y}].Int}/100]} <= 20
else
while ${Me.Pet.ToPawn.CombatState} == 0 && i:Inc<=${Group.Count} && ${Group[i].ToPawn.CombatState} == 1
if ${Math.Calc[${Math.Distance[${Me.Pet.ToPawn.Location.X},${Me.Pet.ToPawn.Location.Y},${Group[i].ToPawn.Location.X},${Group[i].ToPawn.Location.Y}].Int}/100]} <= 2000
if ${Math.Calc[${Math.Distance[${Me.Pet.ToPawn.Location.X},${Me.Pet.ToPawn.Location.Y},${Group[i].ToPawn.Location.X},${Group[i].ToPawn.Location.Y}].Int}/100]} <= 5
if !${Me.InGroup}
while ${Me.InCombat}
if !${Me.Target.IsDead} && ${Me.Pet.ToPawn.CombatState} == 0
else
if ${Math.Calc[${Math.Distance[${Me.Pet.ToPawn.Location.X},${Me.Pet.ToPawn.Location.Y},${Me.Target.Location.X},${Me.Target.Location.Y}].Int}/100]} <= 20
Now the power of structured programming really shows itself...isn't she pretty? I notice right off that I'm checking !${Me.InGroup} once I've already established that I'm in a group...this code will never run! Looking closer, it seems that I'm doing almost the same things I'd be doing in a group anyway.
I also see some while loops that should probably be simple conditional "ifs". The entire handler is going to be looped, so I have to be careful that my sub-loops aren't going to let the script get "stuck". It might not matter much now, but suppose later I want to write code to take special action if my HP get low. That code would never run if I'm stuck going "am I sill in combat? do I need to send the pet?" over and over. Of course using small sub-handlers that talk to each other is valid as well, but that tends to be a bit more involved and is the type of design decision it's usually best to make early on.
Shifting things around a bit, we have:
Code:
if !${Me.HavePet}
if ${Me.InCombat}
if !${Me.Target.IsDead} && ${Me.Pet.ToPawn.CombatState} == 0
if !${Me.Target.IsDead} && ${Me.Pet.ToPawn.CombatState} == 1
if ${Math.Calc[${Math.Distance[${Me.Pet.ToPawn.Location.X},${Me.Pet.ToPawn.Location.Y},${Me.Target.Location.X},${Me.Target.Location.Y}].Int}/100]} <= 20
elseif ${Me.InGroup}
while ${Me.Pet.ToPawn.CombatState} == 0 && i:Inc<=${Group.Count} && ${Group[i].ToPawn.CombatState} == 1
if ${Math.Calc[${Math.Distance[${Me.Pet.ToPawn.Location.X},${Me.Pet.ToPawn.Location.Y},${Group[i].ToPawn.Location.X},${Group[i].ToPawn.Location.Y}].Int}/100]} <= 2000
if ${Math.Calc[${Math.Distance[${Me.Pet.ToPawn.Location.X},${Me.Pet.ToPawn.Location.Y},${Group[i].ToPawn.Location.X},${Group[i].ToPawn.Location.Y}].Int}/100]} <= 5
So the logic seems to be something like this -
Provided that I have a pet, do the following:
- Check that I'm in combat. If so:
-- Send in my pet if needed
-- If the pet's attacking and close enough, cycle through its abilities
- I'm not in combat, so if I'm grouped:
-- Assist my buddies in order, and have my pet do its thing
Attached is my revision, based on the above and probably with a refinement or two I forgot to mention. To be sure, there's still room for improvement (that distance check is just ugly to look at more than once, and the group stuff needs some work) but hopefully it demonstrates some coding practices that will serve you well in the long run if you keep them in mind.
One last note, I changed the name to VGPetAssist.iss, as a lot of people have scripts for multiple games all stuffed into the same directory.