Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 41 additions & 11 deletions C7/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using C7Engine;
using C7GameData;
using Serilog;
using C7.UIElements;
using C7Engine.Pathing;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -178,6 +179,8 @@ public override async void _Ready() {
FileDialog.Canceled += OnResolved;
FileDialog.FileSelected += path => OnResolved();

advisor.VisibilityChanged += OnAdvisorVisibilityChanged;

try {
await InitializeGame();
await StartGame();
Expand Down Expand Up @@ -380,6 +383,9 @@ public void HandleEngineMessage(MessageToUI msg) {
case MsgShowScienceAdvisor mSSA:
EmitSignal(SignalName.ShowSpecificAdvisor, C7Action.ShowScienceAdvisor);
break;
case MsgPromptResearch:
PromptForResearchIfNeeded();
break;
case MsgUpdateUiAfterDomesticChange mUUASC:
// Ensure the citizen moods are correct before displaying them.
foreach (City c in controller.cities) {
Expand Down Expand Up @@ -501,6 +507,40 @@ private void _onEndTurnButtonPressed() {
}
}

// Runs any action a popup deferred until an advisor screen was closed.
private void OnAdvisorVisibilityChanged() {
if (!advisor.Visible) {
popupOverlay.OnAdvisorClosed();
}
}

// Prompts for a tech to research, or auto-picks one if prompts are disabled.
private void PromptForResearchIfNeeded() {
if (controller.cities.Count == 0 || popupOverlay.ShowingPopup) {
return;
}
if (controller.GetAvailableTechsToResearch(EngineStorage.gameData.techs).Count == 0) {
return;
}

if (controller.freeTechsRemaining > 0) {
popupOverlay.ShowPopup(new ScienceSelection(controller, claimingFreeTech: true), PopupOverlay.PopupCategory.Info);
return;
}

if (controller.currentlyResearchedTech != null) {
return;
}

bool promptForResearch = PreferencesSettings.GetPromptForResearch();

if (promptForResearch) {
popupOverlay.ShowPopup(new ScienceSelection(controller), PopupOverlay.PopupCategory.Info);
} else {
PlayerAI.MaybePickTechToResearch(controller, EngineStorage.gameData.techs);
}
}

private void OnPlayerStartTurn() {
EngineStorage.ReadGameData((GameData gameData) => {
log.Information("Starting player turn");
Expand All @@ -520,17 +560,7 @@ private void OnPlayerStartTurn() {

// If the player can pick a new tech to research, prompt them to do so
// once they have a city.
if (controller.cities.Count > 0
&& controller.currentlyResearchedTech == null
&& controller.GetAvailableTechsToResearch(gameData.techs).Count > 0) {
popupOverlay.ShowPopup(
new ScienceSelection(controller),
PopupOverlay.PopupCategory.Info);

if (controller.currentlyResearchedTech == null && controller.GetAvailableTechsToResearch(gameData.techs).Count > 0) {
PlayerAI.MaybePickTechToResearch(controller, gameData.techs);
}
}
PromptForResearchIfNeeded();

// Allow fast forwarding in observer mode.
if (gameData.observerMode && turnsLeftToFastForward > 0) {
Expand Down
2 changes: 1 addition & 1 deletion C7/UIElements/Advisors/ScienceAdvisor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private void LoadTechTree() {
});
}

void DrawTechTree(string eraName, Player player, List<Tech> allTechs, HashSet<Tech> availableTechsToResearch) {
void DrawTechTree(string eraName, Player player, List<Tech> allTechs, List<Tech> availableTechsToResearch) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this hashset converted to a List? It's done in a few places..

// clear all tech-box items so we don't draw new ones over the old ones
foreach (TechBox tb in techBoxes) {
background.RemoveChild(tb);
Expand Down
2 changes: 1 addition & 1 deletion C7/UIElements/Popups/Popup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ protected void AddButton(string label, int verticalPosition, Action action) {
button.SetPosition(new Vector2(HORIZONTAL_POSITION, verticalPosition));
AddChild(button);
button.Pressed += () => {
action();
ReleaseFocus();
action();

@Billytifft Billytifft Oct 11, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is to fix a crash when selecting Whats the Big Picture from Science Advisor popup.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have never crashed there, could you elaborate on what happens, and even how to reproduce it?

};
}

Expand Down
14 changes: 14 additions & 0 deletions C7/UIElements/Popups/PopupOverlay.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Godot;
using C7GameData;
using Serilog;
using System;

[GlobalClass]
[Tool]
Expand All @@ -26,6 +27,9 @@ public partial class PopupOverlay : HBoxContainer {

Control currentChild = null;

// Set when a popup defers its decision until an advisor screen is closed.
private Action advisorCloseAction;

[Export]
private Control control;

Expand All @@ -47,6 +51,16 @@ public void OnHidePopup() {

public bool ShowingPopup => currentChild is not null;

public void SetAdvisorCloseAction(Action action) {
advisorCloseAction = action;
}

public void OnAdvisorClosed() {
Action action = advisorCloseAction;
advisorCloseAction = null;
action?.Invoke();
}

public void PlaySound(AudioStream stream) {
AudioStreamPlayer player = GetNode<AudioStreamPlayer>("PopupSound");
player.Stream = stream;
Expand Down
60 changes: 50 additions & 10 deletions C7/UIElements/Popups/ScienceSelection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
public partial class ScienceSelection : Popup {
Player player;
List<Tech> options = new();
bool claimingFreeTech;
OptionButton optionButton;

public ScienceSelection(Player player) {
public ScienceSelection(Player player, bool claimingFreeTech = false) {
alignment = BoxContainer.AlignmentMode.End;
margins = new Margins(right: 10);
this.player = player;
this.claimingFreeTech = claimingFreeTech;
}

public override void _Ready() {
Expand All @@ -30,26 +33,29 @@ public override void _Ready() {
AddHeader("Science Advisor", 120);

Label messageLabel = new();
messageLabel.Text = "What shall we explore now?";
messageLabel.Text = claimingFreeTech
? "You have earned a free advance. Choose a technology:"
: "What shall we explore now?";
messageLabel.SetPosition(new Vector2(25, 160));
AddChild(messageLabel);

OptionButton optionButton = MakeStyledOptionButton();
optionButton = MakeStyledOptionButton();
AddChild(optionButton);
optionButton.SetPosition(new Vector2(25, 190));

AddButton("OK. Sounds good.", 235, () => {
new MsgChooseResearch(options[optionButton.Selected], AdvisorState.DontShow).send();
GetParent().EmitSignal(PopupOverlay.SignalName.HidePopup);
Choose(SelectedTech);
});
AddButton("What's the big picture?", 265, () => {
if (claimingFreeTech) {
GetParent<PopupOverlay>().SetAdvisorCloseAction(ClaimFreeTechFromCurrentResearch);
}
new MsgShowScienceAdvisor().send();
GetParent().EmitSignal(PopupOverlay.SignalName.HidePopup);
});

AddConfirmButton(new Vector2(width - 40, height - 40), () => {
new MsgChooseResearch(options[optionButton.Selected], AdvisorState.DontShow).send();
GetParent().EmitSignal(PopupOverlay.SignalName.HidePopup);
Choose(SelectedTech);
});
}

Expand Down Expand Up @@ -95,9 +101,43 @@ private OptionButton MakeStyledOptionButton() {
}

private void AddItem(GameData gameData, Tech tech, OptionButton optionButton) {
int turns = player.EstimateTurnsToResearch(gameData, tech);
string turnsStr = turns == int.MaxValue ? "--" : $"{turns}";
optionButton.AddItem($"{tech.Name} ({turnsStr} turns)");
if (claimingFreeTech) {
optionButton.AddItem(tech.Name);
} else {
int turns = player.EstimateTurnsToResearch(gameData, tech);
string turnsStr = turns == int.MaxValue ? "--" : $"{turns}";
optionButton.AddItem($"{tech.Name} ({turnsStr} turns)");
}
options.Add(tech);
}

private void Choose(Tech tech) {
if (tech == null) {
return;
}
if (claimingFreeTech) {
new MsgClaimFreeTech(tech).send();
} else {
new MsgChooseResearch(tech, AdvisorState.DontShow).send();
}
GetParent().EmitSignal(PopupOverlay.SignalName.HidePopup);
}

// Spends the free advance on the queued tech, matching Civ3.
private void ClaimFreeTechFromCurrentResearch() {
Tech selected = player.ResearchQueue.Count > 0 ? player.ResearchQueue.Peek() : SelectedTech;
if (selected != null) {
new MsgClaimFreeTech(selected).send();
}
}

private Tech SelectedTech {
get {
int selected = optionButton.Selected;
if (selected < 0 || selected >= options.Count) {
return null;
}
return options[selected];
}
}
}
8 changes: 8 additions & 0 deletions C7Engine/AI/PlayerAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ private static void MaybeDoPriorityReevaluation(Player player) {
}

public static void MaybePickTechToResearch(Player player, List<Tech> techs) {
// AI players get no prompt, so they spend any free advances immediately.
while (!player.isHuman && player.freeTechsRemaining > 0) {
Tech freeTech = player.GetAvailableTechsToResearch(techs).FirstOrDefault();
if (freeTech == null || !player.ClaimFreeTech(EngineStorage.gameData, freeTech)) {
break;
}
}

while (player.currentlyResearchedTech == null || player.knownTechs.Contains(player.currentlyResearchedTech)) {
Tech toResearch = player.GetAvailableTechsToResearch(techs).FirstOrDefault();
if (toResearch == null) {
Expand Down
Loading
Loading