PDA

View Full Version : Scripting help needed: possible to passively capture an npc's ID?



SirKris
09-24-2016, 06:04 AM
Just wondering if it is possible to acquire a creatures ID based an action they take, like spell casting or entering the room, or attacking?

For instance, if I wanted to make a script that casts 703 at an enemy creature prepping a spell, but there are multiple enemies that in the room that share the same creature name, how can I capture the correct ID of the enemy that has actually prepared the spell? Is this possible?

Any and all help would be appreciated. Thanks.

-Kris

Tgo01
09-24-2016, 06:55 AM
You would have to use status_tags in order to grab the critter's ID number when they are casting a spell.

SirKris
09-24-2016, 09:01 AM
This is exactly what I was looking for! Somehow I overlooked status_tags on wiki API page.

So is the best way to capture the ID's presented from status_tags by the use of regular expressions? Or is there an easier way to extract just ID? I'll have to brush up on regular expression if so, but that shouldn't be a problem.

Thanks Tgo!

Oh, also there are two other problems I've been trying to solve for a while, if you have time and feel like answering.

1) Any way to ascertain if flying targets that are out of melee range without swinging or casting first, like with war griffins? Their status seems to always show as "flying around" even after they attack and are in melee range.
2) Is there a way to get the wound location on creatures without having to have to do a look? I played around a little with the Wounds and Scars function, but they only seem to work on myself unless I'm doing something wrong while trying to use them on creatures.

Haldrik
09-25-2016, 06:36 PM
This is exactly what I was looking for! Somehow I overlooked status_tags on wiki API page.

So is the best way to capture the ID's presented from status_tags by the use of regular expressions? Or is there an easier way to extract just ID? I'll have to brush up on regular expression if so, but that shouldn't be a problem.

Thanks Tgo!

Oh, also there are two other problems I've been trying to solve for a while, if you have time and feel like answering.

1) Any way to ascertain if flying targets that are out of melee range without swinging or casting first, like with war griffins? Their status seems to always show as "flying around" even after they attack and are in melee range.
2) Is there a way to get the wound location on creatures without having to have to do a look? I played around a little with the Wounds and Scars function, but they only seem to work on myself unless I'm doing something wrong while trying to use them on creatures.

status_tags simply turns on XML for the script. You would treat it as you would regular lines at that point. So regex capture would be sufficient. You would probably use a while line = get loop. I'd probably do something like this...

castNPC = false

thread.New {
while true
if castNPC
{
cast spell at NPCID
castNPC = false
}
sleep 0.03
end
}

while line = get
if line =~ XMLSTUFF[creatureid]
castNPC = $1 // regex capture
end
end

Keep in mind Ruby has tons of different ways to regex capture!


#1 - No
#2 - No. But you can do a look using DownStreamHookRemove to make look invisible.

SirKris
09-25-2016, 10:42 PM
Thanks Haldrik. I thought that I'd be working with threads and while loops, but I'm a little rusty on my ruby/lich scripting so your example is very much appreciated. And good to know that I can stop wasting time on my other 2 issues.

One last thing, do you think the while loop and single thread will hold up if it receives multiple true evaluations simultaneously? For instance, lets say I want to capture the ID of several creatures that just spawn simultaneously. My thinking is the loop would grab just the first creature ID, and ignore the other creature spawns that happened in the same instance of time (less than .03 seconds apart, or even .01 for that matter). So now I'm left trying to think of a way to grab multiple IDs from game lines that are generated at the same time. IF that's the case, would all I need to do is to check to see if the next game line also matches my regex, and extract the ID from that line too? I hope this is clear. I may be over thinking this. I might give this a try after tonight's football games if I'm not tired. Thanks again!

Haldrik
09-26-2016, 03:20 AM
Thanks Haldrik. I thought that I'd be working with threads and while loops, but I'm a little rusty on my ruby/lich scripting so your example is very much appreciated. And good to know that I can stop wasting time on my other 2 issues.

One last thing, do you think the while loop and single thread will hold up if it receives multiple true evaluations simultaneously? For instance, lets say I want to capture the ID of several creatures that just spawn simultaneously. My thinking is the loop would grab just the first creature ID, and ignore the other creature spawns that happened in the same instance of time (less than .03 seconds apart, or even .01 for that matter). So now I'm left trying to think of a way to grab multiple IDs from game lines that are generated at the same time. IF that's the case, would all I need to do is to check to see if the next game line also matches my regex, and extract the ID from that line too? I hope this is clear. I may be over thinking this. I might give this a try after tonight's football games if I'm not tired. Thanks again!

Welp. The thread could keep up, the real problem is you wouldn't have time to do anything.

For example....

Monster A preps implode
While line grabs Monster A ID
single thread casts because Monster A ID = true
Cast RT: 3 second

Monster B preps implode...
repeat above, however cast RT causes failure.

I'll give you a real quick and dirty solution... It was my vaespilion nuke anti-implosion counter measure. symbol holi is instant cast so I would just blow up 1-3 no matter what. Even if only one was in the room. Note, there is much more elegant ways of doing this, I just didn't feel like writing it.


if line =~ /^A vaespilon draws an ancient sigil/
pause_script 'bigshot' if running? 'bigshot'
pause_script 'ubermonk' if running? 'ubermonk'

for targetNum in ['first', 'second', 'third']
waitrt?
result = GSC.bput "sym holi #{targetNum} vaesp", "^What were|^A wave of power flows out of you and toward a vaespilon.+"
echo result
redo if result !~ /Suddenly|What were|wait|^A wave of power flows out of you and toward a vaespilon\.$/
end

unpause_script 'bigshot' if running? 'bigshot'
unpause_script 'ubermonk' if running? 'ubermonk'
end

Tillmen
09-26-2016, 10:18 PM
The problem with using multiple threads is that all threads in a script share the same game buffer. So, you end up with a situation like this:


;e Thread.new { loop { echo "thread: #{get}" } }; loop { echo "main: #{get}" }
--- Lich: exec3 active.
>look
[Upper Dragonsclaw, Boulder]
You notice a rock cairn.
Obvious paths: down
>
[exec3: main: [Upper Dragonsclaw, Boulder]]
[exec3: thread: You notice a rock cairn.]
[exec3: main: Obvious paths: down]


If only one thread is using the game buffer, everything works fine. But if two threads (the main script is also a thread) are using the game buffer, neither one will get all the game lines. This won't be the case in Lich 5, but don't hold your breath waiting for that.

For now, I like to create a separate script that just watches for things like monster spell preps and sets global variables. Then, another script can check the global variables, decide what to do, and do it.

As an example, here's a script that will probably keep track of when a thunder troll prepares a spell:



status_tags
$spell_prepped_npc_ids = LimitedArray.new
$spell_prepped_npc_ids.max_size = 25
while line = get
if line =~ /^<pushBold\/>A <a exist="(.*?)" noun=".*?">.*?<\/a><popBold\/> raises its fists to the sky\./
$spell_prepped_npc_ids.push($1) unless $spell_prepped_npc_ids.include?($1)
elsif line =~ /^<pushBold\/>A <a exist="(.*?)" noun=".*?">.*?<\/a><popBold\/> claps its hands together in front of (?:you|<.*?>[A-Z][a-z]+<.*?>)!/
$spell_prepped_npc_ids.delete($1)
end
end


And another script that decides which monster to attack, and attacks it:


target_name_regex = /\b(?:thunder troll|puma|etc)$/
# you probably want to break the loop if fried/encumbered/bounty/etc
loop {
# you might want to use some sort of ability while in roundtime in a dangerous situation, but lets assume you don't have any of that
waitrt?
# only target live monsters with certain names
target_list = GameObj.npcs.find_all { |npc| (npc.name =~ target_name_regex) and (npc.status !~ /dead/) }
if target_list.empty?
# nothing to target; pretend there's code to loot and move
sleep 3
elsif (target_list.length > 9000) or GameObj.npcs.any? { |npc| npc.name == 'big ugly kobold of doom' }
# you should probably leave
elsif checkcastrt > 0
# looks like we're staying in the room, but rather than waiting for our entire cast roundtime to end, lets wait a tenth of a second and check again if we should leave
sleep 0.1
next
elsif target = target_list.find { |npc| $spell_prepped_npc_ids.include?(npc.id) }
# first priority is to corrupt anything with a spell prepped
$spell_prepped_npc_ids.delete(npc.id)
# we're assuming that 703 will be successful; you could have the other script watch for a successful 703 and delete from $spell_prepped_npc_ids instead
cast(703, target)
elsif (target_list.count { |npc| npc.status.nil? } > 2) and GameObj.pcs.nil? and not GameObj.loot.any? { |o| o.noun == 'disk' and o.name !~ /#{Char.name}/ }
# no npc has a spell prepped
# there are three or more monsters with a nil status, and no other players or disks in the room
# a nil status generally means the monster is not prone/kneeling/stunned/etc
# let's e-wave, I guess
cast(410)
elsif target = target_list.find { |npc| npc.status.nil? }
# no npc has a spell prepped
# there are not three or more monsters with a nil status
# there's at least one monster with a nil status, lets attack that
cast(702, target)
elsif target = target_list.find { |npc| npc.status !~ /stunned/ }
# no npc has a spell prepped
# there are no monsters with a nil status
# there's at least one monster that isn't stunned, lets attack that
cast(702, target)
else
# everything's half dead, pick something at random
target = target_list[rand(target_list.length)]
cast(702, target)
end
}


This is, of course, not a full blown hunting script. It's just some code I wrote up just now as an example. It probably isn't using the spells you want to use at the time you want to use them, but it should give you enough building blocks to write a script that can make much smarter decisions than your standard one-size-fits-all hunting script.

SirKris
09-27-2016, 06:06 AM
Oh wow, good stuff there Tillmen. I didn’t know multiple threads created the problem you described, so I’ll just stick to running multiple scripts for the time being.

Upgrading many of my attacking and spell casting scripts to have a smarter AI is exactly what I’m trying to do. So I’m not too worried if I can corrupt each creature that prepared a spell at the same time, I just want to be able to take appropriate action if that is the case. However, I didn’t think of the scenario so I’m glad you mentioned that, Haldrik, and that would be a nice little trick I could use for my voln character.

One reason why I wanted to ensure I could grab each relevant game line (such as 4 creatures spawning simultaneously, for example) so I can optimize my warmage’s attack script. I basically have it to where it’s first swinging at targets that aren’t stunned or otherwise disabled. However, I want to optimize it so that it doesn’t swing at new creatures that enter the room. In that case I want to filter those creatures out from my attacks until they swing or are otherwise disabled or forced up in stance.

Heh, those status_tags are going to allow me to have all sorts of fun adding AI for my scripts. I just recently re-activated and part the reason was just because I missed making scripts in GS. Thanks so much for all the knowledge and even examples. It’s hugely appreciated and gets my creative juices flowing.