aboutsummaryrefslogtreecommitdiff
path: root/game/src/CreditsMenu/CreditsMenu.gd
blob: 0db4d7d690f17007949ee4d5e75431ef843ecc8e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
extends Control

signal back_button_pressed

###############
# Credits CSV format
# The project title row is the only requirement within the csv file, however
# it can be on any row, so long as it exists.
# ----------------------
# title,project-title
# role-name,person-name
# role-name,person-name
# role-name,person-name
# ...
###############

@export_file("*.csv")
var core_credits_path : String

@export
var godot_engine_scene : PackedScene

@export_group("Label Variants", "label_variants_")
@export
var label_variants_project : StringName

@export
var label_variants_role : StringName

@export
var label_variants_person : StringName

@export
var credits_list: VBoxContainer

const title_key : String = "TITLE"

# REQUIREMENTS:
# * 1.5 Credits Menu
# * SS-17

# REQUIREMENTS
# * FS-4
func _load_credit_file(path : String) -> Dictionary:
   var roles := {}
   var core_credits = FileAccess.open(path, FileAccess.READ)
   if core_credits == null:
      push_error("Failed to open credits file %s (error code %d)" % [path, FileAccess.get_open_error()])
      return roles

   while not core_credits.eof_reached():
      var line := core_credits.get_csv_line()
      var role := line[0].strip_edges().to_upper()

      # If the line does not have an identifiable role or is empty then skip it
      if role.is_empty() or line.size() < 2:
         if not (role.is_empty() and line.size() < 2):
            push_warning("Incorrectly formatted credit line %s in %s" % [line, path])
         continue

      var person := line[1].strip_edges()

      if person.is_empty():
         push_warning("Incorrectly formatted credit line %s in %s" % [line, path])
         continue
      if line.size() > 2:
         push_warning("Extra entries ignored in credit line %s in %s" % [line, path])

      if role not in roles:
         roles[role] = [person]
      else:
         if person in roles[role]:
            push_warning("Duplicate person %s for role %s in %s" % [person, role, path])
         else:
            roles[role].push_back(person)
   if title_key in roles:
      if roles[title_key].size() > 1:
         push_warning("More than one %s: %s in %s" % [title_key, roles[title_key], path])
         roles[title_key] = [roles[title_key][0]]
   else:
      push_warning("Credits file %s missing %s" % [path, title_key])
   for role_list in roles.values():
      role_list.sort_custom(func(a : String, b : String) -> bool: return a.naturalnocasecmp_to(b) < 0)
   return roles

func _add_label(node : Node, text : String, type_variation : StringName) -> void:
   var label := Label.new()
   label.name = 'Label' + text
   label.text = text
   label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
   label.theme_type_variation = type_variation
   node.add_child(label)

# REQUIREMENTS:
# * UI-34, UI-35
func _add_project_credits(project : Dictionary) -> void:
   var project_credits_list = VBoxContainer.new()
   project_credits_list.name = 'Credits'
   if title_key in project:
      var title : String = project[title_key][0]
      project_credits_list.name += title
      _add_label(project_credits_list, title, label_variants_project)
      project_credits_list.add_child(HSeparator.new())

   for role in project:
      if role == title_key:
         continue

      var role_parent = VBoxContainer.new()

      for person in project[role]:
         _add_label(role_parent, person, label_variants_person)

      _add_label(project_credits_list, role, label_variants_role)
      project_credits_list.add_child(role_parent)
      project_credits_list.add_child(HSeparator.new())

   credits_list.add_child(project_credits_list)

func _add_godot_credits() -> void:
   var godot_credits_list = VBoxContainer.new()
   godot_credits_list.name = 'CreditsGodot'
   var godot_engine = godot_engine_scene.instantiate()
   godot_credits_list.add_child(godot_engine)
   godot_credits_list.add_child(HSeparator.new())

   var author_dict := Engine.get_author_info()
   _add_label(godot_credits_list, "Contributors", label_variants_role)

   for role in author_dict:
      var role_parent = VBoxContainer.new()

      for person in author_dict[role]:
         _add_label(role_parent, person, label_variants_person)

      _add_label(godot_credits_list, role.replace("_", " ").capitalize(), label_variants_role)
      godot_credits_list.add_child(role_parent)
      godot_credits_list.add_child(HSeparator.new())

   var donor_dict := Engine.get_donor_info()
   _add_label(godot_credits_list, "Donors", label_variants_role)

   for role in donor_dict:
      if donor_dict[role].size() == 0 or donor_dict[role][0].begins_with("None"): continue
      var role_parent = VBoxContainer.new()

      for person in donor_dict[role]:
         _add_label(role_parent, person, label_variants_person)

      _add_label(godot_credits_list, role.replace("_", " ").capitalize(), label_variants_role)
      godot_credits_list.add_child(role_parent)
      godot_credits_list.add_child(HSeparator.new())

   credits_list.add_child(godot_credits_list)

func _add_link_button(node : Node, text : String, url: String, type_variation : StringName) -> void:
   var button := LinkButton.new()
   button.name = 'LinkButton' + text
   button.text = text
   button.uri = url
   button.size_flags_horizontal = SIZE_SHRINK_CENTER
   button.theme_type_variation = type_variation
   node.add_child(button)

func _add_licenses() -> void:
   var license_list = VBoxContainer.new()
   license_list.name = 'Licenses'
   _add_label(license_list, "Third-Party Licenses", label_variants_project)
   license_list.add_child(HSeparator.new())

   var license_info := {
      "OpenVic": ["GPLv3", "https://github.com/OpenVicProject/OpenVic/blob/main/LICENSE.md"],
      "Godot": ["MIT", "https://github.com/godotengine/godot/blob/master/LICENSE.txt"],
      "FreeType": ["FreeType License", "https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/docs/FTL.TXT"],
      "ENet": ["MIT", "http://enet.bespin.org/License.html"],
      "mbed TLS": ["APLv2", "https://github.com/Mbed-TLS/mbedtls/blob/development/LICENSE"]
   }
   # Add additional licenses required for attribution here
   # These licenses should also either be displayed or exported alongside this project

   for project in license_info:
      _add_label(license_list, project, label_variants_role)
      _add_link_button(license_list, license_info[project][0], license_info[project][1], label_variants_person)
      license_list.add_child(HSeparator.new())

   credits_list.add_child(license_list)


# REQUIREMENTS:
# * SS-17
func _ready():
   _add_project_credits(_load_credit_file(core_credits_path))
   _add_godot_credits()
   _add_licenses()

# REQUIREMENTS:
# * UI-38
# * UIFUN-37
func _on_back_button_pressed() -> void:
   back_button_pressed.emit()