aboutsummaryrefslogtreecommitdiff
path: root/game
diff options
context:
space:
mode:
author Hop311 <Hop3114@gmail.com>2023-08-17 20:25:25 +0200
committer GitHub <noreply@github.com>2023-08-17 20:25:25 +0200
commitaa9961b27a4859f088b037dd8037accb4e3119be (patch)
treee93af9cabfbefc6ea089a153399e04d74e4b605a /game
parentff0d38b5d53fa95609f2587a2be5205f0c0d3118 (diff)
parent9a086523513994b0183d5f7d479b2f82412177f6 (diff)
Merge pull request #146 from Nemrav/piechart2
Add Piecharts (gd 4.1)
Diffstat (limited to 'game')
-rw-r--r--game/localisation/en_GB/mapmodes.csv1
-rw-r--r--game/localisation/en_GB/menus.csv3
-rw-r--r--game/src/Game/Autoload/GuiScale.gd10
-rw-r--r--game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd7
-rw-r--r--game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn24
-rw-r--r--game/src/Game/GameStart.gd9
-rw-r--r--game/src/Game/GameStart.tscn3
-rw-r--r--game/src/Game/LoadingScreen.gd10
-rw-r--r--game/src/Game/Menu/OptionMenu/GuiScaleSelector.gd2
-rw-r--r--game/src/Game/Theme/PieChart/PieChart.gd216
-rw-r--r--game/src/Game/Theme/PieChart/PieChart.tscn38
11 files changed, 296 insertions, 27 deletions
diff --git a/game/localisation/en_GB/mapmodes.csv b/game/localisation/en_GB/mapmodes.csv
index 3b7fd52..8fa5798 100644
--- a/game/localisation/en_GB/mapmodes.csv
+++ b/game/localisation/en_GB/mapmodes.csv
@@ -7,3 +7,4 @@ mapmode_index;Index
mapmode_rgo;RGO
mapmode_infrastructure;Infrastructure
mapmode_population;Population Density
+mapmode_culture;Nationality
diff --git a/game/localisation/en_GB/menus.csv b/game/localisation/en_GB/menus.csv
index 1d16af6..57994ad 100644
--- a/game/localisation/en_GB/menus.csv
+++ b/game/localisation/en_GB/menus.csv
@@ -131,3 +131,6 @@ building_fort;Fort
building_naval_base;Naval Base
building_railroad;Railroad
EXPAND_PROVINCE_BUILDING;Expand
+
+;; Pie Chart
+PIECHART_TOOLTIP_NO_DATA;No data
diff --git a/game/src/Game/Autoload/GuiScale.gd b/game/src/Game/Autoload/GuiScale.gd
index afd73df..d364ff5 100644
--- a/game/src/Game/Autoload/GuiScale.gd
+++ b/game/src/Game/Autoload/GuiScale.gd
@@ -26,7 +26,7 @@ func _ready():
func has_guiscale(guiscale_value : float) -> bool:
return guiscale_value in _guiscales
-
+
func add_guiscale(guiscale_value: float, guiscale_name: StringName=&"") -> bool:
if has_guiscale(guiscale_value): return true
var scale_dict := { value = guiscale_value }
@@ -39,24 +39,24 @@ func add_guiscale(guiscale_value: float, guiscale_name: StringName=&"") -> bool:
return false
_guiscales[guiscale_value] = scale_dict
return true
-
+
#returns floats
func get_guiscale_value_list() -> Array:
var list := _guiscales.keys()
list.sort_custom(func(a, b): return a > b)
return list
-
+
func get_guiscale_display_name(guiscale_value : float) -> StringName:
return _guiscales.get(guiscale_value, {display_name = &"unknown gui scale"}).display_name
func get_current_guiscale() -> float:
return get_tree().root.content_scale_factor
-
+
func set_guiscale(guiscale:float) -> void:
print("New GUI scale: %f" % guiscale)
if not has_guiscale(guiscale):
push_warning("Setting GUI Scale to non-standard value %sx" % [guiscale])
get_tree().root.content_scale_factor = guiscale
-
+
func reset_guiscale() -> void:
set_guiscale(get_current_guiscale())
diff --git a/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd b/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd
index 9a3690e..0220be2 100644
--- a/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd
+++ b/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd
@@ -7,6 +7,9 @@ extends PanelContainer
@export var _rgo_icon_texture_rect : TextureRect
@export var _rgo_name_label : Label
@export var _buildings_container : Container
+@export var _pop_type_chart : PieChart
+@export var _pop_ideology_chart : PieChart
+@export var _pop_culture_chart : PieChart
const _missing_suffix : String = "_MISSING"
@@ -113,6 +116,10 @@ func _update_info() -> void:
_total_population_label.text = Localisation.tr_number(_province_info.get(GameSingleton.get_province_info_total_population_key(), 0))
+ _pop_type_chart.set_to_distribution(_province_info.get(GameSingleton.get_province_info_pop_types_key(), {}))
+ _pop_ideology_chart.set_to_distribution(_province_info.get(GameSingleton.get_province_info_pop_ideologies_key(), {}))
+ _pop_culture_chart.set_to_distribution(_province_info.get(GameSingleton.get_province_info_pop_cultures_key(), {}))
+
_rgo_name_label.text = _province_info.get(GameSingleton.get_province_info_rgo_key(),
GameSingleton.get_province_info_rgo_key() + _missing_suffix)
_rgo_icon_texture_rect.texture = GameSingleton.get_good_icon_texture(_rgo_name_label.text)
diff --git a/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn b/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn
index 7c82f10..7e49ac8 100644
--- a/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn
+++ b/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn
@@ -1,8 +1,9 @@
-[gd_scene load_steps=2 format=3 uid="uid://byq323jbel48u"]
+[gd_scene load_steps=3 format=3 uid="uid://byq323jbel48u"]
[ext_resource type="Script" path="res://src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd" id="1_3n8k5"]
+[ext_resource type="PackedScene" uid="uid://cr7p1k2xm7mum" path="res://src/Game/Theme/PieChart/PieChart.tscn" id="2_3oytt"]
-[node name="ProvinceOverviewPanel" type="PanelContainer" node_paths=PackedStringArray("_province_name_label", "_region_name_label", "_life_rating_bar", "_total_population_label", "_rgo_icon_texture_rect", "_rgo_name_label", "_buildings_container")]
+[node name="ProvinceOverviewPanel" type="PanelContainer" node_paths=PackedStringArray("_province_name_label", "_region_name_label", "_life_rating_bar", "_total_population_label", "_rgo_icon_texture_rect", "_rgo_name_label", "_buildings_container", "_pop_type_chart", "_pop_ideology_chart", "_pop_culture_chart")]
editor_description = "UI-56"
anchors_preset = 2
anchor_top = 1.0
@@ -19,6 +20,9 @@ _total_population_label = NodePath("PanelList/InteractList/TotalPopulation")
_rgo_icon_texture_rect = NodePath("PanelList/InteractList/RGOInfo/RGOIcon")
_rgo_name_label = NodePath("PanelList/InteractList/RGOInfo/RGOName")
_buildings_container = NodePath("PanelList/InteractList/BuildingsContainer")
+_pop_type_chart = NodePath("PanelList/InteractList/PopStats/PopTypeChart")
+_pop_ideology_chart = NodePath("PanelList/InteractList/PopStats/PopIdeologyChart")
+_pop_culture_chart = NodePath("PanelList/InteractList/PopStats/PopCultureChart")
[node name="PanelList" type="VBoxContainer" parent="."]
layout_mode = 2
@@ -88,6 +92,22 @@ vertical_alignment = 1
layout_mode = 2
mouse_filter = 1
+[node name="PopStats" type="HBoxContainer" parent="PanelList/InteractList"]
+layout_mode = 2
+
+[node name="PopTypeChart" parent="PanelList/InteractList/PopStats" instance=ExtResource("2_3oytt")]
+layout_mode = 2
+
+[node name="PopIdeologyChart" parent="PanelList/InteractList/PopStats" instance=ExtResource("2_3oytt")]
+layout_mode = 2
+
+[node name="PopCultureChart" parent="PanelList/InteractList/PopStats" instance=ExtResource("2_3oytt")]
+layout_mode = 2
+
+[node name="HSeparator3" type="HSeparator" parent="PanelList/InteractList"]
+layout_mode = 2
+mouse_filter = 1
+
[node name="BuildingsContainer" type="GridContainer" parent="PanelList/InteractList"]
layout_mode = 2
columns = 3
diff --git a/game/src/Game/GameStart.gd b/game/src/Game/GameStart.gd
index 995541f..bfbbfb1 100644
--- a/game/src/Game/GameStart.gd
+++ b/game/src/Game/GameStart.gd
@@ -49,12 +49,3 @@ func _initialize_game() -> void:
func _on_splash_container_splash_end():
loading_screen.show()
-
-func _on_loading_screen_load_started():
- Events.Loader.startup_load_begun.emit()
-
-func _on_loading_screen_load_changed(percentage : float) -> void:
- Events.Loader.startup_load_changed.emit(percentage)
-
-func _on_loading_screen_load_ended():
- Events.Loader.startup_load_ended.emit()
diff --git a/game/src/Game/GameStart.tscn b/game/src/Game/GameStart.tscn
index f16daa3..189e72f 100644
--- a/game/src/Game/GameStart.tscn
+++ b/game/src/Game/GameStart.tscn
@@ -51,8 +51,5 @@ stream = ExtResource("5_8euyy")
autoplay = true
expand = true
-[connection signal="load_changed" from="LoadingScreen" to="." method="_on_loading_screen_load_changed"]
-[connection signal="load_ended" from="LoadingScreen" to="." method="_on_loading_screen_load_ended"]
-[connection signal="load_started" from="LoadingScreen" to="." method="_on_loading_screen_load_started"]
[connection signal="splash_end" from="SplashContainer" to="." method="_on_splash_container_splash_end"]
[connection signal="finished" from="SplashContainer/SplashVideo" to="SplashContainer" method="_on_splash_startup_finished"]
diff --git a/game/src/Game/LoadingScreen.gd b/game/src/Game/LoadingScreen.gd
index 3cbf199..d953d20 100644
--- a/game/src/Game/LoadingScreen.gd
+++ b/game/src/Game/LoadingScreen.gd
@@ -1,9 +1,5 @@
extends Control
-signal load_started()
-signal load_changed(percentage : float)
-signal load_ended()
-
@export var quote_file_path : String = "res://common/quotes.txt"
@export_subgroup("Nodes")
@@ -24,7 +20,7 @@ func start_loading_screen(thread_safe_function : Callable) -> void:
thread.wait_to_finish()
thread.start(thread_safe_function)
- load_started.emit()
+ Events.Loader.startup_load_begun.emit()
func try_update_loading_screen(percent_complete: float, quote_should_change = false):
# forces the function to behave as if deferred
@@ -34,9 +30,9 @@ func try_update_loading_screen(percent_complete: float, quote_should_change = fa
quote_label.text = quotes[randi() % quotes.size()]
if is_equal_approx(percent_complete, 100):
thread.wait_to_finish()
- load_ended.emit()
+ Events.Loader.startup_load_ended.emit()
else:
- load_changed.emit(percent_complete)
+ Events.Loader.startup_load_changed.emit(percent_complete)
func _ready():
if Engine.is_editor_hint(): return
diff --git a/game/src/Game/Menu/OptionMenu/GuiScaleSelector.gd b/game/src/Game/Menu/OptionMenu/GuiScaleSelector.gd
index 4dd86e1..42be5e0 100644
--- a/game/src/Game/Menu/OptionMenu/GuiScaleSelector.gd
+++ b/game/src/Game/Menu/OptionMenu/GuiScaleSelector.gd
@@ -32,7 +32,7 @@ func _sync_guiscales(to_select : float = GuiScale.get_current_guiscale()) -> voi
if selected == -1:
selected = default_selected
-
+
func _setup_button():
if default_value <= 0:
default_value = ProjectSettings.get_setting("display/window/stretch/scale")
diff --git a/game/src/Game/Theme/PieChart/PieChart.gd b/game/src/Game/Theme/PieChart/PieChart.gd
new file mode 100644
index 0000000..cfd7917
--- /dev/null
+++ b/game/src/Game/Theme/PieChart/PieChart.gd
@@ -0,0 +1,216 @@
+@tool
+extends TextureRect
+
+class_name PieChart
+
+
+@export var donut : bool = false
+@export_range(0.0, 1.0) var donut_inner_radius : float = 0.5
+@export_range(0.0, 0.5) var radius : float = 0.4
+@export var shadow_displacement : Vector2 = Vector2(0.55, 0.6)
+@export var shadow_tightness : float = 1.0
+@export var shadow_radius : float = 0.6
+@export var shadow_thickness : float = 1.0
+
+@export var trim_colour : Color = Color(0.0, 0.0, 0.0)
+@export_range(0.0, 1.0) var trim_size : float = 0.02
+@export var donut_inner_trim : bool = true
+@export var slice_gradient_falloff : float = 3.6
+@export var slice_gradient_base : float = 3.1
+
+@export var _rich_tooltip : RichTextLabel
+var _pie_chart_image : Image
+
+# A data class for the pie chart
+class SliceData:
+ extends RefCounted
+ # Primary properties, change these to change
+ # the displayed piechart
+ var colour : Color = Color(1.0, 0.0, 0.0)
+ var tooltip : String = ""
+ var quantity : float = -1
+ # Derived properties, don't set from an external script
+ var final_angle : float = -1
+ var percentage : float = 0:
+ get:
+ return percentage
+ set(value):
+ percentage = clampf(value, 0, 1)
+
+ func _init(quantityIn : float, tooltipIn : String, colourIn : Color):
+ colour = colourIn
+ tooltip = tooltipIn
+ quantity = quantityIn
+
+# The key of an entry of this dictionary should be an easy to reference constant
+# The tooltip label is what the user will actually read
+var _slices : Dictionary = {}
+
+# Slice keys/labels in the order they should be displayed
+var _slice_order : Array = []
+
+# Example slices:
+"""
+ "label1": SliceData.new(5, "Conservative", Color(0.0, 0.0, 1.0)),
+ "label2": SliceData.new(3, "Liberal", Color(1.0, 1.0, 0.0)),
+ "label3": SliceData.new(2, "Reactionary", Color(0.4, 0.0, 0.6))
+"""
+
+# These functions are the interface a developer will use to update the piechart
+# The piechart will only redraw once one of these has been triggered
+func add_or_replace_label(labelName : String, quantity : float, tooltip : String, colour : Color = Color(0.0, 0.0, 0.0)) -> void:
+ _slices[labelName] = SliceData.new(quantity, tooltip, colour)
+ if _slice_order.find(labelName) == -1:
+ _slice_order.push_back(labelName)
+ _recalculate()
+
+func update_label_quantity(labelName : String, quantity : float) -> void:
+ if _slices.has(labelName):
+ _slices[labelName].quantity = quantity
+ _recalculate()
+
+func update_label_colour(labelName : String, colour : Color) -> void:
+ if _slices.has(labelName):
+ _slices[labelName].colour = colour
+ _recalculate()
+
+func update_label_tooltip(labelName : String, tooltip : String) -> void:
+ if _slices.has(labelName):
+ _slices[labelName].tooltip = tooltip
+
+func remove_label(labelName : String) -> bool:
+ if _slices.erase(labelName):
+ var index := _slice_order.find(labelName)
+ if index == -1:
+ push_error("Slice in dictionary but not order list: ", labelName)
+ else:
+ _slice_order.remove_at(index)
+ _recalculate()
+ return true
+ return false
+
+func clear_slices() -> void:
+ _slices.clear()
+ _slice_order.clear()
+
+# Distribution dictionary of the form:
+# { "<label>": { "size": <quantity>, "colour": <colour> } }
+func set_to_distribution(dist : Dictionary) -> void:
+ clear_slices()
+ for key in dist:
+ var entry : Dictionary = dist[key]
+ _slices[key] = SliceData.new(entry[GameSingleton.get_piechart_info_size_key()], key, entry[GameSingleton.get_piechart_info_colour_key()])
+ _slice_order = _slices.keys()
+ sort_slices()
+
+# Sorted by quantity, smallest to largest, so that the smallest slice
+# is to the left of a radial line straight upwards
+func sort_slices() -> void:
+ _slice_order.sort_custom(func (a, b): return _slices[a].quantity < _slices[b].quantity)
+ _recalculate()
+
+func _ready():
+ if not Engine.is_editor_hint():
+ const image_size : int = 256
+ _pie_chart_image = Image.create(image_size, image_size, false, Image.FORMAT_RGBA8)
+ texture = ImageTexture.create_from_image(_pie_chart_image)
+ _recalculate()
+
+# Update the slice angles based on the new slice data
+func _recalculate() -> void:
+ # Where the slices are the public interface, these are the actual paramters
+ # which will be sent to the shader/draw function
+ var angles : Array = []
+ var colours : Array = []
+
+ var total : float = 0
+ for label in _slice_order:
+ var quantity : float = _slices[label].quantity
+ if quantity > 0:
+ total += quantity
+
+ var current_arc_start : float = 0
+ var current_arc_finish : float = 0
+
+ for label in _slice_order:
+ var slice : SliceData = _slices[label]
+ if slice.quantity > 0:
+ slice.percentage = slice.quantity / total
+ var rads_to_cover : float = slice.percentage * 2.0 * PI
+ current_arc_finish = current_arc_start + rads_to_cover
+ slice.final_angle = current_arc_finish
+ current_arc_start = current_arc_finish
+ angles.push_back(current_arc_finish)
+ colours.push_back(slice.colour)
+
+ GameSingleton.draw_pie_chart(_pie_chart_image, angles, colours, radius, shadow_displacement, shadow_tightness, shadow_radius, shadow_thickness,
+ trim_colour, trim_size, slice_gradient_falloff, slice_gradient_base, donut, donut_inner_trim, donut_inner_radius / 2)
+ texture.set_image(_pie_chart_image)
+
+# Process mouse to select the appropriate tooltip for the slice
+func _gui_input(event : InputEvent):
+ if event is InputEventMouse:
+ var pos : Vector2 = event.position
+ var _handled : bool = _handle_tooltip(pos)
+
+func _on_mouse_exited():
+ _rich_tooltip.visible = false
+
+# Takes a mouse position, and sets an appropriate tooltip for the slice the mouse
+# is hovered over. Returns a boolean on whether the tooltip was handled.
+func _handle_tooltip(pos : Vector2) -> bool:
+ # Is it within the circle?
+ var real_radius := size.x / 2.0
+ var center := Vector2(real_radius, real_radius)
+ var distance := center.distance_to(pos)
+ var real_donut_inner_radius : float = real_radius * donut_inner_radius
+ if distance <= real_radius and (not donut or distance >= real_donut_inner_radius):
+ if _slice_order.is_empty():
+ _rich_tooltip.text = "PIECHART_TOOLTIP_NO_DATA"
+ else:
+ var angle := _convert_angle(center.angle_to_point(pos))
+ var selected_label : String = ""
+ for label in _slice_order:
+ if angle <= _slices[label].final_angle:
+ if not selected_label or _slices[label].final_angle < _slices[selected_label].final_angle:
+ selected_label = label
+ if not selected_label:
+ selected_label = _slice_order[0]
+ _rich_tooltip.text = _create_tooltip(selected_label)
+ _rich_tooltip.visible = true
+ _rich_tooltip.position = pos + Vector2(5, 5) + get_global_rect().position
+ _rich_tooltip.reset_size()
+ else:
+ # Technically the corners of the bounding box
+ # are part of the chart, but we don't want a tooltip there
+ _rich_tooltip.visible = false
+ return _rich_tooltip.visible
+
+# Create a list of all the values and percentages
+# with the hovered one highlighted
+func _create_tooltip(labelHovered : String) -> String:
+ var slice_tooltips : PackedStringArray = []
+ for label in _slice_order:
+ var slice : SliceData = _slices.get(label)
+ var percent := _format_percent(slice.percentage)
+ var entry : String = "%s %s%%" % [label, percent]
+ if label == labelHovered:
+ entry = "[i][u][b]>>%s<<[/b][/u][/i]" % entry
+ slice_tooltips.push_back(entry)
+ # Slices are ordered smallest to largest, but here we want the opposite
+ slice_tooltips.reverse()
+ return "[font_size=10]%s[/font_size]" % "\n".join(slice_tooltips)
+
+# Angle from center.angle_to_point is measured from the +x axis,
+# but the chart starts from +y
+# The input angle is also -180 to 180, where we want 0 to 360
+func _convert_angle(angleIn : float) -> float:
+ # Make the angle start from +y, range is now -90 to 270
+ var angle := angleIn + PI / 2.0
+ # Adjust range to be 0 to 360
+ if angle < 0:
+ angle += 2.0 * PI
+ return angle
+
+func _format_percent(percentIn : float) -> float:
+ return snappedf((percentIn * 100), 0.1)
diff --git a/game/src/Game/Theme/PieChart/PieChart.tscn b/game/src/Game/Theme/PieChart/PieChart.tscn
new file mode 100644
index 0000000..a0e9992
--- /dev/null
+++ b/game/src/Game/Theme/PieChart/PieChart.tscn
@@ -0,0 +1,38 @@
+[gd_scene load_steps=2 format=3 uid="uid://cr7p1k2xm7mum"]
+
+[ext_resource type="Script" path="res://src/Game/Theme/PieChart/PieChart.gd" id="2_ub6u3"]
+
+[node name="PieChart" type="TextureRect" node_paths=PackedStringArray("_rich_tooltip")]
+custom_minimum_size = Vector2(50, 50)
+anchors_preset = -1
+anchor_right = 0.039
+anchor_bottom = 0.069
+offset_right = -32.92
+offset_bottom = -34.68
+size_flags_horizontal = 4
+size_flags_vertical = 4
+expand_mode = 3
+script = ExtResource("2_ub6u3")
+_rich_tooltip = NodePath("RichToolTip")
+
+[node name="RichToolTip" type="RichTextLabel" parent="."]
+visible = false
+top_level = true
+layout_mode = 2
+offset_right = 50.0
+offset_bottom = 50.0
+mouse_filter = 2
+bbcode_enabled = true
+fit_content = true
+autowrap_mode = 0
+
+[node name="Panel" type="Panel" parent="RichToolTip"]
+show_behind_parent = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[connection signal="mouse_exited" from="." to="." method="_on_mouse_exited"]