Add deterministic captures 14-21 for the remaining screen-design states, fix the panel/button layout issues they exposed, and record 21/21 compare and judge results. Bump the source to 0.9.3 and move the completed milestone to DONE.md.
179 lines
7.0 KiB
GDScript
179 lines
7.0 KiB
GDScript
class_name ShiftReview
|
|
extends CanvasLayer
|
|
## End-of-shift review screen (PLAN.md: Evidence and Body Camera, Shift Review).
|
|
|
|
const Strings := preload("res://src/core/strings.gd")
|
|
const UiArt := preload("res://src/game/ui/art.gd")
|
|
const UiStyles := preload("res://src/game/ui/ui_styles.gd")
|
|
const UiFocus := preload("res://src/game/ui/ui_focus.gd")
|
|
|
|
signal restart_requested()
|
|
signal continue_requested()
|
|
signal timeline_requested(review: Dictionary)
|
|
|
|
## Kept so the timeline button can hand the full review back to the game manager.
|
|
var _review: Dictionary = {}
|
|
|
|
var report_label: Label
|
|
var _campaign_label: Label = null
|
|
var _continue_button: Button = null
|
|
var _timeline_button: Button = null
|
|
var _restart_button: Button = null
|
|
var _quit_button: Button = null
|
|
|
|
func _init() -> void:
|
|
name = "ShiftReview"
|
|
layer = 30
|
|
var root := Control.new()
|
|
root.name = "ReviewRoot"
|
|
root.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
add_child(root)
|
|
var art := UiArt.new()
|
|
if not art.add_screen_backdrop(root, "shift_review",
|
|
Rect2(0.172, 0.033, 0.59, 0.927)):
|
|
var dim := ColorRect.new()
|
|
dim.color = Color(0.04, 0.05, 0.08, 0.92)
|
|
dim.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
root.add_child(dim)
|
|
|
|
var center_host := CenterContainer.new()
|
|
center_host.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
root.add_child(center_host)
|
|
var center := VBoxContainer.new()
|
|
center.custom_minimum_size = Vector2(760, 0)
|
|
center.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
center.add_theme_constant_override("separation", 8)
|
|
center_host.add_child(center)
|
|
|
|
var title := Label.new()
|
|
title.text = Strings.lookup("review.title")
|
|
title.add_theme_font_size_override("font_size", 28)
|
|
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
center.add_child(title)
|
|
|
|
report_label = Label.new()
|
|
report_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
report_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
report_label.custom_minimum_size = Vector2(720, 0)
|
|
center.add_child(report_label)
|
|
|
|
_campaign_label = Label.new()
|
|
_campaign_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
_campaign_label.add_theme_color_override("font_color", Color(0.62, 0.8, 0.95))
|
|
center.add_child(_campaign_label)
|
|
|
|
_continue_button = Button.new()
|
|
_continue_button.name = "ContinueButton"
|
|
_continue_button.text = Strings.lookup("review.continue_campaign")
|
|
_continue_button.pressed.connect(func(): continue_requested.emit())
|
|
_continue_button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
|
center.add_child(_continue_button)
|
|
|
|
_timeline_button = Button.new()
|
|
_timeline_button.name = "TimelineButton"
|
|
_timeline_button.text = Strings.lookup("review.timeline_button")
|
|
_timeline_button.pressed.connect(func(): timeline_requested.emit(_review))
|
|
_timeline_button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
|
center.add_child(_timeline_button)
|
|
|
|
_restart_button = Button.new()
|
|
_restart_button.name = "ReturnMenuButton"
|
|
_restart_button.text = Strings.lookup("ui.review_return_menu")
|
|
_restart_button.pressed.connect(func(): restart_requested.emit())
|
|
_restart_button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
|
center.add_child(_restart_button)
|
|
|
|
_quit_button = Button.new()
|
|
_quit_button.name = "QuitButton"
|
|
_quit_button.text = Strings.lookup("ui.quit_game")
|
|
_quit_button.pressed.connect(func(): get_tree().quit())
|
|
_quit_button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
|
_style_review_button(_timeline_button)
|
|
_style_review_button(_continue_button)
|
|
_style_review_button(_restart_button)
|
|
_style_review_button(_quit_button)
|
|
center.add_child(_quit_button)
|
|
|
|
func _style_review_button(button: Button) -> void:
|
|
button.custom_minimum_size = Vector2(300, 46)
|
|
UiStyles.apply_button(button, 22)
|
|
|
|
func show_review(review: Dictionary, scenario: Resource, history: Array = [],
|
|
campaign_summary: Dictionary = {}) -> void:
|
|
_review = review
|
|
var reputation: Dictionary = review.get("reputation", {})
|
|
var incidents: Dictionary = review.get("incidents", {})
|
|
var crowd: Dictionary = review.get("crowd", {})
|
|
var radio: Dictionary = review.get("radio", {})
|
|
var outcomes: Array = review.get("responder_outcomes", [])
|
|
var investigations: Array = review.get("investigations", [])
|
|
var incident_list: Array = incidents.get("incidents", [])
|
|
var peaceful := 0
|
|
for inc in incident_list:
|
|
if inc.get("outcome", "") in ["peaceful", "left"]:
|
|
peaceful += 1
|
|
|
|
# Persisted-shift history (Save autoload): record count + best rating.
|
|
var history_line := ""
|
|
if history.size() > 0:
|
|
var best := -1.0
|
|
var past_grades: Array[String] = []
|
|
for entry in history:
|
|
var rep: Dictionary = entry.get("reputation", {})
|
|
var rating_v: float = float(rep.get("rating", 0.0))
|
|
best = maxf(best, rating_v)
|
|
var g: String = rep.get("grade", "?")
|
|
if not past_grades.has(g):
|
|
past_grades.append(g)
|
|
history_line = Strings.fmt("review.history", [
|
|
history.size(), best, ", ".join(past_grades)])
|
|
|
|
var lines: Array[String] = [
|
|
Strings.fmt("review.venue", [scenario.display_name if scenario else review.get("scenario_id", "")]),
|
|
Strings.fmt("review.policy", [scenario.policy_summary if scenario else "See venue policy."]),
|
|
Strings.fmt("review.grade", [reputation.get("grade", "?"), reputation.get("rating", 0.0)]),
|
|
history_line,
|
|
Strings.fmt("review.incidents", [incident_list.size(), peaceful]),
|
|
Strings.fmt("review.npcs", [review.get("spawned_count", 0)]),
|
|
Strings.fmt("review.radio", [radio.get("battery", 0.0), radio.get("emergency_cooldown_s", 0.0)]),
|
|
Strings.fmt("review.crowd", [crowd.get("mood", 0.0) * 100.0, str(crowd.get("panic", false))]),
|
|
Strings.fmt("review.responder", [", ".join(outcomes) if outcomes.size() > 0 else Strings.lookup("review.none")]),
|
|
Strings.fmt("review.investigations", [" | ".join(investigations) if investigations.size() > 0 else Strings.lookup("review.none")]),
|
|
"",
|
|
Strings.lookup("review.highlights"),
|
|
]
|
|
var mods: Array = reputation.get("modifiers", [])
|
|
var shown := 0
|
|
for mod in mods:
|
|
var delta: float = float(mod.get("delta", 0.0))
|
|
lines.append(Strings.fmt("review.modifier", [str(mod.get("category", "")), "%+.0f" % delta, str(mod.get("reason", ""))]))
|
|
shown += 1
|
|
if shown >= 12:
|
|
break
|
|
report_label.text = "\n".join(lines)
|
|
var next_venue := str(campaign_summary.get("next_venue", ""))
|
|
var campaign_complete := bool(campaign_summary.get("complete", false))
|
|
if _campaign_label:
|
|
if campaign_complete:
|
|
_campaign_label.text = Strings.lookup("review.campaign_complete")
|
|
elif next_venue != "":
|
|
_campaign_label.text = Strings.fmt("review.campaign_next", [
|
|
int(campaign_summary.get("shift_number", 1)) - 1,
|
|
next_venue.replace("_", " ").capitalize()])
|
|
else:
|
|
_campaign_label.text = ""
|
|
if _continue_button:
|
|
_continue_button.visible = not campaign_complete and next_venue != ""
|
|
visible = true
|
|
var buttons: Array = [_continue_button, _timeline_button, _restart_button, _quit_button]
|
|
UiFocus.link(buttons)
|
|
var first := UiFocus.first_enabled(buttons)
|
|
if first:
|
|
first.grab_focus()
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if not visible:
|
|
return
|
|
if event.is_action_pressed("ui_cancel"):
|
|
restart_requested.emit()
|
|
get_viewport().set_input_as_handled() |