vanilla-wow-addons – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 ------------------------------------------------------
2 -- HuntersHelper.lua
3 ------------------------------------------------------
4 FHH_VERSION = "11000.1";
5 ------------------------------------------------------
6  
7 -- Saved configuration & info
8 FHH_Config = { };
9 FHH_Config.Tooltip = true;
10  
11 --FHH_AbilityInfo = { };
12 -- Has the following internal structure:
13 -- REALM_PLAYER = {
14 -- SKILL
15 -- }
16  
17 -- Runtime state
18 FHH_State = { };
19 FHH_State.RealmPlayer = nil;
20 FHH_State.TamingCritter = nil;
21 FHH_State.TamingType = nil;
22  
23 -- Constants
24 MAX_REPORTED_ZONES = 4;
25  
26  
27 function FHH_OnLoad()
28  
29 this:RegisterEvent("PLAYER_ENTERING_WORLD");
30 this:RegisterEvent("UPDATE_MOUSEOVER_UNIT");
31  
32 -- Register Slash Commands
33 SLASH_FHH1 = "/huntershelper";
34 SLASH_FHH2 = "/hh";
35 SlashCmdList["FHH"] = function(msg)
36 FHH_ChatCommandHandler(msg);
37 end
38  
39 GFWUtils.Print("Fizzwidget Hunter's Helper "..FHH_VERSION.." initialized!");
40  
41 end
42  
43 function FHH_OnEvent(event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
44  
45 --DevTools_Dump({event=event, arg1=arg1, arg2=arg2, arg3=arg3, arg4=arg4, arg5=arg5, arg6=arg6, arg7=arg7, arg8=arg8, arg9=arg9});
46  
47 if ( event == "PLAYER_ENTERING_WORLD" ) then
48  
49 _, realClass = UnitClass("player");
50 if (realClass == "HUNTER") then
51 -- only do stuff related to taming and checking hunter spells if you're a hunter.
52 this:RegisterEvent("UNIT_AURA");
53 this:RegisterEvent("UNIT_NAME_UPDATE");
54 this:RegisterEvent("CRAFT_SHOW");
55 this:RegisterEvent("CHAT_MSG_SYSTEM");
56  
57 if (FHH_State.RealmPlayer == nil) then
58 FHH_State.RealmPlayer = GetCVar("realmName") .. "." .. UnitName("player");
59 end
60 if (FHH_AbilityInfo == nil or FHH_AbilityInfo[FHH_State.RealmPlayer] == nil or GFWTable.Count(FHH_AbilityInfo[FHH_State.RealmPlayer]) == 0) then
61  
62 if (realClass == "HUNTER" and UnitLevel("player") > 9) then
63 GFWUtils.Print("Hunter's Helper needs to collect info about what pet skills you already know; please open your Beast Training window. (Info on future skills will be collected as they are learned.)");
64 end
65 end
66 end
67  
68 elseif ( event == "UPDATE_MOUSEOVER_UNIT" ) then
69  
70 if ( UnitExists("mouseover") and not UnitPlayerControlled("mouseover") and FHH_Config.Tooltip ) then
71  
72 local _, myClass = UnitClass("player");
73 if (FHH_Config.Tooltip == "hunter" and myClass ~= "HUNTER") then return; end
74  
75 FHH_ModifyTooltip("mouseover");
76  
77 end
78  
79 elseif ( event == "UNIT_AURA" ) then
80  
81 if ( arg1 == "player" and FHH_HasTameEffect("player") ) then
82 FHH_State.TamingCritter = UnitName("target");
83 local unlocalizedCreepName = GFWTable.KeyOf(FHH_Localized, FHH_State.TamingCritter);
84 if (unlocalizedCreepName) then
85 FHH_State.TamingCritter = unlocalizedCreepName;
86 end
87 FHH_State.TamingType = UnitClassification("target");
88 end
89  
90 elseif ( event == "UNIT_NAME_UPDATE" ) then
91  
92 if ( arg1 == "pet" and FHH_State.TamingCritter ) then
93 local loyaltyDescription = GetPetLoyalty();
94 if (loyaltyDescription) then
95 local _, _, loyaltyLevel = string.find(loyaltyDescription, "(%d+)");
96 if (tonumber(loyaltyLevel) and tonumber(loyaltyLevel) > 1) then
97 GFWUtils.Print("Got "..event.." but pet's loyalty > 1; ignoring.");
98 FHH_State.TamingCritter = nil;
99 FHH_State.TamingType = nil;
100 return;
101 end
102 end
103 if (UnitName("pet") ~= UnitCreatureFamily("pet")) then
104 GFWUtils.Print("Got "..event.." but pet's UnitName() ~= UnitCreatureFamily(); ignoring.");
105 FHH_State.TamingCritter = nil;
106 FHH_State.TamingType = nil;
107 return;
108 end
109 --GFWUtils.Print(event..": checking newly tamed pet");
110 FHH_CheckPetSpells();
111 FHH_State.TamingCritter = nil;
112 FHH_State.TamingType = nil;
113 end
114  
115 elseif ( event == "CRAFT_SHOW" ) then
116  
117 -- Beast Training uses the CraftFrame; we can tell it's not really a craft because it doesn't have a skill-level bar.
118 local name, rank, maxRank = GetCraftDisplaySkillLine();
119 if ( name ) then return; end
120  
121 FHH_ScanCraftFrame();
122  
123 elseif ( event == "CHAT_MSG_SYSTEM" ) then
124  
125 local pattern = GFWUtils.FormatToPattern(ERR_LEARN_SPELL_S); -- "You have learned a new spell: %s."
126 local _, _, compositeSpellName = string.find(arg1, pattern);
127 if (compositeSpellName == nil) then return; end
128  
129 local _, _, spellName, rankNum = string.find(compositeSpellName, "(.+) %(.+ (%d+)%)");
130 if (spellName and rankNum and spellName ~= "" and rankNum ~= "" ) then
131 spellName = string.gsub(spellName, "^%s+", ""); -- strip leading spaces
132 spellName = string.gsub(spellName, "%s+$", ""); -- and trailing spaces
133 local spellID = FHH_SpellIDforName(spellName);
134 if (FHH_NewInfo and FHH_NewInfo.SpellIDAliases and FHH_NewInfo.SpellIDAliases[spellID]) then
135 spellID = FHH_NewInfo.SpellIDAliases[spellID];
136 end
137 if (spellID and (FHH_RequiredLevel[spellID] or (FHH_NewInfo and FHH_NewInfo.RequiredLevel and FHH_NewInfo.RequiredLevel[spellID]))) then
138 -- only track spells we know are hunter pet spells
139 if (FHH_AbilityInfo == nil) then
140 FHH_AbilityInfo = {};
141 end
142 if (FHH_AbilityInfo[FHH_State.RealmPlayer] == nil or table.getn(FHH_AbilityInfo[FHH_State.RealmPlayer]) == 0) then
143 FHH_AbilityInfo[FHH_State.RealmPlayer] = { };
144 end
145 table.insert(FHH_AbilityInfo[FHH_State.RealmPlayer], spellName.." "..rankNum);
146 table.sort(FHH_AbilityInfo[FHH_State.RealmPlayer]);
147 end
148 end
149  
150 end
151  
152 end
153  
154 function FHH_ChatCommandHandler(msg)
155  
156 -- Print Help
157 if ( msg == "help" ) or ( msg == "" ) then
158 GFWUtils.Print("Fizzwidget Hunter's Helper "..FHH_VERSION..":");
159 GFWUtils.Print("/huntershelper /hh <command>");
160 GFWUtils.Print("- "..GFWUtils.Hilite("help").." - Print this helplist.");
161 GFWUtils.Print("- "..GFWUtils.Hilite("on").." | "..GFWUtils.Hilite("off").." | "..GFWUtils.Hilite("onlyhunter").." - Turn display of beast abilities in tooltips on or off, or make them only appear if you're playing a hunter.");
162 GFWUtils.Print("- "..GFWUtils.Hilite("status").." - Check current settings.");
163 GFWUtils.Print("- "..GFWUtils.Hilite("find <ability> <rank>").." - List where beasts with a given ability (e.g. Bite 6) can be found.");
164 return;
165 end
166  
167 if (msg == "version") then
168 GFWUtils.Print("Fizzwidget Hunter's Helper "..FHH_VERSION);
169 return;
170 end
171  
172 if (msg == "onlyhunter") then
173 FHH_Config.Tooltip = "hunter";
174 GFWUtils.Print("Hunter's Helper is enabled; beast tooltips will display ability info only when playing a Hunter character.");
175 return;
176 end
177 if (msg == "on") then
178 FHH_Config.Tooltip = true;
179 GFWUtils.Print("Hunter's Helper is enabled; beast tooltips will display ability info.");
180 return;
181 end
182 if (msg == "off") then
183 FHH_Config.Tooltip = nil;
184 GFWUtils.Print("Hunter's Helper is disabled; no extra info added to tooltips.");
185 return;
186 end
187  
188 -- Check Status
189 if ( msg == "status" ) then
190 if ( FHH_Config.Tooltip == "hunter" ) then
191 GFWUtils.Print("Hunter's Helper is enabled; beast tooltips will display ability info only when playing as a Hunter.");
192 elseif ( FHH_Config.Tooltip ) then
193 GFWUtils.Print("Hunter's Helper is enabled; beast tooltips will display ability info.");
194 else
195 GFWUtils.Print("Hunter's Helper is disabled; no extra info added to tooltips.");
196 end
197 return;
198 end
199  
200 if (msg == "reset") then
201 FHH_Config = { };
202 FHH_Config.Tooltip = true;
203 FHH_AbilityInfo = nil;
204 FHH_NewInfo = nil;
205  
206 GFWUtils.Print("Hunter's Helper has been reset to default options and all stored data cleared.");
207 return;
208 end
209  
210 if (msg == "test") then
211 FHH_RunAllTests();
212 return;
213 end
214  
215 if (msg == "dynamic") then
216 FHH_SpellNamesToIDs = {};
217 FHH_SpellIDsToNames = {};
218 FHH_LearnableBy = {};
219 FHH_RequiredLevel = {};
220 FHH_SpellInfo = {};
221 FHH_BeastInfo = {};
222 FHH_BeastLevels = {};
223 GFWUtils.Print("Hunter's Helper: only consulting dynamic tables until next reload.");
224 return;
225 end
226  
227 local _, _, cmd, spellName, rankNum = string.find(msg, "(find%w*) ([^%d]+) *(%d*)");
228 if (cmd == "find" or cmd == "findall") then
229 if (spellName == nil or spellName == "") then
230 GFWUtils.Print("Usage: "..GFWUtils.Hilite("/hh find <ability> <rank>"));
231 return;
232 end
233  
234 spellName = string.gsub(spellName, "^%s+", ""); -- strip leading spaces
235 spellName = string.gsub(spellName, "%s+$", ""); -- and trailing spaces
236 spellName = string.lower(spellName);
237 local spellID;
238  
239 -- first, look up the input against our spell ID keys
240 if (FHH_SpellIDsToNames[spellName]) then
241 spellID = spellName;
242 end
243 if (spellID == nil and FHH_NewInfo and FHH_NewInfo.SpellIDsToNames and FHH_NewInfo.SpellIDsToNames[spellName]) then
244 spellID = spellName;
245 end
246  
247 -- failing that, try looking it up as a proper name, case insensitively
248 if (spellID == nil) then
249 for properName in FHH_SpellNamesToIDs do
250 if (string.lower(properName) == spellName) then
251 spellID = FHH_SpellNamesToIDs[properName];
252 end
253 end
254 if (spellID == nil and FHH_NewInfo and FHH_NewInfo.SpellNamesToIDs) then
255 for properName in FHH_NewInfo.SpellNamesToIDs do
256 if (string.lower(properName) == spellName) then
257 spellID = FHH_NewInfo.SpellNamesToIDs[properName];
258 end
259 end
260 end
261 end
262  
263 if (spellID == nil) then
264 GFWUtils.Print(GFWUtils.Hilite(spellName).." is not a known beast ability.");
265 return;
266 end
267 FHH_Find(spellID, rankNum);
268 return;
269 end
270  
271 -- if we got all the way to here, we got invalid input.
272 FHH_ChatCommandHandler("help");
273  
274 end
275  
276 function FHH_ModifyTooltip(unit)
277 local creepName = UnitName(unit);
278 local creepLevel = UnitLevel(unit);
279 local creepFamily = UnitCreatureFamily(unit);
280 local creepType = UnitClassification(unit);
281 local abilitiesLine;
282  
283 local unlocalizedCreepName = GFWTable.KeyOf(FHH_Localized, creepName);
284 if (unlocalizedCreepName) then
285 creepName = unlocalizedCreepName;
286 end
287  
288 -- if this beast is in our database, make sure we have the right level range & type info
289 FHH_CheckBeastLevel(creepName, creepLevel, creepType);
290  
291 -- if this is a Beast Lore tooltip, parse out and use its tamed abilities info
292 if (FHH_TAMED_ABILS_PATTERN == nil) then
293 FHH_TAMED_ABILS_PATTERN = GFWUtils.FormatToPattern(PET_SPELLS_TEMPLATE);
294 end
295 for lineNum = 1, GameTooltip:NumLines() do
296 local lineText = getglobal("GameTooltipTextLeft"..lineNum):GetText();
297 if (lineText) then
298 if (string.find(lineText, LIGHTYELLOW_FONT_COLOR_CODE)) then
299 return; -- if we've already added a line to this tooltip, we should stop.
300 end
301 local _, _, beastLoreInfo = string.find(lineText, FHH_TAMED_ABILS_PATTERN);
302 if (beastLoreInfo) then
303 abilitiesLine = lineNum;
304 local beastLoreList = GFWUtils.Split(beastLoreInfo, ", ");
305 local beastSpellTable = {};
306 for _, niceSpellName in beastLoreList do
307 local _, _, spellName, rankNum = string.find(niceSpellName, "^(.+) %(.+ (%d+)%)$");
308 if (spellName == nil or spellName == "" or tonumber(rankNum) == nil) then
309 GFWUtils.PrintOnce(GFWUtils.Red("Hunter's Helper Error: ").."Can't parse spell "..GFWUtils.Hilite(niceSpellName).." from "..GFWUtils.Hilite(critter)..".");
310 else
311 spellName = string.gsub(spellName, "^%s+", ""); -- strip leading spaces
312 spellName = string.gsub(spellName, "%s+$", ""); -- and trailing spaces
313 local spellID = FHH_SpellIDforName(spellName);
314 if (FHH_NewInfo and FHH_NewInfo.SpellIDAliases and FHH_NewInfo.SpellIDAliases[spellID]) then
315 spellID = FHH_NewInfo.SpellIDAliases[spellID];
316 end
317 if (spellID == nil) then
318 spellID = FHH_RecordNewSpellID(spellName, true);
319 end
320 beastSpellTable[spellID] = tonumber(rankNum);
321 end
322 end
323 FHH_CheckSpellTables(creepName, beastSpellTable, creepLevel, creepFamily);
324 end
325 end
326 end
327  
328 -- look up the list of abilities we think this critter has
329 local abilitiesList = nil;
330 if (FHH_NewInfo and FHH_NewInfo.BeastInfo and FHH_NewInfo.BeastInfo[creepName]) then
331 abilitiesList = FHH_NewInfo.BeastInfo[creepName];
332 elseif (FHH_BeastInfo[creepName]) then
333 abilitiesList = FHH_BeastInfo[creepName];
334 if (FHH_NewInfo and FHH_NewInfo.BadBeastInfo and FHH_NewInfo.BadBeastInfo[creepName]) then
335 local newAbilitiesList = {};
336 for spellID, rankNum in abilitiesList do
337 if (FHH_NewInfo.BadBeastInfo[creepName][spellID] ~= rankNum) then
338 newAbilitiesList[spellID] = rankNum;
339 end
340 end
341 abilitiesList = newAbilitiesList;
342 end
343 end
344  
345 if (abilitiesList and GFWTable.Count(abilitiesList) > 0) then
346  
347 -- build textual description from that list (with color coding if you're a hunter)
348 local coloredList = {};
349 local _, myClass = UnitClass("player");
350 for spellName, rankNum in abilitiesList do
351 if (myClass == "HUNTER" and FHH_AbilityInfo and FHH_AbilityInfo[FHH_State.RealmPlayer] and GFWTable.Count(FHH_AbilityInfo[FHH_State.RealmPlayer]) > 0) then
352 local playerRanks = FHH_AbilityInfo[FHH_State.RealmPlayer][spellName];
353 if (playerRanks and GFWTable.IndexOf(playerRanks, rankNum) ~= 0) then
354 table.insert(coloredList, GRAY_FONT_COLOR_CODE..FHH_SpellDescription(spellName, rankNum)..FONT_COLOR_CODE_CLOSE);
355 else
356 table.insert(coloredList, GREEN_FONT_COLOR_CODE..FHH_SpellDescription(spellName, rankNum)..FONT_COLOR_CODE_CLOSE);
357 end
358 else
359 table.insert(coloredList, FHH_SpellDescription(spellName, rankNum));
360 end
361 end
362 local abilitiesText = table.concat(coloredList, ", ");
363 abilitiesText = string.gsub(abilitiesText, "( %d+)", " ("..RANK.."%1)");
364  
365 -- add it to the tooltip (or, if Beast Lore, replace its line with our color-coded one)
366 if (abilitiesLine) then
367 local lineText = getglobal("GameTooltipTextLeft"..abilitiesLine);
368 lineText:SetText(GFWUtils.LtY(string.format(PET_SPELLS_TEMPLATE, abilitiesText)));
369 else
370 GameTooltip:AddLine(GFWUtils.LtY(string.format(PET_SPELLS_TEMPLATE, abilitiesText)), 1.0, 1.0, 1.0);
371 GameTooltip:SetHeight(GameTooltip:GetHeight() + 14);
372 local width = 20 + getglobal(GameTooltip:GetName().."TextLeft"..GameTooltip:NumLines()):GetWidth();
373 if ( GameTooltip:GetWidth() < width ) then
374 GameTooltip:SetWidth(width);
375 end
376 end
377 end
378  
379 end
380  
381 function FHH_ScanCraftFrame()
382 local numCrafts = GetNumCrafts();
383 if not ( numCrafts > 0 )then return; end
384  
385 if (FHH_AbilityInfo == nil) then
386 FHH_AbilityInfo = {};
387 end
388 if (FHH_AbilityInfo[FHH_State.RealmPlayer] == nil or table.getn(FHH_AbilityInfo[FHH_State.RealmPlayer]) == 0) then
389 FHH_AbilityInfo[FHH_State.RealmPlayer] = { };
390 end
391  
392 for i=1, numCrafts do
393 local craftName, craftSubSpellName, _, _, _, _, requiredLevel = GetCraftInfo(i);
394 local _, _, rankNum = string.find(craftSubSpellName, "(%d)");
395 if (rankNum and tonumber(rankNum)) then
396 rankNum = tonumber(rankNum);
397 end
398 local craftIcon = GetCraftIcon(i);
399 if (craftIcon) then
400 craftIcon = string.gsub(craftIcon, "^Interface\\Icons\\", "");
401 end
402  
403 local spellID = FHH_SpellIDforIcon(craftIcon, craftName);
404 local nameSpellID = FHH_SpellIDforName(craftName);
405 if (spellID and nameSpellID and spellID ~= nameSpellID) then
406 if (FHH_NewInfo == nil) then
407 FHH_NewInfo = {};
408 end
409 if (FHH_NewInfo.SpellIDAliases == nil) then
410 FHH_NewInfo.SpellIDAliases = {};
411 end
412 FHH_NewInfo.SpellIDAliases[nameSpellID] = spellID;
413 end
414  
415 if (FHH_AbilityInfo[FHH_State.RealmPlayer][spellID] == nil) then
416 FHH_AbilityInfo[FHH_State.RealmPlayer][spellID] = {};
417 end
418 if (GFWTable.IndexOf(FHH_AbilityInfo[FHH_State.RealmPlayer][spellID], rankNum) == 0) then
419 table.insert(FHH_AbilityInfo[FHH_State.RealmPlayer][spellID], rankNum)
420 end;
421  
422 if ( requiredLevel and requiredLevel > 0 ) then
423 FHH_RecordNewRequiredLevel(spellID, tonumber(rankNum), requiredLevel, true);
424 end
425 end
426 FHH_ProcessAliases();
427 end
428  
429 function FHH_Find(spellID, rankNum)
430 local niceSpellName = FHH_SpellIDsToNames[spellID];
431 if (niceSpellName == nil and FHH_NewInfo and FHH_NewInfo.SpellIDsToNames and FHH_NewInfo.SpellIDsToNames[spellID]) then
432 niceSpellName = FHH_NewInfo.SpellIDsToNames[spellID];
433 end
434  
435 local spellInfo = FHH_SpellInfo[spellID];
436 local newSpellInfo;
437 if (FHH_NewInfo and FHH_NewInfo.SpellInfo) then
438 newSpellInfo = FHH_NewInfo.SpellInfo[spellID];
439 end
440 if (spellInfo == nil or (type(spellInfo) == "table" and GFWTable.Count(spellInfo) == 0)) then
441 if (newSpellInfo == nil or GFWTable.Count(newSpellInfo) == 0) then
442 GFWUtils.Print("No info available for ".. GFWUtils.Hilite(niceSpellName)..".");
443 return;
444 else
445 spellInfo = newSpellInfo;
446 end
447 end
448 local rankTable = FHH_RequiredLevel[spellID];
449 local newRankTable;
450 if (FHH_NewInfo and FHH_NewInfo.RequiredLevel) then
451 newRankTable = FHH_NewInfo.RequiredLevel[spellID];
452 end
453 if (rankTable == nil or GFWTable.Count(rankTable) == 0) then
454 if (newRankTable == nil or GFWTable.Count(newRankTable) == 0) then
455 GFWUtils.Print(GFWUtils.Red("Hunter's Helper "..FHH_VERSION.." error:").." found "..GFWUtils.Hilite(niceSpellName).." but can't find rank info. Please report to gazmik@fizzwidget.com");
456 return;
457 else
458 rankTable = newRankTable;
459 end
460 end
461  
462 rankNum = tonumber(rankNum);
463 if (rankNum) then
464 if not (rankTable[rankNum]) then
465 GFWUtils.Print(GFWUtils.Hilite(niceSpellName).." is not known to have a rank "..GFWUtils.Hilite(rankNum)..".");
466 return;
467 end
468  
469 -- report minimum pet level for ability
470 local minLevel = rankTable[rankNum];
471 local petLevel = 60;
472 if (UnitExists("pet")) then
473 petLevel = tonumber(UnitLevel("pet"));
474 end
475 if (minLevel == nil) then
476 minLevel = newRankTable[rankNum];
477 end
478 if (minLevel == nil) then
479 GFWUtils.Print(GFWUtils.Red("Hunter's Helper "..FHH_VERSION.." error:").." can't find required level for "..GFWUtils.Hilite(niceSpellName.." "..rankNum)..". Please report to gazmik@fizzwidget.com");
480 else
481 if (type(minLevel) == "string") then
482 GFWUtils.Print(GFWUtils.Hilite(niceSpellName.." "..rankNum).." requires at least pet level "..GFWUtils.Hilite(minLevel)..". (Assumed because it was found on a beast of this level that you tamed. Open you Beast Training window and Hunter's Helper can collect more accurate information.)");
483 elseif (petLevel >= minLevel) then
484 GFWUtils.Print(GFWUtils.Hilite(niceSpellName.." "..rankNum).." requires pet level "..GFWUtils.Hilite(minLevel)..".");
485 else
486 GFWUtils.Print(GFWUtils.Hilite(niceSpellName.." "..rankNum).." requires pet level "..GFWUtils.Red(minLevel)..".");
487 end
488 end
489 else
490 local knownRanks = {};
491 for rankNum in rankTable do
492 table.insert(knownRanks, rankNum);
493 end
494 local newRanks = {};
495 for rankNum in (newRankTable or {}) do
496 table.insert(newRanks, rankNum);
497 end
498 local allRanks = GFWTable.Merge(knownRanks, newRanks);
499 GFWUtils.Print("Ranks known about for "..GFWUtils.Hilite(niceSpellName)..": "..table.concat(allRanks, " "));
500 if (type(spellInfo) ~= "string") then
501 GFWUtils.Print("Type "..GFWUtils.Hilite("/hh find "..spellID).." and a number to get info about that rank.");
502 end
503 end
504  
505 -- report available creature families
506 local families = FHH_LearnableBy[spellID];
507 if (type(families) == "table" and FHH_NewInfo and FHH_NewInfo.LearnableBy and FHH_NewInfo.LearnableBy[spellID]) then
508 families = GFWTable.Merge(families, FHH_NewInfo.LearnableBy[spellID]);
509 if (table.getn(GFWTable.Diff(families, FHH_AllFamilies)) == 0 ) then
510 families = FHH_ALL_FAMILIES;
511 end
512 end
513 if (families or (type(families) == "table" and table.getn(families) == 0)) then
514 if (type(families) == "string") then
515 GFWUtils.Print(GFWUtils.Hilite(niceSpellName).." is learnable by "..GFWUtils.Hilite(families)..".");
516 else
517 local listText = table.concat(families, ", ");
518 GFWUtils.Print(GFWUtils.Hilite(niceSpellName).." is learnable by: "..GFWUtils.Hilite(listText)..".");
519 end
520 end
521  
522 -- case 1: first levels of Growl are innate
523 if (spellID == "growl" and rankNum and rankNum <= 2) then
524 GFWUtils.Print("You should already know "..GFWUtils.Hilite(niceSpellName.." "..rankNum).." if you've learned Beast Training.");
525 return;
526 end
527  
528 -- case 2: spells taught by trainers, for which rank doesn't matter
529 if (type(spellInfo) == "string") then
530 local spellSummary = niceSpellName;
531 if (rankNum) then
532 spellSummary = spellSummary.." "..rankNum;
533 end
534 GFWUtils.Print(GFWUtils.Hilite(spellSummary).." is learned from "..spellInfo..".");
535 return;
536 end
537  
538 if (rankNum == nil) then return; end
539  
540 --case 3: lookup by spell and rank, report by zone (sanity check first)
541 local spellRankInfo = spellInfo[rankNum];
542 local maxZones = MAX_REPORTED_ZONES;
543 if (cmd == "findall") then
544 maxZones = 100; -- arbitrarily high so we find everything.
545 end
546 if (spellRankInfo == nil ) then
547 GFWUtils.Print("Hunter's Helper doesn't know about any creatures with "..GFWUtils.Hilite(niceSpellName.." "..rankNum)..".");
548 return;
549 end
550 GFWUtils.Print(GFWUtils.Hilite(niceSpellName.." "..rankNum).." can be learned from:");
551 local numReportedZones = 0;
552 local zoneName = GFWZones.UnlocalizedZone(GetRealZoneText());
553 local critterList = {};
554 if (spellRankInfo[zoneName] and table.getn(spellRankInfo[zoneName]) > 0) then
555 critterList = GFWTable.Merge(critterList, spellRankInfo[zoneName]);
556 end
557 if (FHH_NewInfo and FHH_NewInfo.SpellInfo and FHH_NewInfo.SpellInfo[spellID] and FHH_NewInfo.SpellInfo[spellID][rankNum] and FHH_NewInfo.SpellInfo[spellID][rankNum][zoneName]) then
558 critterList = GFWTable.Merge(critterList, FHH_NewInfo.SpellInfo[spellID][rankNum][zoneName]);
559 end
560 if (table.getn(critterList) > 0) then
561 GFWUtils.Print(GFWZones.LocalizedZone(zoneName)..": "..GFWUtils.Hilite(FHH_CreatureListString(critterList)));
562 numReportedZones = numReportedZones + 1;
563 end
564  
565 local zoneConnections = GFWZones.ConnectionsForZone(zoneName);
566  
567 if (zoneConnections == nil) then
568 -- player is in an unknown zone; instead of doing nothing, let's pick a known zone to start searching from.
569 local _, race = UnitRace("player");
570 if (race == "Night Elf") then
571 zoneName = "Teldrassil";
572 elseif (race == "Dwarf") then
573 zoneName = "Dun Morogh";
574 elseif (race == "Gnome") then
575 zoneName = "Dun Morogh";
576 elseif (race == "Human") then
577 zoneName = "Elwynn Forest";
578 elseif (race == "Tauren") then
579 zoneName = "Mulgore";
580 elseif (race == "Orc") then
581 zoneName = "Durotar";
582 elseif (race == "Troll") then
583 zoneName = "Durotar";
584 elseif (race == "Scourge") then
585 zoneName = "Tirisfal Glades";
586 else
587 -- unlikely, but in case we can't parse the race name...
588 local faction = UnitFactionGroup("player");
589 if (faction == "Alliance") then
590 zoneName = "Ironforge";
591 elseif (faction == "Horde") then
592 zoneName = "Orgrimmar";
593 else
594 -- on the off chance we can't even parse a major-faction name...
595 zoneName = "Stranglethorn Vale";
596 end
597 end
598 zoneConnections = GFWZones.ConnectionsForZone(zoneName);
599 end
600  
601 for _, zones in zoneConnections do
602 for _, zoneName in zones do
603 local critterList = {};
604 if (spellRankInfo[zoneName] and table.getn(spellRankInfo[zoneName]) > 0) then
605 critterList = GFWTable.Merge(critterList, spellRankInfo[zoneName]);
606 end
607 if (FHH_NewInfo and FHH_NewInfo.SpellInfo and FHH_NewInfo.SpellInfo[spellID] and FHH_NewInfo.SpellInfo[spellID][rankNum] and FHH_NewInfo.SpellInfo[spellID][rankNum][zoneName]) then
608 critterList = GFWTable.Merge(critterList, FHH_NewInfo.SpellInfo[spellID][rankNum][zoneName]);
609 end
610 if (table.getn(critterList) > 0) then
611 GFWUtils.Print(GFWZones.LocalizedZone(zoneName)..": "..GFWUtils.Hilite(FHH_CreatureListString(critterList)));
612 numReportedZones = numReportedZones + 1;
613 if (numReportedZones >= maxZones) then return; end
614 end
615 end
616 end
617  
618 if (numReportedZones == 0) then
619 -- if we get here, we think we know about the spell but can't find beasts with it in our table. this shouldn't happen.
620 GFWUtils.Print(GFWUtils.Red("Hunter's Helper "..FHH_VERSION.." error:").." got spell info for "..GFWUtils.Hilite(niceSpellName.." "..rankNum).." but no zone info. Please report to gazmik@fizzwidget.com.");
621 end
622 end
623  
624 function FHH_CreatureListString(critterList)
625 local listString = ""
626 for _, name in critterList do
627 local info = FHH_BeastLevels[name];
628 if (info == nil and FHH_NewInfo and FHH_NewInfo.BeastLevels) then
629 info = FHH_NewInfo.BeastLevels[name];
630 end
631 if (info == nil) then
632 listString = listString..", ";
633 else
634 local unlocalizedName = FHH_Localized[name];
635 if (unlocalizedName) then
636 name = unlocalizedName;
637 end
638 listString = listString .. name .. " ";
639 local myLevel = UnitLevel("player");
640 local minLevel = info.min;
641 local maxLevel = info.max;
642 if (info.min > UnitLevel("player")) then
643 minLevel = RED_FONT_COLOR_CODE..info.min..FONT_COLOR_CODE_CLOSE;
644 end
645 if (info.max and info.max > UnitLevel("player")) then
646 maxLevel = RED_FONT_COLOR_CODE..info.max..FONT_COLOR_CODE_CLOSE;
647 end
648 if (info.min == info.max or info.max == nil) then
649 listString = listString.."("..minLevel;
650 else
651 listString = listString.."("..minLevel.."-"..maxLevel;
652 end
653 if (info.type == nil) then
654 listString = listString.."), ";
655 else
656 listString = listString.." "..info.type.."), ";
657 end
658 end
659 end
660 listString = string.gsub(listString, ", $", "");
661 return listString;
662 end
663  
664 function FHH_HasTameEffect(unit)
665  
666 local i = 1;
667 local buff;
668 buff = UnitBuff(unit, i);
669 while buff do
670 if ( string.find(buff, "Ability_Hunter_BeastTaming") ) then
671 return true;
672 end
673 i = i + 1;
674 buff = UnitBuff(unit, i);
675 end
676 return false;
677  
678 end
679  
680 function FHH_SpellIDforName(spellName)
681 local spellID = FHH_SpellNamesToIDs[spellName];
682 if (spellID == nil and FHH_NewInfo and FHH_NewInfo.SpellNamesToIDs) then
683 spellID = FHH_NewInfo.SpellNamesToIDs[spellName];
684 end
685 return spellID;
686 end
687  
688 function FHH_SpellIDforIcon(spellIcon, spellName)
689 local spellID = FHH_SpellIcons[spellIcon];
690 if (spellID == nil and FHH_NewInfo and FHH_NewInfo.SpellIcons) then
691 spellID = FHH_NewInfo.SpellIcons[spellIcon];
692 end
693 if (spellID == nil) then
694 spellID = FHH_SpellIDforName(spellName);
695 end
696 if (spellID == nil) then
697 spellID = FHH_RecordNewSpellIcon(spellIcon, spellName);
698 end
699 return spellID;
700 end
701  
702 function FHH_CheckPetSpells()
703 local currentPetSpells = { };
704 local i = 1;
705 local spellName, spellRank = GetSpellName(i, BOOKTYPE_PET);
706 local spellIcon = GetSpellTexture(i, BOOKTYPE_PET);
707 while spellName do
708 local _, _, rankNum = string.find(spellRank, "(%d+)");
709 if (spellIcon) then
710 spellIcon = string.gsub(spellIcon, "^Interface\\Icons\\", "");
711 end
712 local spellID = FHH_SpellIDforIcon(spellIcon, spellName);
713 local nameSpellID = FHH_SpellIDforName(spellName);
714 if (spellID and nameSpellID and spellID ~= nameSpellID) then
715 if (FHH_NewInfo == nil) then
716 FHH_NewInfo = {};
717 end
718 if (FHH_NewInfo.SpellIDAliases == nil) then
719 FHH_NewInfo.SpellIDAliases = {};
720 end
721 FHH_NewInfo.SpellIDAliases[nameSpellID] = spellID;
722 end
723  
724 currentPetSpells[spellID] = tonumber(rankNum);
725 i = i + 1;
726 spellName, spellRank = GetSpellName(i, BOOKTYPE_PET);
727 spellIcon = GetSpellTexture(i, BOOKTYPE_PET);
728 end
729  
730 if (GFWTable.Count(currentPetSpells) > 0) then
731 FHH_ProcessAliases();
732 FHH_CheckSpellTables(FHH_State.TamingCritter, currentPetSpells);
733 else
734 --GFWUtils.Print("pet has no spells");
735 end
736 end
737  
738 function FHH_SpellDescription(spellID, rankNum)
739 local niceSpellName = FHH_SpellIDsToNames[spellID];
740 if (niceSpellName == nil and FHH_NewInfo and FHH_NewInfo.SpellIDsToNames and FHH_NewInfo.SpellIDsToNames[spellID]) then
741 niceSpellName = FHH_NewInfo.SpellIDsToNames[spellID];
742 end
743 if (niceSpellName == nil) then
744 niceSpellName = spellID;
745 end
746 return niceSpellName.." "..rankNum;
747 end
748  
749 function FHH_SpellDescriptions(spellList)
750 local descriptions = {};
751 for spellID, rankNum in spellList do
752 table.insert(descriptions, FHH_SpellDescription(spellID, rankNum));
753 end
754 return descriptions;
755 end
756  
757 function FHH_SpellDescripionList(spellList)
758 return table.concat(FHH_SpellDescriptions(spellList), ", ");
759 end
760  
761 function FHH_CheckSpellTables(critter, spellList, level, family)
762  
763 if ( spellList == nil or GFWTable.Count(spellList) == 0 ) then return; end
764  
765 -- process any recently learned spellID aliases so we record data correctly.
766 local newSpellList = {};
767 local changed = false;
768 for spellID, rankNum in spellList do
769 if (FHH_NewInfo and FHH_NewInfo.SpellIDAliases and FHH_NewInfo.SpellIDAliases[spellID]) then
770 spellID = FHH_NewInfo.SpellIDAliases[spellID];
771 changed = true;
772 end
773 newSpellList[spellID] = rankNum;
774 end
775 if (changed) then
776 spellList = newSpellList;
777 end
778  
779 if (level == nil) then
780 level = UnitLevel("pet");
781 end
782 if (family == nil) then
783 family = UnitCreatureFamily("pet");
784 end
785  
786 if ( FHH_BeastInfo[critter] ) then
787  
788 -- record any spells the critter has that our built-in table doesn't know about
789 local unknownPetSpells = { };
790 for spellID, rankNum in spellList do
791 if ( FHH_BeastInfo[critter][spellID] == nil ) then
792 unknownPetSpells[spellID] = rankNum;
793 end
794 end
795 if ( GFWTable.Count(unknownPetSpells) > 0 ) then
796 if (FHH_NewInfo == nil) then
797 FHH_NewInfo = {};
798 end
799 if (FHH_NewInfo.BeastInfo == nil) then
800 FHH_NewInfo.BeastInfo = {};
801 end
802 FHH_NewInfo.BeastInfo[critter] = spellList; -- we want to remember the entire current spells list
803 end
804  
805 -- record any spells our built-in table thinks the critter has, but the critter actually doesn't
806 local wrongPetSpells = { };
807 for spellID, rankNum in FHH_BeastInfo[critter] do
808 if ( spellList[spellID] ~= rankNum ) then
809 wrongPetSpells[spellID] = rankNum;
810 end
811 end
812 if ( GFWTable.Count(wrongPetSpells) > 0 ) then
813 if (FHH_NewInfo == nil) then
814 FHH_NewInfo = {};
815 end
816 if (FHH_NewInfo.BadBeastInfo == nil) then
817 FHH_NewInfo.BadBeastInfo = {};
818 end
819 FHH_NewInfo.BadBeastInfo[critter] = wrongPetSpells;
820 end
821  
822 if (FHH_NewInfo and (( FHH_NewInfo.BeastInfo and FHH_NewInfo.BeastInfo[critter]) or (FHH_NewInfo.BadBeastInfo and FHH_NewInfo.BadBeastInfo[critter]))) then
823 local details = "(expected "..FHH_SpellDescripionList(FHH_BeastInfo[critter]).."; found "..FHH_SpellDescripionList(spellList)..").";
824 GFWUtils.PrintOnce("Hunter's Helper "..FHH_VERSION.." has incorrect data on "..GFWUtils.Hilite(critter.." "..details).." Please submit a correction to gazmik@fizzwidget.com.)", 60);
825 end
826  
827 else
828  
829 -- this pet is entirely new to our list
830 if (FHH_NewInfo == nil) then
831 FHH_NewInfo = {};
832 end
833 if (FHH_NewInfo.BeastInfo == nil) then
834 FHH_NewInfo.BeastInfo = {};
835 end
836 FHH_NewInfo.BeastInfo[critter] = spellList;
837 FHH_CheckBeastLevel(critter, level, FHH_State.TamingType);
838  
839 local details = "(found "..FHH_SpellDescripionList(spellList).." in "..GetRealZoneText()..").";
840 GFWUtils.PrintOnce("Hunter's Helper "..FHH_VERSION.." has no data on "..GFWUtils.Hilite(critter.." "..details).." Please submit a correction to gazmik@fizzwidget.com.)", 60);
841  
842 end
843  
844 for spellID, rankNum in spellList do
845 FHH_RecordNewSpellInfo(spellID, rankNum, critter);
846 FHH_RecordNewRequiredFamily(spellID, family);
847 FHH_RecordNewRequiredLevel(spellID, rankNum, level);
848 end
849  
850 end
851  
852 function FHH_RecordNewSpellInfo(spellID, rankNum, critter)
853 if (FHH_SpellInfo[spellID] and FHH_SpellInfo[spellID][rankNum]) then
854 for zoneName, beastsTable in FHH_SpellInfo[spellID][rankNum] do
855 for _, aBeast in beastsTable do
856 if (aBeast == critter) then
857 return; -- we've already recorded this in our static data
858 end
859 end
860 end
861 end
862  
863 if (FHH_NewInfo == nil) then
864 FHH_NewInfo = {};
865 end
866 if (FHH_NewInfo.SpellInfo == nil) then
867 FHH_NewInfo.SpellInfo = {};
868 end
869 if (FHH_NewInfo.SpellInfo[spellID] == nil) then
870 FHH_NewInfo.SpellInfo[spellID] = {};
871 end
872 if (FHH_NewInfo.SpellInfo[spellID][rankNum] == nil) then
873 FHH_NewInfo.SpellInfo[spellID][rankNum] = {};
874 end
875 local currentZone = GFWZones.UnlocalizedZone(GetRealZoneText());
876 if (FHH_NewInfo.SpellInfo[spellID][rankNum][currentZone] == nil) then
877 FHH_NewInfo.SpellInfo[spellID][rankNum][currentZone] = {};
878 end
879 if (not GFWTable.KeyOf(FHH_NewInfo.SpellInfo[spellID][rankNum][currentZone], critter)) then
880 table.insert(FHH_NewInfo.SpellInfo[spellID][rankNum][currentZone], critter);
881 end
882 end
883  
884 function FHH_RecordNewRequiredFamily(spellID, family)
885 if (FHH_LearnableBy[spellID] and GFWTable.KeyOf(FHH_LearnableBy[spellID], family)) then
886 return; -- we've already recorded this in our static data
887 end
888  
889 if (FHH_NewInfo == nil) then
890 FHH_NewInfo = {};
891 end
892 if (FHH_NewInfo.LearnableBy == nil) then
893 FHH_NewInfo.LearnableBy = {};
894 end
895 if (FHH_NewInfo.LearnableBy[spellID] == nil) then
896 FHH_NewInfo.LearnableBy[spellID] = {};
897 end
898 if (not GFWTable.KeyOf(FHH_NewInfo.LearnableBy[spellID], family)) then
899 table.insert(FHH_NewInfo.LearnableBy[spellID], family);
900 end
901 end
902  
903 function FHH_RecordNewRequiredLevel(spellID, rankNum, level, verified)
904 if (FHH_RequiredLevel[spellID] and FHH_RequiredLevel[spellID][rankNum]) then
905 return; -- we've already recorded this in our static data
906 end
907  
908 if (FHH_NewInfo == nil) then
909 FHH_NewInfo = {};
910 end
911 if (FHH_NewInfo.RequiredLevel == nil) then
912 FHH_NewInfo.RequiredLevel = {};
913 end
914 if (FHH_NewInfo.RequiredLevel[spellID] == nil) then
915 FHH_NewInfo.RequiredLevel[spellID] = {};
916 end
917 if (verified) then
918 FHH_NewInfo.RequiredLevel[spellID][rankNum] = level;
919 elseif (FHH_NewInfo.RequiredLevel[spellID][rankNum] == nil) then
920 FHH_NewInfo.RequiredLevel[spellID][rankNum] = tostring(level);
921 else
922 local existingRank = FHH_NewInfo.RequiredLevel[spellID][rankNum];
923 if (type(existingRank) == "string") then
924 -- we don't have a certain answer yet, we'll use what we just got to refine it
925 FHH_NewInfo.RequiredLevel[spellID][rankNum] = tostring(math.min(level, tonumber(existingRank)));
926 end
927 end
928 end
929  
930 function FHH_CheckBeastLevel(creepName, creepLevel, creepType)
931 if (creepLevel < 1) then
932 return; -- UnitLevel sometimes returns -1 for common mobs (maybe a WDB cache thing) so we toss nonsensical values.
933 end
934  
935 if (FHH_NewInfo and FHH_NewInfo.BeastLevels and FHH_NewInfo.BeastLevels[creepName]) then
936 FHH_NewInfo.BeastLevels[creepName].min = math.min(FHH_NewInfo.BeastLevels[creepName].min, creepLevel);
937 FHH_NewInfo.BeastLevels[creepName].max = math.max(FHH_NewInfo.BeastLevels[creepName].max, creepLevel);
938 if (FHH_NewInfo.BeastLevels[creepName].type and creepType ~= "normal") then
939 FHH_NewInfo.BeastLevels[creepName].type = creepType;
940 end
941 elseif (FHH_BeastLevels[creepName]) then
942 if (creepLevel < FHH_BeastLevels[creepName].min or (FHH_BeastLevels[creepName].max and creepLevel > FHH_BeastLevels[creepName].max)) then
943 if (FHH_NewInfo == nil) then
944 FHH_NewInfo = {};
945 end
946 if (FHH_NewInfo.BeastLevels == nil) then
947 FHH_NewInfo.BeastLevels = {};
948 end
949 FHH_NewInfo.BeastLevels[creepName] = {};
950 FHH_NewInfo.BeastLevels[creepName].min = math.min(FHH_BeastLevels[creepName].min, creepLevel);
951 FHH_NewInfo.BeastLevels[creepName].max = math.max(FHH_BeastLevels[creepName].max or FHH_BeastLevels[creepName].min, creepLevel);
952 end
953 if (creepType ~= "normal" and creepType ~= FHH_BeastLevels[creepName].type) then
954 if (FHH_NewInfo == nil) then
955 FHH_NewInfo = {};
956 end
957 if (FHH_NewInfo.BeastLevels == nil) then
958 FHH_NewInfo.BeastLevels = {};
959 end
960 if (FHH_NewInfo.BeastLevels[creepName] == nil) then
961 FHH_NewInfo.BeastLevels[creepName] = {};
962 end
963 FHH_NewInfo.BeastLevels[creepName].min = math.min(FHH_BeastLevels[creepName].min, creepLevel);
964 FHH_NewInfo.BeastLevels[creepName].max = math.max(FHH_BeastLevels[creepName].max or FHH_BeastLevels[creepName].min, creepLevel);
965 FHH_NewInfo.BeastLevels[creepName].type = creepType;
966 end
967 end
968 end
969  
970 function FHH_RecordNewSpellID(spellName)
971 -- we have a new spell on our hands; we'll use its lowercase name as a key for now.
972 spellID = string.lower(spellName);
973 if (FHH_NewInfo == nil) then
974 FHH_NewInfo = {};
975 end
976 if (FHH_NewInfo.SpellNamesToIDs == nil) then
977 FHH_NewInfo.SpellNamesToIDs = {};
978 end
979 if (FHH_NewInfo.SpellIDsToNames == nil) then
980 FHH_NewInfo.SpellIDsToNames = {};
981 end
982 FHH_NewInfo.SpellNamesToIDs[spellName] = spellID;
983 FHH_NewInfo.SpellIDsToNames[spellID] = spellName;
984 return spellID;
985 end
986  
987 function FHH_RecordNewSpellIcon(spellIcon, spellName)
988 spellID = FHH_RecordNewSpellID(spellName);
989 if (FHH_NewInfo == nil) then
990 FHH_NewInfo = {};
991 end
992 if (FHH_NewInfo.SpellIcons == nil) then
993 FHH_NewInfo.SpellIcons = {};
994 end
995 FHH_NewInfo.SpellIcons[spellIcon] = spellID;
996 return spellID;
997 end
998  
999 function FHH_ProcessAliases()
1000 if (FHH_NewInfo and FHH_NewInfo.SpellIDAliases) then
1001 for oldID, newID in FHH_NewInfo.SpellIDAliases do
1002  
1003 if (FHH_NewInfo.SpellNamesToIDs) then
1004 local newNamesToIDs = {};
1005 local changed = false;
1006 for name, id in FHH_NewInfo.SpellNamesToIDs do
1007 if (id == oldID) then
1008 newNamesToIDs[name] = newID;
1009 changed = true;
1010 else
1011 newNamesToIDs[name] = id;
1012 end
1013 end
1014 if (changed) then
1015 FHH_NewInfo.SpellNamesToIDs = newNamesToIDs;
1016 end
1017 end
1018  
1019 if (FHH_NewInfo.BeastInfo) then
1020 for beast, spellList in FHH_NewInfo.BeastInfo do
1021 if (spellList[oldID]) then
1022 spellList[newID] = spellList[oldID];
1023 spellList[oldID] = nil;
1024 end
1025 end
1026 end
1027  
1028 if (FHH_NewInfo.BadBeastInfo) then
1029 for beast, spellList in FHH_NewInfo.BadBeastInfo do
1030 if (spellList[oldID]) then
1031 spellList[newID] = spellList[oldID];
1032 spellList[oldID] = nil;
1033 end
1034 end
1035 end
1036  
1037 if (FHH_AbilityInfo) then
1038 for realmPlayer, abilityTable in FHH_AbilityInfo do
1039 if (abilityTable[oldID]) then
1040 abilityTable[newID] = abilityTable[oldID];
1041 abilityTable[oldID] = nil;
1042 end
1043 end
1044 end
1045  
1046 if (FHH_NewInfo.SpellIDsToNames and FHH_NewInfo.SpellIDsToNames[oldID]) then
1047 FHH_NewInfo.SpellIDsToNames[newID] = FHH_NewInfo.SpellIDsToNames[oldID];
1048 FHH_NewInfo.SpellIDsToNames[oldID] = nil;
1049 end
1050  
1051 if (FHH_NewInfo.RequiredLevel and FHH_NewInfo.RequiredLevel[oldID]) then
1052 FHH_NewInfo.RequiredLevel[newID] = FHH_NewInfo.RequiredLevel[oldID];
1053 FHH_NewInfo.RequiredLevel[oldID] = nil;
1054 end
1055  
1056 if (FHH_NewInfo.LearnableBy and FHH_NewInfo.LearnableBy[oldID]) then
1057 FHH_NewInfo.LearnableBy[newID] = FHH_NewInfo.LearnableBy[oldID];
1058 FHH_NewInfo.LearnableBy[oldID] = nil;
1059 end
1060  
1061 if (FHH_NewInfo.SpellInfo and FHH_NewInfo.SpellInfo[oldID]) then
1062 FHH_NewInfo.SpellInfo[newID] = FHH_NewInfo.SpellInfo[oldID];
1063 FHH_NewInfo.SpellInfo[oldID] = nil;
1064 end
1065 end
1066 end
1067 end
1068