Runescape Stat Generator

Hey fellow RuneScape enthusiasts,

I recently stumbled upon a Reddit thread where user PoftheM was on the hunt for a tool that could generate a comprehensive overview of their RuneScape skills, mirroring the in-game skills tab. The challenge was to find a solution that allowed for manual inputs of skill levels and quest points, ensuring a retroactive and consistent representation of their account progression.

Well, guess what? I’ve got exciting news for you! I’ve developed a tool that aligns perfectly with PoftheM’s request, and I’m thrilled to introduce the RuneStat Generator.

Here’s a quick breakdown of what the tool offers:

1. Authentic In-Game Look: The RuneStat Generator provides a user-friendly interface that mirrors the in-game skills tab, capturing the essence of your RuneScape journey.

2. Manual Skill Inputs: Say goodbye to automated username-based data fetching. With RuneStat, you have complete control over your skill levels and quest points. Input them manually for a personalized touch.

3. Customizable Layout: Enjoy the familiar layout and icons of the in-game skills view. The tool strives to replicate the RuneScape experience down to the smallest detail.

4. Retroactive Overview: RuneStat lets you create a retroactive overview of your account progression, allowing you to track your RuneScape journey accurately.

To give you a sneak peek, is an example card created using RuneStat:

The output runescape skills table image is saved in the folder where the runescape_stat_generator.exe was ran from.

And you can start crafting your own by downloading the file for free here. The file was generated using the pyinstaller module using the spec config file. Don’t want to download the file? No worries, if you want the source code you can find it here: https://github.com/slyautomation/osrs_stat_generator

Feel free to test it out and let me know what you think. Happy RuneScaping!

# Embedded file name: main.py
from PIL import Image
def resourcePath(relativePath):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        basePath = sys._MEIPASS
    except Exception:
        basePath = os.path.abspath(".")

    return os.path.join(basePath, relativePath)

def create_digit_total(num):
    from PIL import Image
    length = len(str(num))
    num = str(num)
    #print(num)
    #print(num[0])
    #print(length)
    x = 0
    image_name_output = resourcePath('blank_image.png')
    mode = 'RGBA'
    w = length
    size = (25 * w, 60)
    color = (0, 0, 0, 0)
    im = Image.new(mode, size, color)
    im.save(image_name_output, 'PNG')
    im.close()
    background = Image.open(image_name_output)
    while x < length:
        filename = resourcePath(str(num[x]) + '.png')
        frontImage = Image.open(filename)
        background.paste(frontImage, (0 + x * 25, 0), frontImage.convert('RGBA'))
        x += 1
    #print("resource path:", os.path.abspath("."))
    background.save((resourcePath('images\\' + str(num) + 'total.png')), format='png')


def create_digit(num, skill):
    from PIL import Image
    length = len(str(num))
    if length > 1:
        image_name_output = resourcePath('blank_image.png')
        mode = 'RGBA'
        size = (50, 60)
        color = (0, 0, 0, 0)
        im = Image.new(mode, size, color)
        im.save(image_name_output, 'PNG')
        im.close()
        filename = resourcePath(str(num)[0] + '.PNG')

        filename1 = resourcePath(str(num)[1] + '.PNG')

        frontImage = Image.open(filename)
        secImage = Image.open(filename1)
        background = Image.open(image_name_output)
        background.paste(frontImage, (0, 0), frontImage.convert('RGBA'))
        background.paste(secImage, (24, 0), secImage.convert('RGBA'))
        background.save(resourcePath(('images/' + str(num) + skill + '.png')), format='png')
    else:
        filename = resourcePath(str(num) + '.png')
        frontImage = Image.open(filename)
        frontImage.save(resourcePath(('images\\' + str(num) + skill + '.png')), format='png')


def generate_stat_card_ttf(name):
    from PIL import Image
    from PIL import ImageFont
    from PIL import ImageDraw
    filename = resourcePath('Card.png')

    img = Image.open(filename)

    draw = ImageDraw.Draw(img)
    draw.fontmode = '1'
    font = ImageFont.FreeTypeFont('RuneScape-Chat-Bold-07.ttf', 13)
    strength = draw.text((40, 10), '73', (255, 255, 0), font=font)
    img.save(resourcePath('sample-out.png'))


def create_copy():
    filename1 = resourcePath('Card.png')
    copy = Image.open(filename1)
    copy.save(resourcePath('New.png'), format='png')


def generate_stat_card(num, skill):
    from PIL import Image
    create_digit(num, skill)
    filename = resourcePath('images\\' + str(num) + skill + '.png')
    filename1 = resourcePath('New.png')
    if num < 10:
        size = (10, 10)
    else:
        size = (15, 15)
    frontImage = Image.open(filename)
    background = Image.open(filename1)
    frontImage.thumbnail(size, Image.NORMAL)
    frontImage = frontImage.convert('RGBA')
    background = background.convert('RGBA')
    dict = {'attack':(42, 13),
     'strength':(42, 45),
     'defence':(42, 77),
     'ranged':(42, 109),
     'prayer':(42, 141),
     'magic':(42, 173),
     'runecrafting':(42, 205),
     'construction':(42, 237),
     'health':(105, 13),
     'agility':(105, 45),
     'herblore':(105, 77),
     'thieving':(105, 109),
     'crafting':(105, 141),
     'fletching':(105, 173),
     'slayer':(105, 205),
     'hunter':(105, 237),
     'mining':(168, 13),
     'smithing':(168, 45),
     'fishing':(168, 77),
     'cooking':(168, 109),
     'firemaking':(168, 141),
     'woodcutting':(168, 173),
     'farming':(168, 205)}
    dict_2 = {'attack':(59, 26),
     'strength':(59, 58),
     'defence':(59, 90),
     'ranged':(59, 122),
     'prayer':(59, 154),
     'magic':(59, 186),
     'runecrafting':(59, 218),
     'construction':(59, 250),
     'health':(122, 26),
     'agility':(122, 58),
     'herblore':(122, 90),
     'thieving':(122, 122),
     'crafting':(122, 154),
     'fletching':(122, 186),
     'slayer':(122, 218),
     'hunter':(122, 250),
     'mining':(185, 26),
     'smithing':(185, 58),
     'fishing':(185, 90),
     'cooking':(185, 122),
     'firemaking':(185, 154),
     'woodcutting':(185, 186),
     'farming':(185, 218)}
    background.paste(frontImage, dict[skill], frontImage.convert('RGBA'))
    if num < 10:
        background.paste(frontImage, dict_2[skill], frontImage.convert('RGBA'))
    else:
        background.paste(frontImage, (dict_2[skill][0] - 5, dict_2[skill][1]), frontImage.convert('RGBA'))
    background.save(resourcePath('New.png'), format='png')


def generate_total(total):
    from PIL import Image
    create_digit_total(total)
    filename = resourcePath('images\\' + str(total) + 'total.png')
    filename1 = resourcePath('New.png')
    if total > 999:
        size = (25, 25)
    else:
        if total > 99:
            size = (19, 19)
        else:
            size = (15, 15)
    frontImage = Image.open(filename)
    background = Image.open(filename1)
    frontImage.thumbnail(size, Image.NORMAL)
    frontImage = frontImage.convert('RGBA')
    background = background.convert('RGBA')
    position = (155, 250)
    if total > 999:
        background.paste(frontImage, position, frontImage.convert('RGBA'))
    else:
        if total > 99:
            background.paste(frontImage, (position[0] + 3, position[1]), frontImage.convert('RGBA'))
        else:
            background.paste(frontImage, (position[0] + 5, position[1]), frontImage.convert('RGBA'))
    background.save(resourcePath('New.png'), format='png')

import os

def ensure_dir():
    directory = os.path.dirname(resourcePath('images'))
    #print(directory)
    if not os.path.exists(resourcePath('images')):
        os.makedirs(resourcePath('images'))

ensure_dir()
skill_list = [
 'attack',
 'strength',
 'defence',
 'ranged',
 'prayer',
 'magic',
 'runecrafting',
 'construction',
 'health',
 'agility',
 'herblore',
 'thieving',
 'crafting',
 'fletching',
 'slayer',
 'hunter',
 'mining',
 'smithing',
 'fishing',
 'cooking',
 'firemaking',
 'woodcutting',
 'farming']

import tkinter
from tkinter import *
from PIL import Image, ImageTk
test = []
root = Tk()

root.title('OSRS Stat Generator')
root.geometry('670x600')
root.configure(background='#40362C')
Font_tuple = ('Unispace', 15)
filename = resourcePath('osrs_title_2.png')
image1 = Image.open(filename)
image1 = image1.convert('RGBA')
h = (500, 500)
image1.thumbnail(h, Image.NORMAL)
test1 = ImageTk.PhotoImage(image1)
label1 = tkinter.Label(image=test1, background='#40362C', anchor=CENTER, justify=CENTER)
label1.image = test1
label1.grid(column=0, columnspan=5, sticky='e')
x = 1
while x < 9:
    lbl = Label(root, text=(skill_list[(x - 1)]), background='#40362C', fg='yellow', padx=20)
    lbl.configure(font=Font_tuple)
    lbl.grid(column=0, row=x)
    x += 1

x = 9
while x < 17:
    lbl = Label(root, text=(skill_list[(x - 1)]), background='#40362C', fg='yellow')
    lbl.configure(font=Font_tuple)
    lbl.grid(column=2, row=(x - 8))
    x += 1

x = 17
while x < 24:
    lbl = Label(root, text=(skill_list[(x - 1)]), background='#40362C', fg='yellow')
    lbl.configure(font=Font_tuple)
    lbl.grid(column=4, row=(x - 16))
    x += 1

x = 1
while x < 9:
    txt = Entry(root, width=5, background='#40362C', fg='yellow')
    txt.insert(-1, 1)
    txt.configure(font=Font_tuple)
    test.append(txt)
    txt.grid(column=1, row=x)
    x += 1

x = 9
while x < 17:
    txt = Entry(root, width=5, background='#40362C', fg='yellow')
    txt.configure(font=Font_tuple)
    txt.insert(-1, 1)
    test.append(txt)
    txt.grid(column=3, row=(x - 8))
    x += 1

x = 17
while x < 24:
    txt = Entry(root, width=5, background='#40362C', fg='yellow')
    txt.configure(font=Font_tuple)
    txt.insert(-1, 1)
    test.append(txt)
    txt.grid(column=5, row=(x - 16))
    x += 1

total = 0
t = 0
while t < len(test):
    total += int(test[t].get())
    t += 1

lbl = Label(root, text=('Total Level: ' + str(total)), background='#40362C', fg='yellow', anchor=CENTER)
lbl.configure(font=Font_tuple)
lbl.grid(column=4, row=8, columnspan=2)



def clicked():
    x = 0
    create_copy()
    total = 0
    t = 0
    while t < len(test):
        total += int(test[t].get())
        t += 1

    lbl = Label(root, text=('Total Level: ' + str(total)), background='#40362C', fg='yellow', anchor=CENTER)
    lbl.configure(font=Font_tuple)
    lbl.grid(column=4, row=8, columnspan=2)
    while x < 23:
        generate_stat_card(int(test[x].get()), skill_list[x])
        x += 1

    generate_total(int(total))
    result = tkinter.Toplevel()
    result.title('OSRS Stat Result')
    result.configure(background='#40362C')
    canvas = Canvas(result, width=300, height=300)
    canvas.pack()
    filename = resourcePath('New.png')

    img = PhotoImage(file=filename)
    canvas.create_image(20, 20, anchor=NW, image=img)
    import os, glob
    basePath = os.path.abspath(".")
    files = glob.glob(basePath + '/images/*')
    for f in files:
        os.remove(f)
    frontImage = Image.open(filename)
    #print(basePath)
    frontImage.save(basePath + '\\Result.png', format='png')
    #print('skills stats generated!!!')
    result.mainloop()


btn = Button(root, text='Generate Stats', fg='yellow',
  command=clicked,
  background='#40362C',
  pady=0)
btn.grid(column=2, row=10, columnspan=2, pady=20)
btn.configure(font=Font_tuple)
root.mainloop()

How does the stat generator code Work?

Here’s a breakdown of the main functionalities:

  1. Image Processing Functions:
    • create_digit_total(num): Creates a total level image by combining individual digit images for each digit in the given number.
    • create_digit(num, skill): Creates an image for a specific skill level by combining two digit images (if necessary).
    • generate_stat_card_ttf(name): Generates a stat card using a TrueType font and saves the result as ‘sample-out.png’.
    • create_copy(): Creates a copy of a card image (‘Card.png’) and saves it as ‘New.png’.
    • generate_stat_card(num, skill): Generates a stat card for a specific skill level and overlays it onto the copied card image.
    • generate_total(total): Generates a total level stat card and overlays it onto the copied card image.
  2. Utility Functions:
    • ensure_dir(): Ensures the existence of the ‘images’ directory.
    • resourcePath(relativePath): Returns the absolute path to a resource, considering both development and PyInstaller scenarios.
  3. Tkinter GUI:
    • The GUI consists of an input section for entering skill levels and a “Generate Stats” button.
    • Entries are organized in three columns corresponding to the skill categories: Attack, Strength, Defense, …, Farming.
    • The total level is displayed at the bottom of the GUI.
    • Clicking the “Generate Stats” button triggers the clicked() function, which calculates the total level, generates individual skill cards, and displays the result in a new Tkinter window.
  4. Cleanup and Display:
    • After generating the result, the script cleans up temporary skill images and displays the resulting stat card in a new Tkinter window (‘Result.png’).

148 thoughts on “Runescape Stat Generator

  1. Very nice post. I just stumbled upon your blog and wanted to say thatI have truly enjoyed browsing your blog posts. After all I’ll besubscribing to your rss feed and I hope you write again very soon!

  2. Thank you for every other excellent article. The place else may anyone get that kind of information in such a perfect manner of writing? I’ve a presentation next week, and I am on the search for such info.

  3. I do agree with all the ideas you’ve offered on your post. They are very convincing and will certainly work. Still, the posts are too quick for novices. May you please extend them a little from subsequent time? Thanks for the post.

  4. I’m no longer certain where you’re getting your info, but good topic.I needs to spend a while learning much more or understanding more.Thanks for great info I was searching forthis info for my mission.

  5. Hello! I just wanted to ask if you ever have any trouble with hackers?My last blog (wordpress) was hacked and I ended up losing several weeks ofhard work due to no back up. Do you have any solutions to protect against hackers?

  6. Hi, I do believe this is an excellent blog. I stumbledupon it 😉 I’m going to return once again since I saved as a favorite it. Money and freedom is the best way to change, may you be rich and continue to help others.

  7. In a particular poker game, a player’s poker money account is comprised of genuine money and real chips. Poker rules might seem quite perplexing for novices. This 1 is too essential as it does the negotiating for you.

  8. Heya i am for the primary time here. I found this board and I in finding It really useful & it helped me out much. I hope to offer something again and aid others such as you helped me.

  9. Aw, this was a very good post. Finding the time and actual effort to generate a very good articleÖ but what can I sayÖ I procrastinate a whole lot and never seem to get nearly anything done.

  10. Excellent post. I was checking constantly this blog and I’m impressed! Very useful info specifically the last part 🙂 I care for such information much. I was looking for this certain information for a very long time. Thank you and good luck.

  11. Профессиональный сервисный центр по ремонту бытовой техники с выездом на дом.
    Мы предлагаем:сервисные центры по ремонту техники в мск
    Наши мастера оперативно устранят неисправности вашего устройства в сервисе или с выездом на дом!

  12. It’s in point of fact a great and helpful piece of information. I’m glad that you just shared this useful information with us. Please keep us up to date like this. Thank you for sharing.

  13. A fascinating discussion is definitely worth comment. There’s no doubt that that you need to write more about this subject matter, it may not be a taboo subject but generally folks don’t talk about these subjects. To the next! Many thanks!!

  14. 🎊เว็บดี ~เว็บตรง ~มั่นคง ปลอดภัย😇เล่นได้ถอนได้เลย❣️ ไม่ต้องทำเทริน🎰🃏สล๊อต บาคาร่า ไฮโล ครบ♻️เว็บมาตรฐานสากล

  15. Gooԁ day! Would you mind іf I share your blog with my facebook group?There’s a ⅼot of folks that I think wouild realⅼy appгeciate y᧐ur content.Please let me know. Thanksmy blog post … lihat disini

  16. Hey there! I’ve been reading your blog for a while now and finally got the courage to go ahead and give you a shout out from Porter Texas! Just wanted to mention keep up the excellent work!

  17. Many thanks to the auspicious writeup. It in reality was a enjoyment account it. Look complicated to far more introduced agreeable from you! Incidentally, how could we be in contact?

  18. An interesting discussion is definitely worth comment. There’s no doubt that that you ought to publish more about this subject, it may not be a taboo matter but usually people don’t speak about these subjects. To the next! Kind regards!!

  19. Thanks , I have recently been searching for info about this topic for ages and yours is the greatest I have came upon till now. But, what in regards to the bottom line? Are you positive in regards to the source?

  20. It’s really a nice and useful piece of information. I am satisfied that you justshared this helpful information with us. Please stay us informed like this.Thanks for sharing.My blog; youtube converter

  21. After I initially commented I clicked the -Notify me when new comments are added- checkbox and now each time a comment is added I get four emails with the same comment. Is there any method you possibly can take away me from that service? Thanks!

  22. Aw, this was an extremely nice post. Finding the time and actual effort to produce a really good articleÖ but what can I sayÖ I procrastinate a whole lot and don’t seem to get nearly anything done.

  23. What’s Happening i’m new to this, I stumbled upon this I’ve discovered It absolutely useful and it hashelped me out loads. I hope to give a contribution & aid different users like its helped me.Great job.

  24. We are looking for some people that are interested in from working their home on a full-time basis. If you want to earn $500 a day, and you don’t mind writing some short opinions up, this might be perfect opportunity for you!

  25. We are looking for some people that are interested in from working their home on a full-time basis. If you want to earn $200 a day, and you don’t mind creating some short opinions up, this might be perfect opportunity for you!

Leave a Reply

Your email address will not be published. Required fields are marked *