Thread Tools Display Modes
02-06-12, 04:52 AM   #1
Animor
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Mar 2011
Posts: 136
Pattern matching and "player playing" status

Hello,

I've got two question I hope to get answers to:

1. I'm trying to match a pattern in strings that can come in two ways:
a. "Bases: 3 Points: 150/1600 ..."
b. "Points/Flags: 2 ..."

I want to get the 150 in case it's a string type 'a' (with ####/####). And in case there is no such pattern (type 'b'), I want to get the number - 2 in the example.
I've tried to use:
Code:
string.match(origString, "(%d+)/*")
But it didn't work - I got 3 for string type 'a'. If I use:
Code:
string.match(origString, "(%d+)/")
then I get the 150, but it obviously fails on string type 'b'.
Is there a matching pattern that will catch both cases? Note that the string before the number can alter, it's not always 'Points' for example.

2. When trying to whisper to an offline player, I get error "No player named XXX is currently playing". Is there a way to know that prior to the whisper? Like an API telling me that? I looked but couldn't find one.

Thanks
  Reply With Quote
02-06-12, 06:26 AM   #2
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
#1 - I don't think you're going to be able to get those with one pattern.

You want the values underlined here:
(a) Bases: 3 Points: 150/1600 ...
(b) Points/Flags: 2 ...

But, any pattern I can think of would be unable to distinguish between the values underlined here:
(a) Bases: 3 Points: 150/1600 ...
(b) Points/Flags: 2 ...

If you require a slash immediately after the number, then the pattern will not work to find the deisred number in the (b) string because there is no slash.

If you can be more specific about where these strings are coming from and what you want to do with them, I can probably provide a better solution.

#2 - There are three ways to tell if someone is online or offline:

(1) They are on your friends list.
(2) They are in your guild.
(3) You perform a /who query that matches them.

For addons sending whispers to arbitrary players who are not on your friends list or in your guild, I'd suggest looking at BadBoy_Levels for an example of how to add a name to your friends list for just long enough to get information about them (and then removing them so they are never "really" on the friends list as far as the user is concerned). BB_L checks their level, but the same code would work to see if they were online.

Depending on why you want to do this, however, I'd be very careful. If you're sending instructions to players in your battleground, it shouldn't be an issue, but if you're sending, say, guild invitations via whisper, you're likely to generate a lot of ill will. Personally, I instantly /ignore anyone who sends me an unsolicited whisper about something I don't care about, and pretty much any topic people are whispering me about using an addon is something I don't care about.
  Reply With Quote
02-06-12, 06:57 AM   #3
Animor
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Mar 2011
Posts: 136
Thanks

As for (1), the info is BG score I get from the API GetWorldStateUIInfo(). Some BGs have points/maxpoints, some have only points.
My current workaround is to identify in what kind of BG the player is now, then set a different matching pattern according to the BG type. I was trying to make a generic matching pattern, in order for the code to be more efficient. I thought that using '*' will try to find the longest pattern so it will try to include '/' in the matched pattern, if any can found. I thought that this is the difference between '-' (shortest pattern) and '*' (longest patter). But apparently I was wrong about it, since it doesn't apply to several matches in a single string.

(2) I'm a bit afraid of adding a player to the friends list, then removing it, like you've suggested. It seems to be too intrusive method. I'm afraid that that player might remain in the friend list on unpredictable cases, like a DC at the wrong moment, etc.
"who" query looks too complex and problematic according to this: http://www.wowpedia.org/API_SetWhoToUI

Edit: btw, I'm not sending unsolicited whisper, I hate those gold seller spam addons myself and I use badboy to report them.

Last edited by Animor : 02-06-12 at 07:02 AM.
  Reply With Quote
02-06-12, 07:27 AM   #4
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Battleground-specific code sounds like the way to go. I don't know which event(s) fire when the return values from that function change, but something like this:

Code:
local zoneFunctions = {
    ["Eye of the Storm"] = function(...)
        -- code goes here to deal with Eye of the Storm messages
    end,
    ["Warsong Gulch"] = function(...)
        -- code goes here to deal with Warsong Gulch messages
    end,
}

local fallbackFunction = function(...)
    -- code goes here to deal with unknown zones,
    -- or just leave the function empty
end

local currentFunction

function addon:ZONE_CHANGED_NEW_AREA()
    local zone = GetRealZoneText()
    currentFunction = zoneFunctions[zone] or fallbackFunction
end

function addon:SOME_EVENT(...)
    -- 1. do any zone-independent pre-processing here

    -- 2. pass the args to the relevant function:
    currentFunction(...)

    -- 3. do any zone-independent post-processing here
end
  Reply With Quote
02-06-12, 07:33 AM   #5
Ketho
A Pyroguard Emberseer
 
Ketho's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2010
Posts: 1,026
Originally Posted by Animor View Post
Thanks

As for (1), the info is BG score I get from the API GetWorldStateUIInfo(). Some BGs have points/maxpoints, some have only points.
My current workaround is to identify in what kind of BG the player is now, then set a different matching pattern according to the BG type. I was trying to make a generic matching pattern, in order for the code to be more efficient. I thought that using '*' will try to find the longest pattern so it will try to include '/' in the matched pattern, if any can found. I thought that this is the difference between '-' (shortest pattern) and '*' (longest patter). But apparently I was wrong about it, since it doesn't apply to several matches in a single string.
Maybe this thread might also help you a bit on the way

http://www.wowinterface.com/forums/s...ad.php?t=40977
  Reply With Quote
02-06-12, 07:44 AM   #6
Animor
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Mar 2011
Posts: 136
Thanks

I've already used a bit different approach. I will be happy to know if you see any flaws in it:
Code:
function MyAddon:GetBGScore(locationName)
  
  local allianceScore	= ""
  local hordeScore	= ""
  local num1 		= 1
  local num2 		= 2
  local matchString  	= "(%d+)"
  local score1		= 0
  local score2		= 0
  local scoreMsg 	= ""
  
  -- BGs in which score format is ####/####
  if sfind(locationName, "Gilneas") or sfind(locationName, "Basin") or sfind(locationName, "Storm") then
    matchString = "(%d+)/"
  end

  -- For BGs in which the first info line is global (not faction score)
  if sfind(locationName, "Warsong") or sfind(locationName, "Peaks") or sfind(locationName, "Storm") then
    num1 = 2
    num2 = 3
  end

  -- Get factions score from the lines that appear top-middle of the screen
  score1 = tonumber(smatch((select(4, GetWorldStateUIInfo(num1)) or ""), matchString)) or 0
  score2 = tonumber(smatch((select(4, GetWorldStateUIInfo(num2)) or ""), matchString)) or 0	

  -- Location on screen - is first line Alliance or Horde?
  if sfind(string.lower(select(5, GetWorldStateUIInfo(num1))), "alliance") then
    allianceScore	= score1
    hordeScore		= score2
  else
    allianceScore	= score2
    hordeScore		= score1
  end
	
  -- No score in Strand of the Ancients, report instead end of round timer.
  if sfind(locationName, "Strand") then	
    local endtime = smatch((select(4, GetWorldStateUIInfo(8)) or ""), "%d+.*") or L["<no data>"]
    scoreMsg = L["End of round in "]..endtime.. L[" min"]
  else
    scoreMsg = L["Score: "]..allianceScore.."(A) - "..hordeScore.."(H), "
  end
  
  return scoreMsg

end

Last edited by Animor : 02-06-12 at 07:53 AM.
  Reply With Quote
02-06-12, 08:08 AM   #7
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
I'd try to come up with something to reduce the use of string manipulation functions, but other than that I don't see anything obviously wrong.

For example, instead of:
Code:
  local matchString  	= "(%d+)"

  -- BGs in which score format is ####/####
  if sfind(locationName, "Gilneas") or sfind(locationName, "Basin") or sfind(locationName, "Storm") then
    matchString = "(%d+)/"
  end
You could define a table with the full names (so you don't have to run string.find for every possible name) and the string (so you can just perform one table lookup instead of many if/or checks:

Code:
local matchStrings = {
	["Arathi Basin"]     = "(%d+)/",
	["Eye of the Storm"] = "(%d+)/",
	["Warsong Gulch"]    = "(%d+)",
}
Code:
local zoneName = GetRealZoneText()

local matchString = matchStrings[zoneName]
This would save time inside the function call, and make it easier to handle new battlegrounds, or adjust for changes in existing battlegrounds.
  Reply With Quote
02-06-12, 08:33 AM   #8
Animor
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Mar 2011
Posts: 136
Thanks, good idea!

But how can I have a default value for matchString using your suggestion? I don't want the addon to break up if a new BG will be added sometime.

Perhaps this will work (or something like that)?
Code:
local matchString = matchStrings[zoneName] or "(%d+)"
  Reply With Quote
02-06-12, 12:17 PM   #9
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Yeah, that works.

On second glance, though, you're calling GetWorldStateUIInfo() too many times, and select() should generally be avoided.

Code:
local matchStrings = {
	-- BGs in which score format is ####/####
	["Arathi Basin"]       = "(%d+)/",
	["Battle for Gilneas"] = "(%d+)/",
	["Eye of the Storm"]   = "(%d+)/",

	-- BGs in which score format is ##
	-- Realistically you don't need these entries, since
	-- their value is the default.
	["Alterac Valley"]         = "(%d+)",
	["Isle of Conquest"]       = "(%d+)",
	["Strand of the Ancients"] = "(%d+)",
	["Twin Peaks"]             = "(%d+)",
	["Warsong Gulch"]          = "(%d+)",
}

local hasGlobalInfo = {
	-- BGs in which the first info line is global (not faction score)
	["Eye of the Storm"] = true,
	["Twin Peaks"]       = true,
	["Warsong Gulch"]    = true,
}

function MyAddon:GetBGScore(locationName)
	local uiType, state, hidden, text, icon, dynamicIcon, tooltip,
		dynamicTooltip, extendedUI, extendedUIState1, extendedUIState2,
		extendedUIState3 = GetWorldStateUIInfo()

	local zoneName = GetRealZoneText()
	local matchString = matchStrings[zoneName] or "(%d+)"

	local n1, n2
	if hasGlobalInfo[zoneName] then
		n1, n2 = 2, 3
	else
		n1, n2 = 1, 2
	end

	local _, _, _, text1, icon1 = GetWorldStateUIInfo(n1)
	local _, _, _, text2, icon2 = GetWorldStateUIInfo(n2)

	local allianceScore = tonumber((text1 or ""):match(matchString)) or 0
	local hordeScore = tonumber((text2 or ""):match(matchString)) or 0

	if not icon1:lower():match("alliance") then
		-- Would be more efficient to check for whatever is in the
		-- is in the Horde icon path... probably "horde" ?
		-- Anyway, if needed, just swap the values; you don't need
		-- extra variables to do this:
		allianceScore, hordeScore = hordeScore, allianceScore
	end

	if zoneName == "Strand of the Ancients" then
		-- No score in Strand of the Ancients, report instead end of round timer.
		local _, _, _, endTime = GetWorldStateUIInfo(8)
		endTime = endTime and endTime:match("%d+.*") or L["<no data>"]
		return L["End of round in "] .. endTime .. L[" min"]
	end

	return L["Score: "] .. allianceScore .. "(A) - " .. hordeScore .. "(H), "
end
Also, rather than concatenating strings for the output, which requires translators to translate random pieces of sentences that may not end up in the correct order for their language, you should take advantage of string substitutions.

So, instead of this:
Code:
L["End of round in "] .. end .. L[" min"]
L["Score: "] .. allianceScore .. "(A) - " .. hordeScore .. "(H), "
Do this:
Code:
format(L["End of round in %d min"], endTime)
format(L["Score: %d (A) - %d (H), ", allianceScore, hordeScore)
This way, translators can place the substition tokens where it is more appropriate for their language. It may not be particularly relevant for these specific examples, but in many cases it will be.
  Reply With Quote
02-06-12, 03:16 PM   #10
Animor
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Mar 2011
Posts: 136
Thanks!
I will rewrite my code accordingly

I see that you have declared the local variable outside of the function.

1. If a local var is used only in a certain function, then why not declaring it inside the function?
2. Is there any difference between "local matchStrings = ..." and "MyAddon.matchStrings = ..." ?
3. Should I set those variables in :OnInitialize() function, or just put them somewhere in the lua file?

Thanks!
  Reply With Quote
02-06-12, 05:46 PM   #11
p3lim
A Pyroguard Emberseer
 
p3lim's Avatar
AddOn Author - Click to view addons
Join Date: Feb 2007
Posts: 1,710
Originally Posted by Animor View Post
Thanks!
I will rewrite my code accordingly

I see that you have declared the local variable outside of the function.

1. If a local var is used only in a certain function, then why not declaring it inside the function?
2. Is there any difference between "local matchStrings = ..." and "MyAddon.matchStrings = ..." ?
3. Should I set those variables in :OnInitialize() function, or just put them somewhere in the lua file?

Thanks!
1: Waste of memory to set the variable(s) multiple times if they don't change, it just creates garbage.
2: No, just the way you look them up.
3: Not a real need unless you want to save loading times (only applicable for massive amounts)
  Reply With Quote
02-06-12, 08:11 PM   #12
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Originally Posted by Animor View Post
2. Is there any difference between "local matchStrings = ..." and "MyAddon.matchStrings = ..." ?
Tables in Lua are passed by reference, so:

Code:
local t = { foo = "bar" }
MyAddon.t = t

MyAddon.t.foo = "over 9000!"

print( t.foo )
==> "over 9000!"
Both t and MyAddon.t point to the same table object in memory. Since there is only one table, changes made to it using one name will also be seen when looking at it under the other name.

This is in contrast to strings/numbers/booleans, which are passed by value:

Code:
local var = "something"
MyAddon.var = var

MyAddon.var = "nothing"

print( var )
==> "something"
var and MyAddon.var point to two different string objects in memory; they may have the same contents at any given time, but they are separate objects, and changes to one will not affect the other.

However, if you're talking about calling t vs. MyAddon.t inside the file where the local t is in scope, calling the local t is faster. Even if MyAddon is local in the same scope, it still adds an additional table lookup to go that way. This is why you'll see many addons do:

Code:
local db = MyAddon.db.profile
if db.something then
   -- do something
end
instead of:

Code:
if MyAddon.db.profile then
   -- do something
end
It's simply faster. If you want to add the table to your addon table so you can access it from outside the addon's file (eg. using in-game slash commands for debugging/testing) that's fine, but don't access it that way from inside the file where the table is already local.

Last edited by Phanx : 02-06-12 at 08:14 PM.
  Reply With Quote
02-07-12, 02:35 AM   #13
Animor
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Mar 2011
Posts: 136
ok, I see. Thanks for the answer.

Does the above also applies to functions, when I use "self"?

For example, this function:
Code:
function MyAddon:SomeFunc()	
	if self.db.profile.globalEn then
	   -- some code
	end
end

is better written like that?
Code:
local db = MyAddon.db.profile

function MyAddon:SomeFunc()	
	if db.globalEn then
	   -- some code
	end
end

Last edited by Animor : 02-07-12 at 03:51 AM.
  Reply With Quote
02-07-12, 09:08 AM   #14
p3lim
A Pyroguard Emberseer
 
p3lim's Avatar
AddOn Author - Click to view addons
Join Date: Feb 2007
Posts: 1,710
Does not matter, the memory footprint is the same afaik.
  Reply With Quote
02-07-12, 01:22 PM   #15
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,327
If you write decent enough code, memory really isn't the main issue since most of the time, we're running on machines that have 2+GB of RAM. What people really need to focus on is CPU usage. The more time the computer spends on your code means lower FPS rates and to an extreme, degraded gameplay experience.

Most addons run with a memory footprint of 2-3MB. The only exception are addons that store massive amounts of data to analyze it. An example of such addon would be Auctioneer, which you'd expect to see run about 30-40MB.
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)

Last edited by SDPhantom : 02-07-12 at 01:26 PM.
  Reply With Quote
02-07-12, 01:39 PM   #16
Animor
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Mar 2011
Posts: 136
Thanks.

I was referring to CPU usage, since the previous answers were talking about how fast a table lookup would be, if I understood them correctly.

So, my question is which one is faster, inside a function:
if db.globalEn then
or
if self.db.profile.globalEn then

I'm asking this since I don't know how the LUA compiler/parser deals with "self".
  Reply With Quote
02-07-12, 02:25 PM   #17
Torhal
A Pyroguard Emberseer
 
Torhal's Avatar
AddOn Author - Click to view addons
Join Date: Aug 2008
Posts: 1,196
Each dot is a table lookup. Therefore, "self.db.profile.globalEn" performs a table lookup on "self" to find "db", another on "db" to find "profile", and yet another on "profile" to find "globalEn". Doing the upvalue of "local db = MyAddon.db.profile" performs these same lookups, but only once - after that, you're performing a single table lookup from the value stored in the variable every time your function is called instead of three lookups.
__________________
Whenever someone says "pls" because it's shorter than "please", I say "no" because it's shorter than "yes".

Author of NPCScan and many other AddOns.
  Reply With Quote
02-07-12, 02:28 PM   #18
Animor
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Mar 2011
Posts: 136
Thanks, that answered my question
  Reply With Quote
02-07-12, 02:32 PM   #19
Torhal
A Pyroguard Emberseer
 
Torhal's Avatar
AddOn Author - Click to view addons
Join Date: Aug 2008
Posts: 1,196
Also, "self" is simply syntactic sugar.

Code:
local foo = {}

foo.bar = function(self)
    -- Do something with self.
end

function foo.bar(self)
    -- Do something with self
end

function foo:bar()
    -- Do something with self
end

Dot-notation means you must explicitly pass in the table as 'self'. Colon-notation does so implicitly. The following examples are equivalent:

Code:
local baz = foo.bar(foo)
baz = foo:bar()
__________________
Whenever someone says "pls" because it's shorter than "please", I say "no" because it's shorter than "yes".

Author of NPCScan and many other AddOns.
  Reply With Quote
02-08-12, 07:16 AM   #20
Animor
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Mar 2011
Posts: 136
Just something that I suddenly thought about: how will I handle localiztion of BG names? If I write for example, as Phanx suggested:

Code:
local zoneName = GetRealZoneText()

local hasGlobalInfo = {
	-- BGs in which the first info line is global (not faction score)
	["Eye of the Storm"] = true,
	["Twin Peaks"]       = true,
	["Warsong Gulch"]    = true,
}

if hasGlobalInfo[zoneName] then
...
end
Will it work only for enUS clients? If yes, how do I get a non-localized BG name?
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » Pattern matching and "player playing" status


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off