Skip to content

Commit

Permalink
Add credits UI
Browse files Browse the repository at this point in the history
Lists all GitHub contributors and moderators, along with GitHub and TPT avatars. Avatars are embedded into the game for instant loading and to guarantee it looks right even without a network connection.

The UI itself is controlled with credits.json. This can be generated in advance using resources/gencredits.py, that script also downloads all the avatars.
  • Loading branch information
jacob1 committed Oct 17, 2024
1 parent b2750fa commit 10c3387
Show file tree
Hide file tree
Showing 18 changed files with 609 additions and 24 deletions.
23 changes: 23 additions & 0 deletions resources/ResourceData.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

class ResourceData
{
public:
const unsigned char * data;
const unsigned int size;

ResourceData(const unsigned char * data, const unsigned int size):
data(data),
size(size)
{ }

ResourceData():
data(nullptr),
size(0)
{ }

bool HasData() const
{
return data != nullptr;
}
};
100 changes: 100 additions & 0 deletions resources/avatars.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import os
import string
import sys

(
script,
output_cpp_path,
output_h_path,
output_dep_path,
input_path,
symbol_prefix,
) = sys.argv

script_path = os.path.realpath(__file__)
avatars_folder_path = os.path.dirname(input_path)

def load_contributors():
contributors = []
with open(input_path) as input_path_f:
while contributor := input_path_f.readline():
if '"' in contributor or "/" in contributor:
raise Exception(f"Invalid data in {input_path}")
contributors.append(contributor.strip())

return contributors

def process_all(contributors):
with open(output_cpp_path, 'w') as output_cpp_f, open(output_h_path, 'w') as output_h_f:
output_cpp_f.write(f'''#include "{output_h_path}"
''')
output_h_f.write(f'''#pragma once
#include <map>
#include <string>
#include "ResourceData.h"
extern std::map<std::string, ResourceData> {symbol_prefix}AvatarLookup;
''')

map_lines = []
for contributor in contributors:
process_contributor(contributor, output_cpp_f, output_h_f, map_lines)

output_cpp_f.write(f'''
std::map<std::string, ResourceData> {symbol_prefix}AvatarLookup = {{
''')
for map_line in map_lines:
output_cpp_f.write(map_line)
output_cpp_f.write("\n")
output_cpp_f.write("};")

with open(output_dep_path, 'w') as output_dep_f:
output_dep_f.write(f'''
{dep_escape(output_cpp_path)} {dep_escape(output_h_path)}: {dep_escape(input_path)} {dep_escape(script_path)}
''')

def process_contributor(contributor, output_cpp_f, output_h_f, map_lines):
with open(f"{avatars_folder_path}/{contributor}.png", 'rb') as input_f:
data = input_f.read()
data_size = len(data)
bytes_str = ', '.join([ str(ch) for ch in data ])

escaped_contributor = username_escape(contributor)
symbol_name = f"{symbol_prefix}_{escaped_contributor}_png"

output_cpp_f.write(f'''
const unsigned char {symbol_name}[] = {{ {bytes_str} }};
const unsigned int {symbol_name}_size = {data_size};
''')

output_h_f.write(f'''
extern const unsigned char {symbol_name}[];
extern const unsigned int {symbol_name}_size;
''')

map_lines.append(f'\t{{ "{contributor}", {{ &{symbol_name}[0], {symbol_name}_size }} }},')

def dep_escape(s):
t = ''
for c in s:
if c in [ ' ', '\\', ':', '$' ]:
t += '\\'
t += c
return t

letters = []
def username_escape(s):
ret = ''
allow_numeric = False
for c in s.lower():
if c in string.ascii_lowercase:
ret = ret + c
allow_numeric = True
if allow_numeric and c in string.digits:
ret = ret + c
return ret


contributors = load_contributors()
process_all(contributors)
12 changes: 12 additions & 0 deletions resources/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,15 @@ endif
data_files += to_array.process('save_local.png', extra_args: 'save_local_png')
data_files += to_array.process('save_online.png', extra_args: 'save_online_png')
data_files += to_array.process('font.bz2', extra_args: 'compressed_font_data')
data_files += to_array.process('credits.json', extra_args: 'credits_json')


avatars_generator = generator(
python3_prog,
output: [ '@[email protected]', '@[email protected]' ],
depfile: '@[email protected]',
arguments: [ join_paths(meson.current_source_dir(), 'avatars.py'), '@OUTPUT0@', '@OUTPUT1@', '@DEPFILE@', '@INPUT@', '@EXTRA_ARGS@' ]
)

data_files += avatars_generator.process('github_avatars/github_avatars.txt', extra_args: 'gh')
data_files += avatars_generator.process('tpt_avatars/tpt_avatars.txt', extra_args: 'tpt')
34 changes: 34 additions & 0 deletions src/gui/credits/ComponentSet.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include "ComponentSet.h"
#include "gui/interface/ScrollPanel.h"

namespace ui
{

ComponentSet::ComponentSet(const std::vector<ui::Component*> &components):
components(components)
{ }

void ComponentSet::AddOffset(ui::Point offset)
{
for (const auto &component : components)
component->Position += offset;
}

void ComponentSet::AddToPanel(ui::ScrollPanel *panel)
{
for (const auto &component : components)
panel->AddChild(component);
}

ui::Point ComponentSet::Size() const
{
ui::Point size = { 0, 0 };
for (const auto &component : components)
{
size.X = std::max(size.X, component->Position.X + component->Size.X);
size.Y = std::max(size.Y, component->Position.Y + component->Size.Y);
}
return size;
}

}
24 changes: 24 additions & 0 deletions src/gui/credits/ComponentSet.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once

#include <vector>
#include "gui/interface/Point.h"

namespace ui
{

class Component;
class ScrollPanel;

class ComponentSet
{
std::vector<ui::Component*> components;

public:
explicit ComponentSet(const std::vector<ui::Component*> &components);

void AddOffset(ui::Point offset);
void AddToPanel(ui::ScrollPanel *panel);
ui::Point Size() const;
};

}
Loading

0 comments on commit 10c3387

Please sign in to comment.