As of Mar 2022, this project is up-to-date with Gen 8 (Pokémon Sword/Shield and its DLC releases, and Pokémon Legends: Arceus). All old images from Gen 7 (Pokémon Ultra Sun/Ultra Moon) are still available for legacy support.
' % (item[0], item[1]) for item in old_images])
}, 'Index', version, commit, '.')
return content
def wrap_docs_page(table_content, gen, gen_dir, curr_page, json_file, title, is_items_page, is_misc_page, version, commit, sprites_counter, new_sprites_only, items_list):
'''Wraps a documentation page in a table node and adds styling'''
gen_url = f'{REPO_BASE_URL}/{gen_dir}'
json_url = f'{REPO_BASE_URL}/data/{json_file}'
gen_link = f'{gen_dir}'
json_link = f'data/{json_file}'
if title is None and gen:
title = 'Gen ' + str(gen) + (f' (new sprites only)' if new_sprites_only else '')
main_info = '''
This table lists all inventory item sprites. These items are from the last several games and is up-to-date as of Pokémon Sword/Shield. The sprites are from Gen 3 through 8.
All sprites are 32×32 in size. There are two sets of sprites: one with a Sword/Shield style white outline around the sprites, and one without (as all previous games). Both sets contain the same number of sprites, and both are listed below.
''' if is_items_page else '''
This table lists all miscellaneous sprites—all that aren't Pokémon box sprites or inventory items.
The following groups of sprites are included:
%(items_list)s
The data for this list can be found in %(json_link)s.
This table lists all Pokémon box sprites for Gen %(gen)s%(subtype)s, which can be found in the %(gen_link)s directory. The list is up-to-date as of Pokémon Sword/Shield, and some of the sprites are from an earlier generation. All shiny sprites were custom-made and are not found in-game.
All box sprites are 68×56 as of Gen 8; the old Gen 7 sprites have been updated to the new size and contrast. (The original 40×30 sprites from Gen 7 are still available in the legacy sprites directory.)
The data for this list (Pokémon names, forms, etc.) is from the gen-%(gen)s key of the items from %(json_link)s.
%(new_sprites_only)s
''' % {
'gen': gen,
'gen_link': gen_link,
'json_link': json_link,
'new_sprites_only': 'Only items that contain "is_prev_gen_icon": false are shown.' if new_sprites_only else '',
'subtype': ' (new sprites only)' if '-new' in curr_page else '',
}
return wrap_in_html('''
%(title_sprite)sPokéSprite
Database project of box and inventory sprites from the Pokémon core series games
' % (docs_url(item[0]), 'curr' if item[1] == curr_page else '', item[2]) for item in menu]
return ''.join(menu_links)
def get_title_venusaur():
return get_img_node(get_pkm_url(DEX_SPRITE_DIR[8], 'venusaur', True, False, False), None, 'Shiny Venusaur', 'p')
def wrap_in_html(content, title, version, commit, res_dir = '.'):
return '''
PokéSprite%(title)s
%(content)s
'''.strip() % {
'res_dir': res_dir,
'content': content,
'title': ' - ' + title if title else '',
'version': version,
'commit': commit
}
def run_cmd(cmd):
return subprocess.check_output(cmd, cwd=BASE_DIR).strip().decode('utf-8')
def write_file(filename, content):
with open(filename, 'wt', encoding="utf8") as file:
print(content, file=file)
def docs_url(slug):
return f'{DOCS_BASE_URL}/{slug}.html'
def read_repo_state():
'''Returns information about the current state of the repository'''
version = '[unknown]'
try:
package = read_json_file(REPO_PACKAGE)
version = package['version']
except:
pass
# In case this fails (e.g. Git is not installed, or this isn't a repo).
commit = '[unknown]'
try:
count = run_cmd(['git', 'rev-list', 'HEAD', '--count'])
branch = run_cmd(['git', 'rev-parse', '--abbrev-ref', 'HEAD'])
hash = run_cmd(['git', 'rev-parse', '--short', '--verify', 'HEAD'])
commit = f'{branch}-{count} [{hash}]'
return (version, commit)
except subprocess.CalledProcessError:
return (version, commit)
except OSError:
return (version, commit)
def read_json_file(file):
'''Reads a single JSON and returns a dict'''
with open(file, encoding="utf8") as json_file:
return json.load(json_file)
def read_data():
'''Retrieves Pokémon and items JSON data'''
return {
'dex': read_json_file(DEX_JSON),
'itm': read_json_file(ITM_JSON),
'misc': read_json_file(MSC_JSON),
'itm_unl': read_json_file(ITM_UNL_JSON),
'meta': read_json_file(META_JSON),
'etc': read_json_file(ETC_JSON)
}
def get_pkm_form(form_name, form_alias, is_unofficial_icon, is_female, has_unofficial_female_icon):
title = []
daggers = []
if form_alias is not None:
alias = f'"{form_alias}"' if form_alias else 'default form'
title.append(f'Alias of {alias}')
daggers.append('†')
if is_unofficial_icon:
title.append('Unofficial icon (see below)')
daggers.append('‡')
if is_female and has_unofficial_female_icon:
title.append('Unofficial female icon (see below)')
daggers.append('‡')
if len(title):
title = '; '.join(title)
daggers = ''.join(daggers)
return f'{form_name}{daggers}'
return form_name
def get_pkm_url(base, slug, is_shiny, is_female, is_right):
return ''.join([
base,
'/regular' if not is_shiny else '/shiny',
'/female' if is_female else '',
'/right' if is_right else '',
'/',
slug,
'.png'
])
def get_etc_url(base, slug):
return ''.join([
base,
'/',
slug,
'.png'
])
def get_itm_url(base, ns, group, file):
return ''.join([
base,
'/',
ns,
'/',
group,
'/',
file,
'.png'
])
def get_misc_url(base, file):
return ''.join([
base,
'/misc/',
file
])
def get_pkm_gen(is_prev_gen_icon, docs_gen):
prev_gen = str(int(docs_gen) - 1)
if is_prev_gen_icon:
return { 'node': prev_gen, 'expl': f'Icon is from generation {prev_gen}', 'cls': '' }
return ''
def get_pkm_gender(is_female, has_female):
if not has_female:
return ''
node = 'F' if is_female else 'M'
expl = 'Female sprite' if is_female else 'Male sprite'
return { 'node': node, 'expl': expl, 'cls': f' gender-{node.lower()}' }
def get_pkm_unofficial(is_unofficial_icon):
return '✓' if is_unofficial_icon else ''
def get_td_node(td):
# If the column is a list, we'll render several columns.
if isinstance(td, list):
cols = []
non_empty = list(filter(len, td))
first_col_span = len(td) - (len(non_empty) - 1)
cols.append(f'
{td[0]}
')
# The rest of the columns are either plain strings, or a dict containing { node, expl }.
for col in non_empty[1:]:
if isinstance(col, dict):
node = col['node']
expl = col['expl']
cls = col['cls']
cols.append(f'
{node}
')
else:
cols.append(f'
{col}
')
return ''.join(cols)
# Otherwise it's a string, and we'll check if it contains an image.
attr = ' class="image"' if str(td)[:4] == '{td}'
def get_img_node(url, name, form_name, type, retina_type = None):
form_name = html.escape(form_name)
cls = [type, 'retina retina-' + retina_type if retina_type else '']
cls = ' '.join(cls).strip()
return f''
def get_gen_str(str):
return str.replace("-", " ").title()
def reset_counter():
'''Resets the global sprite counter'''
global _n_counter
_n_counter = 0
def get_counter():
'''Increments and returns the global sprite counter'''
global _n_counter
_n_counter += 1
return _n_counter
def determine_form(slug, form_name, form_data):
'''Return two Pokémon form strings: one for display, and one referencing a file'''
# The regular form is indicated by a '$', and can be an alias of another one.
form_value = '' if form_name == '$' else form_name
form_alias = form_data.get('is_alias_of', None)
form_alias = '' if form_alias == '$' else form_alias
form_file = form_alias if form_alias is not None else form_value
form_display = form_value
# Save two slugs: the first one is literally just the Pokémon name plus its form,
# and the other uses the 'is_alias_of' slug and is used for selecting the right file.
form_slug_display = '-'.join(filter(len, [slug, form_display]))
form_slug_file = '-'.join(filter(len, [slug, form_file]))
return (form_slug_file, form_slug_display, form_alias)
def append_pkm(cols, base, slug_display, slug_file, form_name, form_alias, has_female, has_unofficial_female_icon, is_female, is_right, is_unofficial_icon, is_prev_gen_icon, docs_gen):
'''Adds a single Pokémon row'''
cols.append([
get_counter(),
get_img_node(get_pkm_url(base, slug_file, False, is_female, is_right), None, form_name, 'p'),
get_img_node(get_pkm_url(base, slug_file, True, is_female, is_right), None, form_name, 'p'),
[get_pkm_form(form_name, form_alias, is_unofficial_icon, is_female, has_unofficial_female_icon), get_pkm_gender(is_female, has_female), get_pkm_gen(is_prev_gen_icon, docs_gen)],
f'{slug_display}'
])
def append_pkm_form(cols, base, slug_display, slug_file, form_name, form_alias, has_female, has_unofficial_female_icon, has_right, add_female, add_right, is_unofficial_icon, is_prev_gen_icon, docs_gen):
'''Adds columns for a single form: at least two, then female sprites, then right-facing sprites'''
append_pkm(cols, base, slug_display, slug_file, form_name, form_alias, has_female, has_unofficial_female_icon, False, False, is_unofficial_icon, is_prev_gen_icon, docs_gen)
if has_female and add_female: append_pkm(cols, base, slug_display, slug_file, form_name, form_alias, has_female, has_unofficial_female_icon, True, False, is_unofficial_icon, is_prev_gen_icon, docs_gen)
if has_right and add_right: append_pkm(cols, base, slug_display, slug_file, form_name, form_alias, has_female, has_unofficial_female_icon, False, True, is_unofficial_icon, is_prev_gen_icon, docs_gen)
def generate_misc_table(misc, meta, curr_page, json_file, version = '[unknown]', commit = '[unknown]'):
'''Generates a documentation table for miscellaneous sprites'''
# Note: this table works slightly different than the rest.
# Instead of having one , it has many of them,
# each one containing one item with potentially multiple sprites.
reset_counter()
groups = meta['misc-groups']
order = list(meta['misc-groups'].keys())
base_url = REPO_BASE_URL
# List of items to display in the opening text.
page_content_list = ['
')
buffer.append('')
for item in misc[misc_set]:
name = item['name']
name_eng = name['eng']
name_jpn = name['jpn']
name_jpn_ro = name['jpn_ro']
origin_gen = item['origin_gen']
desc = item.get('description', {})
desc_eng = desc.get('eng')
desc_gen = desc.get('from_gen')
desc_eng_esc = html.escape(desc_eng) if desc_eng else ''
name_eng_desc = f'{name_eng}' if desc_eng else name_eng
row_n = 0
files = item['files'].items()
buffer.append('')
images = flatten([[item['files'][n]] for n in item['files']])
for k, vs in files:
vs = vs if isinstance(vs, list) else [vs]
gen_row_n = 0
for v in vs:
count = get_counter()
gen_n = get_gen_str(k)
res = item['resolution'][k]
attributes = item.get("attributes", {}).get(k, {})
retina_type = \
'ribbon-gen8' if (res == '2x' and misc_set in ['ribbon', 'mark']) else \
'origin-mark' if (res == '2x' and misc_set in ['origin-marks']) else \
'special-attribute' if (res == '2x' and misc_set in ['special-attribute']) else \
None
buffer.append('
')
buffer.append(f'
{count}
')
if row_n == 0:
rows = len(files)
rowspan = f' rowspan="{len(images)}"' if len(images) > 1 else ''
buffer.append(f'
')
buffer.append('')
for item in misc['seals']:
name = item['name']
name_eng = name['eng']
name_jpn = name['jpn']
row_n = 0
files = item['files'].items()
buffer.append('')
for k, v in files:
count = get_counter()
gen_n = get_gen_str(k)
res = item['resolution'][k]
retina_type = \
'seal-3x' if res == '3x' else \
'seal-2x' if res == '2x' else \
None
buffer.append('
')
buffer.append(f'
{count}
')
if row_n == 0:
rows = len(files)
rowspan = f' rowspan="{rows}"' if rows > 1 else ''
buffer.append(f'
')
buffer.append('')
buffer.append('')
item_dict = {}
for id, item in itm.items():
group, name = item.split('/')
if not item_dict.get(group):
item_dict[group] = []
item_dict[group].append({ 'name': name, 'id': id, 'linked': True })
for item, details in itm_unl.items():
group, name = item.split('/')
type = { 'name': name, 'id': None, 'linked': False, 'type': details['type'], 'dupe_id': details.get('of', {}).get('item_id') }
of_file = details.get('of', {}).get('file')
if details['type'] == 'duplicate' and of_file:
type['expl'] = f'Duplicate of "{of_file}"'
if details['type'] == 'specific' and of_file:
type['expl'] = f'Subitem of "{of_file}"'
item_dict[group].append(type)
for group, items in item_dict.items():
item_dict[group] = sorted(items, key=lambda x: x['name'])
for group, items in item_dict.items():
if not len(items): continue
title = inv['item-groups'].get(group, None)
title = title['name']['eng'] if title else group.title()
buffer.append(f'
{title}
')
for item in items:
count = get_counter()
name = item['name']
id = item['id']
expl = item.get('expl', False)
imgs = ['
Gen %(gen)s sprite overview table%(subtype)s pokesprite-images v%(version)s %(commit)s
' % { 'subtype': ' (new sprites only)' if new_sprites_only else '', 'gen': gen, 'version': version, 'commit': commit })
buffer.append('
#
Dex
Name
名前/ローマ字
Sprites
Form
Slug
')
buffer.append('')
buffer.append('' % { 'gen': gen })
# Loop over each Pokémon and generate rows for each of its forms, one regular and one shiny,
# including gender differences and right-facing sprites.
for idx, pkm in dex.items():
#if int(idx) > 25 and idx != '172' and idx != '593': continue
slug_en = pkm['slug']['eng']
gen_data = pkm[f'gen-{str(gen)}']
# Main columns - contains general information spanned across all rows.
main_cols = [f'#{str(idx)}', pkm['name']['eng'], pkm['name']['jpn'], pkm['name']['jpn_ro']]
# Form columns - form-specific information. A global sprite counter is also prepended.
form_cols = []
if not 'forms' in gen_data:
continue
for form_name, form_data in gen_data['forms'].items():
form_slug_file, form_slug_display, form_alias = determine_form(slug_en, form_name, form_data)
form_name_clean = EMPTY_PLACEHOLDER if form_name == '$' else form_name
is_prev_gen_icon = form_data.get('is_prev_gen_icon', False)
if new_sprites_only and is_prev_gen_icon:
continue
append_pkm_form(
form_cols,
base_url,
form_slug_display,
form_slug_file,
form_name_clean,
form_alias,
form_data.get('has_female', False),
form_data.get('has_unofficial_female_icon', False),
form_data.get('has_right', False),
add_female,
add_right,
form_data.get('is_unofficial_icon', False),
form_data.get('is_prev_gen_icon', False),
gen
)
if not form_cols:
continue
first_col = form_cols[0]
rest_cols = form_cols[1:]
sprites_counter += len(form_cols)
# First row (containing one form and all main cols):
buffer.append('
')
buffer.append(f'
{first_col[0]}
')
for col in main_cols:
buffer.append(f'
{col}
')
for first_row_col in first_col[1:]:
buffer.append(get_td_node(first_row_col))
buffer.append('
')
# All other rows (only form cols, skipping over the main cols):
for row in rest_cols:
buffer.append('
')
for col in row:
buffer.append(get_td_node(col))
buffer.append('
')
# Add the remaining other icons.
if not new_sprites_only:
buffer.append('