Page 6 of 9
Re: createpath + getid + getpos together
Posted: Wed Nov 20, 2013 3:02 am
by rock5
Well of course it can't be used exactly like that because although 123456 is a number <s>123456 is a syntax error. It could be a string, eg
but then it would have to parse the string to get the "<s>" and the id number. And it's not like it would make things simpler. You would still have to have a separate check for it
Code: Select all
elseif tonumber(subKeyString) then -- Must be id
translatedSubTEXT = GetIdName(tonumber(subKeyString))
elseif subKeyString:sub(1,3) =="<S>" then -- Must be id plural
If you then did
GetIdName(subKeyString), it would then have to do the work of parsing the string. Wait, it would still have to parse it to get the id. I guess it could be done but I don't think it's intuitive. If you have an id you would get the plural like this
Code: Select all
single = GetIdName(id)
plural = GetIdName("<S>"..id)
Which looks like its the wrong way to do it to me. Whereas if the syntax was GetIdName(id, plural) you would do
Code: Select all
single = GetIdName(id)
plural = GetIdName(id, true)
I don't want to use a separate function because otherwise it would be repeating similar code and if I decide to have it return the shortnote as well then it saves it being repeated 3 times.
Anyway, still thinking about it. I sometimes don't rush into things but instead take my time thinking of the best way to do something before I even begin.
Re: createpath + getid + getpos together
Posted: Thu Nov 21, 2013 3:19 am
by Bill D Cat
I did some playing around with the
GetIdName() function and got this to work. I noticed that some of the items do not have a plural form of the name defined, and will return an empty string "" for them. So I've added a fallback check to use the singular form of the name in those cases. I'll leave it to you to modify the
getTEXT() function to parse for the "<S>".
Code: Select all
-- Returns the name for a given id
function GetIdName(itemId,plural)
plural = plural or false
if plural == true then pluralOffset = 4 else pluralOffset = 0 end
if itemId ~= nil and itemId > 0 then
local itemAddress = GetItemAddress(itemId)
if itemAddress ~= nil and itemAddress > 0 then
local name = memoryReadStringPtr(getProc(), itemAddress + addresses.nameOffset + pluralOffset, 0)
if name == "" and plural then
name = memoryReadStringPtr(getProc(), itemAddress + addresses.nameOffset, 0)
end
if name == nil then
-- Item data not totally substantiated yet. Do "GetCraftRequestItem", then the address will exist.
if RoMScript then RoMScript("GetCraftRequestItem("..itemId..", -1)") end
local name = memoryReadStringPtr(getProc(), itemAddress + addresses.nameOffset + pluralOffset, 0)
if name == "" and plural then
name = memoryReadStringPtr(getProc(), itemAddress + addresses.nameOffset, 0)
end
end
return name
end
end
end
Re: createpath + getid + getpos together
Posted: Thu Nov 21, 2013 7:06 am
by rock5
A couple of points first. In this case "plural = plural or false" doesn't seem to do anything. You only really need to know if plural is true or not true so you could leave that out. Also you made pluralOffset a global variable. Should be local.
I've been doing some work to make it return card and recipe names. I've got it working but have been taking my time to get it just right. I'm still not 100% happy with it. Now I'll have to merge these changes and I just have to figure out what order is best to do everything. I don't even know if the GetCraftRequestItem code is needed for plurals.
Re: createpath + getid + getpos together
Posted: Thu Nov 21, 2013 12:14 pm
by Bill D Cat
Sorry, I'm just a bit impatient
Code: Select all
if not keystring or type(keystring) ~= "string" then return end
local resultTEXT = memoryGetTEXT(keystring)
for subKeyString in string.gmatch(resultTEXT,"%[(.-)%]") do
local translatedSubTEXT
if subKeyString:sub(1,1) == "$" then -- variable. See if it's player.
if subKeyString == "$PLAYERNAME" or subKeyString == "$playername" then
translatedSubTEXT = player.Name
end
elseif tonumber(subKeyString) then -- Must be id
translatedSubTEXT = GetIdName(tonumber(subKeyString))
elseif subKeyString:sub(1,3) == "<S>" then -- Must be id plural
translatedSubTEXT = GetIdName(tonumber(subKeyString:sub(4,9)),true)
else
translatedSubTEXT = memoryGetTEXT(subKeyString)
end
if translatedSubTEXT ~= nil and translatedSubTEXT ~= subKeyString and not string.find(translatedSubTEXT,"%%") then
resultTEXT = string.gsub(resultTEXT, "%["..subKeyString.."%]", translatedSubTEXT)
end
end
And as for making the pluralOffset value a local variable, I get an error when I add the local keyword, so I just left off for now:
Code: Select all
if plural == true then pluralOffset = 4 else pluralOffset = 0 end -- This works
if plural == true then local pluralOffset = 4 else local pluralOffset = 0 end --This gives the error below
11:04am - ...les (x86)/MicroMacro/scripts/rom/classes/memorytable.lua:126: attempt to perform arithmetic on global 'pluralOffset' (a nil value)
Re: createpath + getid + getpos together
Posted: Thu Nov 21, 2013 2:00 pm
by rock5
Where you put the local made it local to the if statement. You have to declare it outside the if statement first so it will be local to the function.
Code: Select all
local pluralOffset
if plural == true then pluralOffset = 4 else pluralOffset = 0 end
Re: createpath + getid + getpos together
Posted: Thu Nov 21, 2013 3:14 pm
by Bill D Cat
While looking through the string_enus.db file, I noticed one other embeded variable that may nor may not need to be parsed. The format looks like [123456|Some Text] where the text is usually some other form of the name, such as a possessive form. I didn't notice too many in quest text strings, and most looked like they were going to be displayed in a yellow "warning" message or banner text.
Code: Select all
SC_101501_07="[101501|Yusalien's] hardened skin slowly resumes its original state again."
Re: createpath + getid + getpos together
Posted: Thu Nov 21, 2013 10:36 pm
by rock5
I noticed them too. I think it's sort of an override. So the id is for showing a link to the npc and the override is the text it shows. I think the reason most of them are possessive names is because that is the most common need for it. Here is an example of one that has nothing to do with the name.
Code: Select all
SC_424995_1 = "Is this true? It's hard to believe that the [119682|Commander] is so concerned about us. I will have to work harder to fulfill my duties!"
119682 is Shar Talos so I assume thats the commander. I could add a filter for that too.
On another note, I think I will be adding a cache for the getTEXT returned results. So it only looks up a keystring once and saves it to a table so subsequent calls will be faster.
Re: createpath + getid + getpos together
Posted: Thu Nov 21, 2013 11:11 pm
by Bill D Cat
Simple enough to filter I suppose. If you wanted the normal ID you could use:
Code: Select all
elseif tonumber(subKeyString:sub(1,6)) and subKeyString:sub(7,7) == "|" then -- Id with a text override
translatedSubTEXT = GetIdName(tonumber(subKeyString:sub(1,6)))
And if you wanted the override text:
Code: Select all
elseif tonumber(subKeyString:sub(1,6)) and subKeyString:sub(7,7) == "|" then -- Id with a text override
translatedSubTEXT = subKeyString:sub(8,#subKeyString)
The question is, which would be more useful? I'm guessing the override text, as that would be what the game would display. I'll do some more investigating after work to see if I can find any that show up in clickable dialog options. Otherwise it probably won't matter which way we handled it.
Re: createpath + getid + getpos together
Posted: Thu Nov 21, 2013 11:22 pm
by rock5
Well if it shows the override text then that is what we would have to use to get a string match.
Re: createpath + getid + getpos together
Posted: Thu Nov 21, 2013 11:28 pm
by Bill D Cat
Yeah, the more I think about it, the more I agree that the override text is the way to go. It's not just clickable links that you may have to worry about. Setting up a message monitoring event for certain system messages may encounter one of these strings as well. I have to remind myself to think outside of the scope that I'm using the bot and to consider how others may want to use it. I'll make it as a developer eventually

Re: createpath + getid + getpos together
Posted: Fri Nov 22, 2013 1:24 am
by rock5
I hope you do. The problem is I have a real hard time letting go of my control. It's real hard for me to let others slowly work things out on their own without stepping in and taking over and writing or rewriting things myself. I was lucky when I first came in. Administrator had already lost interest in the bot and no previous developers remained. At first it was just me pestering the Administrator with my ideas. Then later when I got better at programming, I would offer my ideas as already written code. Then eventually Administrator just gave me developer access to the svn. I think the reason I've lasted so long as a developer is because I don't have a life.

Re: createpath + getid + getpos together
Posted: Fri Nov 22, 2013 3:45 am
by Bill D Cat
Don't worry, I don't want to take over this project, just be a contributor of (hopefully) good ideas. I too have a life, but I like to spend what free time I have coding. I do a lot of programming at work, so it sort of carries over into my personal time. I just hope I've been able to help others from time to time, and to bring a fresh perspective on things that can be added to, or expanded upon with this project. The changes I've posted to getTEXT() and GetIdName() are allowing my current waypoint project to move ahead much more smoothly. Having the translated strings will allow the waypoints to truly be language independent and usable by more people without needing modifications.
My time spent in RoM these days is not to advance one character to the maximum, but rather to pass time doing something I enjoy. And right now, I am enjoying the self-given challenge to script as many of the zones in their entirety as I possibly can.
1. Elven Island - Done, though being improved upon now.
2. Yrvandis Hollows - Done, only refining the elite fight remains.
3. Howling Mountains - Single class quests done, working on quests that are only available after you take your second class.
4. Coast of Opportunity - All quests in and around Heffner Camp done. Will cross the river soon.
5. Sascilia Steppes - All quests leading up to Rose Caravan done.
Re: createpath + getid + getpos together
Posted: Fri Nov 22, 2013 4:20 am
by Bill D Cat
Here's where my code additions are right now:
functions.lua, line 2388:
Code: Select all
if not keystring or type(keystring) ~= "string" then return end
local resultTEXT = memoryGetTEXT(keystring)
for subKeyString in string.gmatch(resultTEXT,"%[(.-)%]") do
local translatedSubTEXT
if subKeyString:sub(1,1) == "$" then -- variable. See if it's player.
if subKeyString == "$PLAYERNAME" or subKeyString == "$playername" then
translatedSubTEXT = player.Name
end
elseif tonumber(subKeyString) then -- Must be id
translatedSubTEXT = GetIdName(tonumber(subKeyString))
elseif subKeyString:sub(1,3) == "<S>" then -- Must be id plural
translatedSubTEXT = GetIdName(tonumber(subKeyString:sub(4,9)),true)
elseif tonumber(subKeyString:sub(1,6)) and subKeyString:sub(7,7) == "|" then -- Id with a text override
translatedSubTEXT = subKeyString:sub(8,#subKeyString)
else
translatedSubTEXT = memoryGetTEXT(subKeyString)
end
if translatedSubTEXT ~= nil and translatedSubTEXT ~= subKeyString and not string.find(translatedSubTEXT,"%%") then
resultTEXT = string.gsub(resultTEXT, "%["..subKeyString.."%]", translatedSubTEXT)
end
end
memorytable.lua, line 120:
Code: Select all
-- Returns the name for a given id
function GetIdName(itemId,plural)
local pluralOffset
if plural == true then pluralOffset = 4 else pluralOffset = 0 end
if itemId ~= nil and itemId > 0 then
local itemAddress = GetItemAddress(itemId)
if itemAddress ~= nil and itemAddress > 0 then
local name = memoryReadStringPtr(getProc(), itemAddress + addresses.nameOffset + pluralOffset, 0)
if name == "" and plural then
name = memoryReadStringPtr(getProc(), itemAddress + addresses.nameOffset, 0)
end
if name == nil then
-- Item data not totally substantiated yet. Do "GetCraftRequestItem", then the address will exist.
if RoMScript then RoMScript("GetCraftRequestItem("..itemId..", -1)") end
name = memoryReadStringPtr(getProc(), itemAddress + addresses.nameOffset + pluralOffset, 0)
if name == "" and plural then
name = memoryReadStringPtr(getProc(), itemAddress + addresses.nameOffset, 0)
end
end
return name
end
end
end
Re: createpath + getid + getpos together
Posted: Sat Nov 23, 2013 12:33 am
by rock5
I've made a real effort today to wrap my head around how everything is supposed to come together. It's more complex than you might think. Consider this; What if you are looking for a plural of a card or recipe? First we would get the npc or item id. First question, do we use the plural of the mob or item to make the name? Some recipes actually have names and plurals so we don't have to get the item and make the name. One of them at least doesn't have a plural. So in fact we have to check the name first. If it doesn't have a name then we check if it's a card or recipe then make the name.
So basically we are saying; check the plural, if no plural then check the singular, if no singular then, check if card or recipe, if card or recipe check plural, if no plural check singular.
I've decided to simplify it. The only time I can see plurals being used is for the getTEXT strings. So I did a search for "[<s>55" and "[<s>77" and came up with no results. So I'm going to just return the singular if I have to look up a recipe item or card mob.
So the logic will be; check the plural, if no plural then check the singular, if no singular then check if card or recipe, if card or recipe then return the singular.
And another thing, I think if the plural address is 0x902EFC, then it doesn't have a plural name and returns "". That means that if the game calls for that items plural it will get "". If it's used in a key string it will have "". That means if our function returns the singular then the string wont match. I'd assume that anywhere in the game that calls for a plural of an id then that id will have a plural. So there shouldn't be any need to return the singular of an id if it doesn't have a plural. Of course it might be possible that the game also returns the singular if it doesn't have a plural but we don't have any evidence of that.
And then there is the issue of the GetCraftRequestItem command in the GetIdName function and when it's necessary. It only runs if the name read is initially nil. If the name address is 0x902EFC then the name returned is "" GetCraftRequestItem doesn't run. I think name is initially nil only if the name address is 0 but I can't be 100% sure. According to this post
http://www.solarstrike.net/phpBB3/viewt ... 530#p25530 it needs it because sometimes the name address is "missing". I'm not 100% sure what I meant by missing. I believe the area where all the strings are are always there so if the name address is an address I'm assuming it will point to a string. So I'm assuming the only way the name would be nil is if the address = 0.
Feel free to comment but just writing it all down helped to clarify it in my mind. I'll be going to a party soon so I wont get much more done now but I will work on it after I get back.
Re: createpath + getid + getpos together
Posted: Sat Nov 23, 2013 12:20 pm
by Bill D Cat
rock5 wrote:I've made a real effort today to wrap my head around how everything is supposed to come together. It's more complex than you might think. Consider this; What if you are looking for a plural of a card or recipe? First we would get the npc or item id. First question, do we use the plural of the mob or item to make the name? Some recipes actually have names and plurals so we don't have to get the item and make the name. One of them at least doesn't have a plural. So in fact we have to check the name first. If it doesn't have a name then we check if it's a card or recipe then make the name.
From what I've seen in the .fdb files, the plural of "Recipe - Hero Potion" would be listed as "Recipes - Hero Potion", so you would use the singular form of the item to create the plural form of the Recipe. I'm just having trouble coming up with a situation where the game would use the plural form of Card or Recipe in the first place.
rock5 wrote:So basically we are saying; check the plural, if no plural then check the singular, if no singular then, check if card or recipe, if card or recipe check plural, if no plural check singular.
Again, is there a case where the plural form would be used in-game, or is this just so the bot user could have the data for their own use?
rock5 wrote:I've decided to simplify it. The only time I can see plurals being used is for the getTEXT strings. So I did a search for "[<s>55" and "[<s>77" and came up with no results. So I'm going to just return the singular if I have to look up a recipe item or card mob.
So the logic will be; check the plural, if no plural then check the singular, if no singular then check if card or recipe, if card or recipe then return the singular.
I agree. Until such time as someone comes across a string that uses the plural form, the singular should be sufficient.
rock5 wrote:And another thing, I think if the plural address is 0x902EFC, then it doesn't have a plural name and returns "". That means that if the game calls for that items plural it will get "". If it's used in a key string it will have "". That means if our function returns the singular then the string wont match. I'd assume that anywhere in the game that calls for a plural of an id then that id will have a plural. So there shouldn't be any need to return the singular of an id if it doesn't have a plural. Of course it might be possible that the game also returns the singular if it doesn't have a plural but we don't have any evidence of that.
Good point. As above, until it is needed, we can always just comment out the fallback code or remove it.
rock5 wrote:And then there is the issue of the GetCraftRequestItem command in the GetIdName function and when it's necessary. It only runs if the name read is initially nil. If the name address is 0x902EFC then the name returned is "" GetCraftRequestItem doesn't run. I think name is initially nil only if the name address is 0 but I can't be 100% sure. According to this post
http://www.solarstrike.net/phpBB3/viewt ... 530#p25530 it needs it because sometimes the name address is "missing". I'm not 100% sure what I meant by missing. I believe the area where all the strings are are always there so if the name address is an address I'm assuming it will point to a string. So I'm assuming the only way the name would be nil is if the address = 0.
Perhaps a different sanity check would be all that is required here. I'll do some test runs later with some extra print statements to see if I can get it to trigger the GetCraftRequestItem command. Who knows if some other changes in the bot and/or game itself has modified that behavior since the time of that last post.
Re: createpath + getid + getpos together
Posted: Sat Nov 23, 2013 11:04 pm
by rock5
Bill D Cat wrote:Perhaps a different sanity check would be all that is required here. I'll do some test runs later with some extra print statements to see if I can get it to trigger the GetCraftRequestItem command. Who knows if some other changes in the bot and/or game itself has modified that behavior since the time of that last post.
Ok but let me explain the process as I understand it.
There are 2 GetCraftRequestItem commands. One is in GetItemAddress and one in GetIdName.
The one in GetItemAddress runs when the item area hasn't been created yet and the id in the id list has 0 where it should be pointing to the item area. When we run GetCraftRequestItem it creates the item area and it's address appears in the id list. This ones easy to trigger. Just access a new random id for the first time.
The one in GetIdName only runs if the item area
was created but not completed and is missing the name address. GetCraftRequestItem wouldn't have run in GetItemAddress otherwise the address would exist. I suspect it only creates these partial item areas in special situations. Maybe for particular types of ids so I would check out different types of ids.
Re: createpath + getid + getpos together
Posted: Sun Nov 24, 2013 12:39 am
by rock5
So I guess the current logic is; if it asks for a plural give it the plural, if it asks for a singular give it the singular, if there is no singular and it's a card or recipe make the name from the item/npc id.
You know most recipes and all cards don't have names so for all of them the above logic will cause it to read the name address of that id needlessly. And seeing as no cards or recipes are used in key string text, I'm thinking of ignoring those few recipes that have names and go directly to the item id to make the name. I'm not even sure those names are used anyway. They could just be a mistake from some inexperienced dev.
So in that case the logic would be; if the id is a card or recipe then switch to the item/npc id and add the correct prefix to the name, then get the name of that id and add it to the name using the plural if it's specified. I don't see a problem with using the plural even on recipes and cards because I can't envision any situation in which a plural of them will ever be asked for.
Re: createpath + getid + getpos together
Posted: Sun Nov 24, 2013 3:36 am
by rock5
Ok this is what I have for GetIdName, finally.
Code: Select all
-- Returns the name for a given id
function GetIdName(itemId, plural)
-- Check itemId
if itemId == nil or itemId == 0 then
return
end
-- Check plural
local pluralOffset
if plural == true then pluralOffset = 4 else pluralOffset = 0 end
-- Get and check item address
local itemAddress = GetItemAddress(itemId)
if itemAddress == nil or itemAddress == 0 then
return
end
-- If card or recipe, update itemId, itemAddress and prefix name
local name = ""
if itemId >= 770000 and itemId <= 772000 then
itemId = memoryReadInt( getProc(), itemAddress + addresses.idCardNPCOffset );
itemAddress = GetItemAddress( itemId );
name = getTEXT("SYS_CARD_TITLE") -- "Card - "
elseif itemId >= 550000 and itemId <= 553000 then
itemId = memoryReadInt( getProc(), itemAddress + addresses.idRecipeItemOffset );
itemAddress = GetItemAddress( itemId );
name = getTEXT("SYS_RECIPE_TITLE") -- "Recipe - "
end
-- Get name/plural address
local nameaddress = memoryReadInt(getProc(), itemAddress + addresses.nameOffset + pluralOffset)
if nameaddress == 0 and RoMScript then
RoMScript("GetCraftRequestItem("..itemId..", -1)")
nameaddress = memoryReadInt(getProc(), itemAddress + addresses.nameOffset + pluralOffset)
end
name = name .. memoryReadString(getProc(), nameaddress)
return name
end
I'm pretty happy with it.
Now to do some work on getTEXT. Let me see, you already added things like text overrides and plurals. I'm going to add a cache. Oops I just found an unusual string
Code: Select all
Sys102145_shortnote = "The treasure robbers died hungry and poor. The cold they felt before they died eventually became the [<S>101390|Frozen Shade's] deadliest weapon."
That's both a plural and override. I can't see why they would do that. So the pattern will need a little tweaking. I don't think '|' is used in subtext for anything else but overrides so we could probably just do a string.find for that.
Re: createpath + getid + getpos together
Posted: Sun Nov 24, 2013 4:03 am
by rock5
You might have noticed in the function above that I changed the whole format of the function. The way I changed it is more like the way we code in the bot. To make things clearer, instead of writing something like this;
Code: Select all
function name()
if a == value then
if b == value then
if c == value then
-- do something
end
end
end
end
We try to write it like this
Code: Select all
function name()
-- Check the a value
if a ~= value then
return
end
-- Check the b value
if b ~= value then
return
end
-- Check the c value
if c ~= value then
return
end
-- do something
end
It does exactly the same thing but is super clear and easier to maintain than nested ifs and makes it easy to comment each step. This is the main thing I learnt working with the bot.
Re: createpath + getid + getpos together
Posted: Sun Nov 24, 2013 8:53 am
by rock5
This is what my getTEXT looks like.
Code: Select all
local getTEXTTable = {}
function getTEXT(keystring)
if not keystring or type(keystring) ~= "string" then return end
if getTEXTTable[keystring] then
return getTEXTTable[keystring]
end
local function memoryGetTEXT(str)
local addressPtrsBase = memoryReadInt(getProc(), addresses.getTEXT)
local startloc = memoryReadInt(getProc(), addressPtrsBase + 0x268)
local endloc = memoryReadInt(getProc(), addressPtrsBase + 0x26C)
local quarter = math.floor((endloc-startloc) / 4)
-- Pattern doesn't work with first string so check that first
if str == "AC_ITEMTYPENAME_0" then
return memoryReadString(getProc(), startloc + 18)
end
local tmpStart = endloc
local tmpEnd = endloc
-- Find which quarter of memory holds string to speed up search
for count = 1,3 do
tmpStart = tmpStart - quarter
local found = findPatternInProcess(getProc(), string.char(0).."Sys", "xxx", tmpStart, tmpEnd);
local tmpText = memoryReadString(getProc(), found + 1)
if tmpText <= str then
startloc = tmpStart
break
else
endloc = tmpStart
end
end
local searchlen = endloc - startloc
local pattern = string.char(0x00) .. str .. string.char(0x00)
local mask = string.rep("x", #pattern)
local offset = #pattern
local found = findPatternInProcess(getProc(), pattern, mask, startloc, searchlen);
if found ~= 0 then
return memoryReadString(getProc(), found + offset)
else
return str
end
end
local resultTEXT = memoryGetTEXT(keystring)
-- Replace known sub key strings
for subKeyString in string.gmatch(resultTEXT,"%[(.-)%]") do
local translatedSubTEXT
if subKeyString:sub(1,1) == "$" then -- variable. See if it's player.
if subKeyString == "$PLAYERNAME" or subKeyString == "$playername" then
translatedSubTEXT = player.Name
end
elseif tonumber(subKeyString) then -- Must be id
translatedSubTEXT = GetIdName(tonumber(subKeyString))
elseif subKeyString:find("|",1,true) then -- Id with a text override
translatedSubTEXT = subKeyString:match(".*|(.*)")
elseif subKeyString:sub(1,3) == "<S>" then -- Must be id plural
translatedSubTEXT = GetIdName(tonumber(subKeyString:sub(4,9)),true)
else
translatedSubTEXT = memoryGetTEXT(subKeyString)
end
if translatedSubTEXT ~= nil and translatedSubTEXT ~= subKeyString and not string.find(translatedSubTEXT,"%%") then
resultTEXT = string.gsub(resultTEXT, "%["..subKeyString.."%]", translatedSubTEXT)
end
end
-- Remember result
if resultTEXT ~= keystring then
getTEXTTable[keystring] = resultTEXT
end
return resultTEXT
end