GogoWatch

GogoWatch

2.3k Downloads

Create Clean Spell List

Gogo1951 opened this issue ยท 9 comments

commented

Here's what I go through to get the clean data.

1 - Export Abilities

import requests
import csv
import io

patch_version = "2.5.2.39832"
#url = 'https://wow.tools/dbc/api/export/?name=%s&build=%s&locale=enUS'
url = "https://wow.tools/dbc/api/export/?name=%s&build=%s"

def connect(dbc, patch):
    text = requests.get(url % (dbc, patch), headers={'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:91.0) Gecko/20100101 Firefox/91.0'}).text
    reader = csv.DictReader(io.StringIO(text))
    return reader


spells = connect("spell", patch_version)
levels = connect("SpellLevels", patch_version)
classes = connect("SpellClassOptions", patch_version)
names = connect("SpellName", patch_version)

print("AbilityID, Rank, LevelAvailable, Name, Class")

levels_table = {}
class_table = {}
names_table = {}

for data in classes:
    class_table[data["SpellID"]] = data

for data in names:
    names_table[data["ID"]] = data["Name_lang"]

for data in levels:
    levels_table[data["SpellID"]] = data

for spell in spells:
    id = spell["ID"]
    if id in levels_table and id in names_table and id in class_table:
        rank = spell["NameSubtext_lang"]
        if not rank or len(rank) == 0:
            rank = "Rank 1"
        print(id, end='')
        print(", \"", end='')
        print(rank, end='')
        print("\", ", end='')
        print(levels_table[id]["BaseLevel"], end='')
        print(", \"", end='')
        print(names_table[id], end='')
        print("\", ", end='')
        print(class_table[id]["SpellClassSet"])

Output Looks Like:

Ability Export.txt

I reformatted this inside of Excel...

Abilities Export.xlsx

2 - Delete any row where the ability doesn't have a numeric "Rank".

Example:

34426 3 Greater Invisibility Passive 1

3 - Delete any row where the ability "LevelAvailable" isn't greater than 1.

These seem like GM abilities, or other odd abilities.

Example:

31751 3 Arcane Missiles 1 0

You can also delete any ability with Class = 0, or 13 (Consumables).

4 - Delete any ability that only has 1 unique value in the "Rank".

Example:

37988 3 Ancient Fire 1 20
16067 3 Arcane Blast 1 35
18091 3 Arcane Blast 1 35
20883 3 Arcane Blast 1 50
30451 3 Arcane Blast 1 64
35927 3 Arcane Blast 1 64
36032 3 Arcane Blast 1 1
38881 3 Arcane Blast 1 64

Careful: Some abilities upgrade to different names. Example. Curious if there is any sort of grouping in the DB. Doubt it. So... probably fine to just purge them and we can re-add as one-off cases manually later.

759 3 Conjure Mana Agate 1 28
3552 3 Conjure Mana Jade 1 38
10053 3 Conjure Mana Citrine 1 48
10054 3 Conjure Mana Ruby 1 58
36883 3 Conjure Mana Diamond 1 68
27101 3 Conjure Mana Emerald 1 68

Careful: Some abilities are GM-only abilities. From the example above... curious if there is any way to weed out the abilities not available to players? Maybe cross check if an ability is learned from a trainer, or learned from a book? Don't know if there is a way to do that.

36883 3 Conjure Mana Diamond 1 68

5 - Sync "LevelAvailable" by Class + Ability + Rank

Watch out for situations where abilities are available, but impacted by talents that are not available until later levels. When encountered, set the "LevelAvailable" to the lowest level for all in that rank.

Example:

6136 3 Chilled 1 1
7321 3 Chilled 1 30
12484 3 Chilled 1 1
15850 3 Chilled 1 1
16927 3 Chilled 1 63
18101 3 Chilled 1 1
31257 3 Chilled 1 70

Desired Output:

6136 3 Chilled 1 1
7321 3 Chilled 1 1
12484 3 Chilled 1 1
15850 3 Chilled 1 1
16927 3 Chilled 1 1
18101 3 Chilled 1 1
31257 3 Chilled 1 1

6 - Compute "MaxLevelBeforeReplace" for all Abilities; should be easier once the above steps are taken.

Note we want to keep the Level 70 abilities in there, and just add "70" for those.

Example:

31661 3 Dragon's Breath 1 50
35250 3 Dragon's Breath 1 70
37289 3 Dragon's Breath 1 70
33041 3 Dragon's Breath 2 56
33042 3 Dragon's Breath 3 64
33043 3 Dragon's Breath 4 70

Desired Output:

31661 3 Dragon's Breath 1 50 55
35250 3 Dragon's Breath 1 50 55
37289 3 Dragon's Breath 1 50 55
33041 3 Dragon's Breath 2 56 63
33042 3 Dragon's Breath 3 64 69
33043 3 Dragon's Breath 4 70 70

7 - Delete All Heals*

*All... not really. Delete heals from Paladins, Priests, Shamans, and Druids that do not have a CD Timer. So like spam-able heals; but we want to keep a few spells, like Tranquility, because there's no reason to cast a low-rank when it's got a long CD. Likely has to stay manual process.

8 - Complete Buff Min Levels

For things like Blessing of Might, the buff can only be given to players who are 10 levels under the "LevelAvailable" level.

Example:

19740 10 Blessing of Might 1 4
19834 10 Blessing of Might 2 12
19835 10 Blessing of Might 3 22
19836 10 Blessing of Might 4 32
19837 10 Blessing of Might 5 42
19838 10 Blessing of Might 6 52
25291 10 Blessing of Might 7 60
27140 10 Blessing of Might 8 70

Desired Output:

19740 10 Blessing of Might 1 4 11 0
19834 10 Blessing of Might 2 12 21 2
19835 10 Blessing of Might 3 22 31 12
19836 10 Blessing of Might 4 32 41 22
19837 10 Blessing of Might 5 42 51 32
19838 10 Blessing of Might 6 52 59 42
25291 10 Blessing of Might 7 60 69 50
27140 10 Blessing of Might 8 70 70 60

9 - Delete All Abilities on Known Ignore List

#2

10 - Clean Up Data into LUA format for DataTables file.

https://github.com/Gogo1951/GogoWatch/blob/main/DataTables.lua

commented

This is just a mid point, working on the trimming rules. Getting to a place where we deal with all spells of the same name at the same time.

This should address 2, 3, 3.5, and 6 so far.

from collections import defaultdict

import requests
import csv
import io

patch_version = "2.5.2.39832"
#url = 'https://wow.tools/dbc/api/export/?name=%s&build=%s&locale=enUS'
url = "https://wow.tools/dbc/api/export/?name=%s&build=%s"

def connect(dbc, patch):
    text = requests.get(url % (dbc, patch), headers={'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:91.0) Gecko/20100101 Firefox/91.0'}).text
    reader = csv.DictReader(io.StringIO(text))
    return reader

spells = connect("spell", patch_version)
levels = connect("SpellLevels", patch_version)
classes = connect("SpellClassOptions", patch_version)
names = connect("SpellName", patch_version)

print("AbilityID, Rank, LevelAvailable, Name, Class")

levels_table = {}
class_table = {}
names_table = {}
spells_table = {}

for data in classes:
    class_table[data["SpellID"]] = data

for data in names:
    names_table[data["ID"]] = data["Name_lang"]

for data in levels:
    levels_table[data["SpellID"]] = data

for data in spells:
    spells_table[data["ID"]] = data

skip_spells = [
    '27204', #QA Spell
    '30331', #Permanent Sheen of Zanza
    '30336', #Permanent Sheen of Spirit
    '34176', #QA Heal Coefficient 1
    '34177', #QA Damage Coefficient
]

no_ranks = ['Summon', 'Shapeshift', 'Passive', 'Racial', '0', 'Racial Passive', '(OLD)']
spell_name_skip_prefixes = ['Gossip', 'Brewfest', 'Cosmetic', 'Winter', 'TEST' ]

# In order to keep some context around and make spells easier to apply logic to
# this will make a dictionary with the key of the spell name and the value be a 
# list of the spells with that name
spell_groups_by_name = defaultdict(list)
for spell_id, name in names_table.items():
    # print(f'Adding spell {spell_id} {name}')
    spell_groups_by_name[name].append(spell_id)



full_spells = {}

def get_spell_details(id):
    if id in levels_table and id in names_table and id in class_table and id not in skip_spells:
        spell = spells_table[id]
        if names_table[id] == 'Fireball':
            print(f'fireball {spell}')
        rank = spell["NameSubtext_lang"]

        print(f"RANK {rank} {spell['NameSubtext_lang']} {spell}")
        if not rank or len(rank) == 0:            
            adjusted_rank = "Rank 1"
        else:
            adjusted_rank = rank

        # 2 - Delete any row where the ability doesn't have a numeric "Rank".
        if adjusted_rank in no_ranks:
            # print('Skipping - no rank -', end=':')
            # print(names_table[id], end='')
            # print("\", ", end='')
            # print(class_table[id]["SpellClassSet"]) 
            return None

        # 3 - Delete any row where the ability "LevelAvailable" isn't greater than 1.
        if int(levels_table[id]["BaseLevel"]) < 1:
            # print('Skipping - base level < 1 - ', end=':')
            # print(names_table[id], end='')
            # print("\", ", end='')
            # print(class_table[id]["SpellClassSet"]) 
           return None 
        
        try:
            #This is more readable than a regex, yes it could be a regex
            rank_string = adjusted_rank.split(' ')[1]
            if ":" in rank_string:
                rank_string = rank_string.split(':')[0]

            if rank_string in no_ranks:
                # print(f'{adjusted_rank} made it passed the first filter')
                return None 


            # 3 3.5 You can also delete any ability with Class = 0, or 13 (Consumables).            
            if class_table[id]['SpellClassSet'] in [0,13]:
                return None

            details = {
                'id': int(id),
                'rank': float(rank_string),
                'base_level': int(levels_table[id]["BaseLevel"]),
                'name': names_table[id],
                'class': class_table[id]["SpellClassSet"],
            }
            return details
            # print(f'Returning details:{details}')

        except:
            print(f'Rank was {rank}')
            print(f'Adjusted rank {adjusted_rank}')
            print(id, end='')
            print(", \"", end='')
            print(rank, end='')
            print("\", ", end='')
            print(levels_table[id]["BaseLevel"], end='')
            print(", \"", end='')
            print(names_table[id], end='')
            print("\", ", end='')
            print(class_table[id]["SpellClassSet"])
            raise

# Use this list to know if a spell family was processed or not
processed_spells = {}
for spell in spells_table.values():
    print(f'Spell {spell}')
    id = spell['ID']
    if id in processed_spells:
        print(f'Already processed {id} skipping')
        continue

    name = names_table[id]
    if any(name.startswith(x) for x in spell_name_skip_prefixes):
        continue
    # print(f'Looking for {name}')
    related_spells = spell_groups_by_name[name]
    print(f'{name}: related spells are {related_spells}')
    spell_results = []
    cut_for_rank = True
    for related_id in related_spells:
        processed_spells[related_id] = True
        spell_details = get_spell_details(related_id)
        print(f'{related_id} - {spell_details}')
        if spell_details is not None:
            # print(f'{related_id} added to results {spell_details}')
            # print(f'deets: {spell_details}')
            spell_results.append(spell_details)
            if spell_details['rank'] != 1.0:
                cut_for_rank = False

    # 4 - Delete any ability that only has 1 unique value in the "Rank".
    if cut_for_rank:
        print(f'Cutting spell {id} {name}')
        continue

    for result in spell_results:
        # 6 - Compute "MaxLevelBeforeReplace" for all Abilities; should be easier once the above steps are taken
        any_set = False 
        for other_spell in spell_results:
            if (result['rank'] + 1) == other_spell['rank'] and (result['base_level'] < other_spell['base_level']):
                result['max_level_before_replace'] = other_spell['base_level'] - 1
                any_set = True
        
        if not any_set:
            result['max_level_before_replace'] = 70

        # print(f'Spell {result}')
        full_spells[result['id']] = result
        
for k, v in full_spells.items():
        print(k, v)
commented

Making a checklist:

  • 2 - Delete any row where the ability doesn't have a numeric "Rank".

Example:

34426 3 Greater Invisibility Passive 1

  • 3 - Delete any row where the ability "LevelAvailable" isn't greater than 1.

These seem like GM abilities, or other odd abilities.

Example:

31751 3 Arcane Missiles 1 0

  • You can also delete any ability with Class = 0, or 13 (Consumables).

  • 4 - Delete any ability that only has 1 unique value in the "Rank".

Example:

37988 3 Ancient Fire 1 20
16067 3 Arcane Blast 1 35
18091 3 Arcane Blast 1 35
20883 3 Arcane Blast 1 50
30451 3 Arcane Blast 1 64
35927 3 Arcane Blast 1 64
36032 3 Arcane Blast 1 1
38881 3 Arcane Blast 1 64

Careful: Some abilities upgrade to different names. Example. Curious if there is any sort of grouping in the DB. Doubt it. So... probably fine to just purge them and we can re-add as one-off cases manually later.

759 3 Conjure Mana Agate 1 28
3552 3 Conjure Mana Jade 1 38
10053 3 Conjure Mana Citrine 1 48
10054 3 Conjure Mana Ruby 1 58
36883 3 Conjure Mana Diamond 1 68
27101 3 Conjure Mana Emerald 1 68

Careful: Some abilities are GM-only abilities. From the example above... curious if there is any way to weed out the abilities not available to players? Maybe cross check if an ability is learned from a trainer, or learned from a book? Don't know if there is a way to do that.

36883 3 Conjure Mana Diamond 1 68

  • 5 - Sync "LevelAvailable" by Class + Ability + Rank

Watch out for situations where abilities are available, but impacted by talents that are not available until later levels. When encountered, set the "LevelAvailable" to the lowest level for all in that rank.

Example:

6136 3 Chilled 1 1
7321 3 Chilled 1 30
12484 3 Chilled 1 1
15850 3 Chilled 1 1
16927 3 Chilled 1 63
18101 3 Chilled 1 1
31257 3 Chilled 1 70

Desired Output:

6136 3 Chilled 1 1
7321 3 Chilled 1 1
12484 3 Chilled 1 1
15850 3 Chilled 1 1
16927 3 Chilled 1 1
18101 3 Chilled 1 1
31257 3 Chilled 1 1

  • 6 - Compute "MaxLevelBeforeReplace" for all Abilities; should be easier once the above steps are taken.

Note we want to keep the Level 70 abilities in there, and just add "70" for those.

Example:

31661 3 Dragon's Breath 1 50
35250 3 Dragon's Breath 1 70
37289 3 Dragon's Breath 1 70
33041 3 Dragon's Breath 2 56
33042 3 Dragon's Breath 3 64
33043 3 Dragon's Breath 4 70

Desired Output:

31661 3 Dragon's Breath 1 50 55
35250 3 Dragon's Breath 1 50 55
37289 3 Dragon's Breath 1 50 55
33041 3 Dragon's Breath 2 56 63
33042 3 Dragon's Breath 3 64 69
33043 3 Dragon's Breath 4 70 70

  • 7 - Delete All Heals*

*All... not really. Delete heals from Paladins, Priests, Shamans, and Druids that do not have a CD Timer. So like spam-able heals; but we want to keep a few spells, like Tranquility, because there's no reason to cast a low-rank when it's got a long CD. Likely has to stay manual process.

  • 8 - Complete Buff Min Levels

For things like Blessing of Might, the buff can only be given to players who are 10 levels under the "LevelAvailable" level.

Example:

19740 10 Blessing of Might 1 4
19834 10 Blessing of Might 2 12
19835 10 Blessing of Might 3 22
19836 10 Blessing of Might 4 32
19837 10 Blessing of Might 5 42
19838 10 Blessing of Might 6 52
25291 10 Blessing of Might 7 60
27140 10 Blessing of Might 8 70

Desired Output:

19740 10 Blessing of Might 1 4 11 0
19834 10 Blessing of Might 2 12 21 2
19835 10 Blessing of Might 3 22 31 12
19836 10 Blessing of Might 4 32 41 22
19837 10 Blessing of Might 5 42 51 32
19838 10 Blessing of Might 6 52 59 42
25291 10 Blessing of Might 7 60 69 50
27140 10 Blessing of Might 8 70 70 60

  • 9 - Delete All Abilities on Known Ignore List

#2

  • 10 - Clean Up Data into LUA format for DataTables file.

https://github.com/Gogo1951/GogoWatch/blob/main/DataTables.lua

commented

This should have a python dict at the end with everything except 9 and 10

from collections import defaultdict

import requests
import csv
import io

patch_version = "2.5.2.39832"
# url = 'https://wow.tools/dbc/api/export/?name=%s&build=%s&locale=enUS'
url = "https://wow.tools/dbc/api/export/?name=%s&build=%s"


def connect(dbc, patch):
    text = requests.get(
        url % (dbc, patch),
        headers={
            "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:91.0) Gecko/20100101 Firefox/91.0"
        },
    ).text
    reader = csv.DictReader(io.StringIO(text))
    return reader


spells = connect("spell", patch_version)
levels = connect("SpellLevels", patch_version)
classes = connect("SpellClassOptions", patch_version)
names = connect("SpellName", patch_version)
cooldowns = connect("SpellCooldowns", patch_version)
spelleffects = connect("SpellEffect", patch_version)

levels_table = {}
class_table = {}
names_table = {}
spells_table = {}
spelleffects_table = {}
cooldowns_table = {}

for data in classes:
    class_table[data["SpellID"]] = data

for data in cooldowns:
    cooldowns_table[data["SpellID"]] = data

for data in names:
    names_table[data["ID"]] = data["Name_lang"]

for data in levels:
    levels_table[data["SpellID"]] = data

for data in spells:
    spells_table[data["ID"]] = data

for data in spelleffects:
    spelleffects_table[data["SpellID"]] = data

skip_spells = [
    "27204",  # QA Spell
    "30331",  # Permanent Sheen of Zanza
    "30336",  # Permanent Sheen of Spirit
    "34176",  # QA Heal Coefficient 1
    "34177",  # QA Damage Coefficient
]

no_ranks = ["Summon", "Shapeshift", "Passive", "Racial", "0", "Racial Passive", "(OLD)"]
spell_name_skip_prefixes = ["Gossip", "Brewfest", "Cosmetic", "Winter", "TEST"]
allowed_rank_one_spells = {
    116: "Frostbolt",
    1949: "Hellfire",
    1008: "Amplify magic",
    604: "Dampen Magic",
    1120: "Drain Soul",
}

allowed_downranked_spells = {2120: "Flamestrike"}

# TODO these are single instances... may have to expand
any_rank_allowed = {
    1454: "Life Tap",
    18220: "Dark Pact",
    755: "Health Funnel",
    118: "Polymorph",
    587: "Conjured Food",
    5504: "Conjured Water",
    42955: "Conjured Refreshment",
}

# In order to keep some context around and make spells easier to apply logic to
# this will make a dictionary with the key of the spell name and the value be a
# list of the spells with that name
spell_groups_by_name = defaultdict(list)
for spell_id, name in names_table.items():
    # print(f'Adding spell {spell_id} {name}')
    spell_groups_by_name[name].append(spell_id)


full_spells = {}


def get_spell_details(id):
    if (
        id in levels_table
        and id in names_table
        and id in class_table
        and id not in skip_spells
    ):
        spell = spells_table[id]
        # if (names_table[id] == 'Tranquility'):
        #     print(id)
        #     print(spell)
        #     print(levels_table[id])
        #     print(class_table[id])
        #     raise
        rank = spell["NameSubtext_lang"]

        # print(f"RANK {rank} {spell['NameSubtext_lang']} {spell}")
        if not rank or len(rank) == 0:
            adjusted_rank = "Rank 1"
        else:
            adjusted_rank = rank

        # 2 - Delete any row where the ability doesn't have a numeric "Rank".
        if adjusted_rank in no_ranks:
            # print('Skipping - no rank -', end=':')
            # print(names_table[id], end='')
            # print("\", ", end='')
            # print(class_table[id]["SpellClassSet"])
            return None

        # 3 - Delete any row where the ability "LevelAvailable" isn't greater than 1.
        if int(levels_table[id]["BaseLevel"]) < 1:
            # print('Skipping - base level < 1 - ', end=':')
            # print(names_table[id], end='')
            # print("\", ", end='')
            # print(class_table[id]["SpellClassSet"])
            return None

        try:
            # This is more readable than a regex, yes it could be a regex
            rank_string = adjusted_rank.split(" ")[1]
            if ":" in rank_string:
                rank_string = rank_string.split(":")[0]

            if rank_string in no_ranks:
                # print(f'{adjusted_rank} made it passed the first filter')
                return None

            # 3 3.5 You can also delete any ability with Class = 0, or 13 (Consumables).
            if class_table[id]["SpellClassSet"] in [0, 13]:
                return None

            cooldown = 0

            if id in cooldowns_table and (
                cooldowns_table[id]["RecoveryTime"] != "0"
                or cooldowns_table[id]["CategoryRecoveryTime"] != "0"
            ):
                if cooldowns_table[id]["RecoveryTime"] != "0":
                    cooldown = int(cooldowns_table[id]["RecoveryTime"])
                else:
                    cooldown = int(cooldowns_table[id]["CategoryRecoveryTime"])

            spelleffect = spelleffects_table[id]
            # https://wow.tools/dbc/?dbc=spelleffectnames&build=2.0.0.5666#page=1&search=heal
            healing_effects = ["10", "67"]
            # https://wow.tools/dbc/?dbc=spellauranames&build=2.0.0.5666#page=1&search=heal
            healing_auras = [
                "8",
                "34",
                "62",
                "84",
                "88",
                "115",
                "118",
                "133",
                "135",
                "136",
            ]

            is_heal = (
                spelleffect["Effect"] in healing_effects
                or spelleffect["EffectAura"] in healing_auras
            )

            name = names_table[id]
            # 8 - Complete Buff Min Levels
            min_target_level = 1
            if (
                "Blessing of" in name
                or "Power Word" in name
                or "Prayer of Spirit" in name
                or "Fortitude" in name
            ):
                if levels_table[id]["BaseLevel"] != "0":
                    min_target_level = int(levels_table[id]["BaseLevel"]) - 10
                print(spell)
                print(levels_table[id])

            details = {
                "id": int(id),
                "rank": float(rank_string),
                "base_level": int(levels_table[id]["BaseLevel"]),
                "name": names_table[id],
                "class": class_table[id]["SpellClassSet"],
                "heal": is_heal,
                "cooldown": cooldown,
                "min_target_level": min_target_level,
            }
            return details
            # print(f'Returning details:{details}')

        except:
            print(f"Rank was {rank}")
            print(f"Adjusted rank {adjusted_rank}")
            print(id, end="")
            print(', "', end="")
            print(rank, end="")
            print('", ', end="")
            print(levels_table[id]["BaseLevel"], end="")
            print(', "', end="")
            print(names_table[id], end="")
            print('", ', end="")
            print(class_table[id]["SpellClassSet"])
            raise


# Use this list to know if a spell family was processed or not
processed_spells = {}
for spell in spells_table.values():
    # print(f'Spell {spell}')
    id = spell["ID"]
    if id == 740:
        print(spell)
        raise
    if id in processed_spells:
        # print(f'Already processed {id} skipping')
        continue

    name = names_table[id]
    if any(name.startswith(x) for x in spell_name_skip_prefixes):
        continue
    # print(f'Looking for {name}')
    related_spells = spell_groups_by_name[name]
    # print(f'{name}: related spells are {related_spells}')
    spell_results = []
    cut_for_rank = True
    for related_id in related_spells:
        processed_spells[related_id] = True
        spell_details = get_spell_details(related_id)
        # print(f'{related_id} - {spell_details}')
        if spell_details is not None:
            # print(f'{related_id} added to results {spell_details}')
            # print(f'deets: {spell_details}')
            spell_results.append(spell_details)
            if spell_details["rank"] != 1.0:
                cut_for_rank = False

    # 4 - Delete any ability that only has 1 unique value in the "Rank".
    if cut_for_rank:
        # print(f'Cutting spell {id} {name}')
        continue

    for result in spell_results:
        # 6 - Compute "MaxLevelBeforeReplace" for all Abilities; should be easier once the above steps are taken
        any_set = False
        for other_spell in spell_results:
            # Fix the max level before replace
            if (result["rank"] + 1) == other_spell["rank"] and (
                result["base_level"] < other_spell["base_level"]
            ):
                result["max_level_before_replace"] = other_spell["base_level"] - 1
                any_set = True

            # Equalize the base level of a spell to its lowest level.
            if (
                result["rank"] == other_spell["rank"]
                and result["base_level"] < other_spell["base_level"]
            ):
                other_spell["base_level"] = result["base_level"]

            if result["heal"] != other_spell["heal"] and other_spell["heal"]:
                result["heal"] = True

            # If a spell has a cooldown, apply that cooldown to everything in the group
            if result["cooldown"] < other_spell["cooldown"]:
                result["cooldown"] = other_spell["cooldown"]

        if not any_set:
            result["max_level_before_replace"] = 70

        # print(f'Spell {result}')
        if result["heal"] and result["cooldown"] < 1:
            # print(f'skipping healing spell {result}')
            continue
        full_spells[result["id"]] = result

# def print_lua(all_spells: dict):
#     for i in range(10):
#         for k, v in all_spells.items():
#             if v['class'] != i:
#                 continue

for k, v in full_spells.items():
    if "Blessing of" in v["name"]:
        key = str(k)
        if key not in names_table:
            print(f"wtf {k} is not in names table")
        else:
            print(names_table[key])

        if key not in levels_table:
            print(f"wtf {k} is not in levels table")
        else:
            print(levels_table[key])

        if key not in class_table:
            print(f"wtf {k} is not in classes table")
        else:
            print(class_table[key])

        if key not in spells_table:
            print(f"wtf {k} not in the spells table")
        else:
            print(spells_table[key])
        print(k, v)

print("CSV BEGIN")
print("AbilityID, Rank, LevelAvailable, Name, Class")
output_list = []
for k, v in full_spells.items():
    output_list.append({'AbilityID': v['id'], 'Rank': v['rank'], 'LevelAvailable': v['base_level'], 'Name': v['name'], 'Class': v['class'], 'MaxLevelBeforeReplace':v['max_level_before_replace'], 'MinTargetLevel': v['min_target_level']})

with open('spell-ranks.csv', 'w', newline='') as csvfile:
    fieldnames = ['AbilityID', 'Rank', 'LevelAvailable', 'Name', 'Class', 'MaxLevelBeforeReplace', 'MinTargetLevel']
    writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerows(output_list)
commented

Need to remove Consecrate Rank 1. Just notes.

commented

Really solid... few healing spells had to be removed, like Holy Shock, and Rejuvenation I think.

Needs to have Pally Res and Priest Res added back in.

Had an issue where there are some books that aren't in the game yet... had to filter a few of those out.

On the whole really solid!

commented

There are a bunch that had like "test..." abilities that may be gumming things up.

commented

Has a few abilities, like frost grenade, that need to be pruned.