Skip to content

Commit

Permalink
Import known civs and war statuses from SAVs, and add a diplomacy popup
Browse files Browse the repository at this point in the history
We now have the lower right hand "D" button for opening the diplomacy popup, and the popup lists the civs we know and whether we're at war with them.

In the LEAD struct I changed some byte arrays to longs/ints to make it easier to dump them to the console when debugging.

In order for the "D" button to work I had to shuffle the "GameStatus" node in front of the unit buttons node, because the invisible HBox for the unit buttons was intercepting all the mouse events.

#571
  • Loading branch information
TomWerner committed Feb 19, 2025
1 parent 7f34aca commit 014ea6c
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 20 deletions.
25 changes: 13 additions & 12 deletions C7/C7Game.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,6 @@ grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2

[node name="GameStatus" type="MarginContainer" parent="CanvasLayer/Control"]
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 0
grow_vertical = 0
script = ExtResource("7")

[node name="UnitButtons" type="VBoxContainer" parent="CanvasLayer/Control" node_paths=PackedStringArray("primaryControls", "specializedControls", "advancedControls")]
layout_mode = 1
anchors_preset = 12
Expand Down Expand Up @@ -142,6 +131,18 @@ layout_mode = 2
size_flags_vertical = 0
alignment = 1

[node name="GameStatus" type="MarginContainer" parent="CanvasLayer/Control" node_paths=PackedStringArray("popupOverlay")]
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 0
grow_vertical = 0
script = ExtResource("7")
popupOverlay = NodePath("../../PopupOverlay")

[node name="ToolBar" type="Control" parent="CanvasLayer/Control"]
layout_mode = 1
anchors_preset = 10
Expand Down Expand Up @@ -281,8 +282,8 @@ visible = false
[connection signal="Quit" from="CanvasLayer/PopupOverlay" to="." method="OnQuitTheGame"]
[connection signal="Retire" from="CanvasLayer/PopupOverlay" to="." method="_on_QuitButton_pressed"]
[connection signal="UnitDisbanded" from="CanvasLayer/PopupOverlay" to="." method="OnUnitDisbanded"]
[connection signal="BlinkyEndTurnButtonPressed" from="CanvasLayer/Control/GameStatus" to="." method="OnPlayerEndTurn"]
[connection signal="ActionRequested" from="CanvasLayer/Control/UnitButtons" to="." method="ProcessAction"]
[connection signal="BlinkyEndTurnButtonPressed" from="CanvasLayer/Control/GameStatus" to="." method="OnPlayerEndTurn"]
[connection signal="pressed" from="CanvasLayer/Control/ToolBar/MarginContainer/HBoxContainer/AdvisorButton" to="CanvasLayer/Advisor" method="ShowLatestAdvisor"]
[connection signal="pressed" from="CanvasLayer/Control/ToolBar/MarginContainer/HBoxContainer/UiBarEndTurnButton" to="." method="_onEndTurnButtonPressed"]
[connection signal="toggled" from="CanvasLayer/Control/SlideOutBar/SlideToggle" to="." method="_on_SlideToggle_toggled"]
Expand Down
3 changes: 3 additions & 0 deletions C7/UIElements/GameStatus/GameStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public partial class GameStatus : MarginContainer {

[Signal] public delegate void BlinkyEndTurnButtonPressedEventHandler();

[Export]
public PopupOverlay popupOverlay;

// Called when the node enters the scene tree for the first time.
public override void _Ready() {
OffsetLeft = -(294 + 5);
Expand Down
32 changes: 32 additions & 0 deletions C7/UIElements/GameStatus/LowerRightInfoBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public partial class LowerRightInfoBox : TextureRect {
Label yearAndGold = new Label();
Label scienceProgress = new();

TextureButton openDiplomacy = new ();

Timer blinkingTimer = new Timer();
bool timerStarted = false; //This "isStopped" returns false if it's never been started. So we need this to know if we've ever started it.

Expand Down Expand Up @@ -88,6 +90,23 @@ private void CreateUI() {
blinkingTimer.WaitTime = 0.6f;
blinkingTimer.Timeout += toggleEndTurnButton;
AddChild(blinkingTimer);

// Add the diplomacy button, with a "D" label on it.
openDiplomacy.TextureNormal = Util.LoadTextureFromPCX("Art/interface/consoleButtons.pcx", 1, 1, 16, 16);
openDiplomacy.TextureHover = Util.LoadTextureFromPCX("Art/interface/consoleButtons.pcx", 17, 1, 16, 16);
openDiplomacy.TexturePressed = Util.LoadTextureFromPCX("Art/interface/consoleButtons.pcx", 33, 1, 16, 16);
openDiplomacy.SetPosition(new Vector2(268, 34));
openDiplomacy.Pressed += OpenDiplomacyPopup;
openDiplomacy.TooltipText = "Diplomacy";
AddChild(openDiplomacy);
openDiplomacy.Hide();

Label openDiplomacyLabel = new();
openDiplomacyLabel.Text = "D";
openDiplomacyLabel.OffsetLeft = 3;
openDiplomacyLabel.OffsetTop = -3;
openDiplomacyLabel.MouseFilter = Control.MouseFilterEnum.Ignore;
openDiplomacy.AddChild(openDiplomacyLabel);
}

public void SetEndOfTurnStatus() {
Expand Down Expand Up @@ -178,6 +197,11 @@ public override void _Process(double delta) {

// Civ and government.
SetTextAndCenterLabel(civAndGovt, $"{player.civilization.name} - Despotism (5.5.0)");

// Only show the diplomacy button if we have civs to talk to.
if (player.playerRelationships.Count > 0 && !openDiplomacy.Visible) {
openDiplomacy.Show();
}
}

base._Process(delta);
Expand All @@ -193,6 +217,14 @@ private void SetTextAndCenterLabel(Label label, string text) {
label.AnchorLeft = 0.5f;
label.AnchorRight = 0.5f;
label.OffsetLeft = -1 * (label.Size.X / 2.0f);
}

private void OpenDiplomacyPopup() {
using (UIGameDataAccess gameDataAccess = new()) {
GameData gD = gameDataAccess.gameData;
Player player = gD.GetHumanPlayers()[0];

GetParent<GameStatus>().popupOverlay.ShowPopup(new DiplomacySelection(player, gD.players), PopupOverlay.PopupCategory.Info);
}
}
}
61 changes: 61 additions & 0 deletions C7/UIElements/Popups/DiplomacySelection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Godot;
using System;
using System.Diagnostics;
using C7GameData;
using C7GameData.Save;
using System.Collections.Generic;
using Serilog;

// The popup for selecting which other civilization to contact.
public partial class DiplomacySelection : Popup {
private Player player;
private List<Player> allPlayers;

public DiplomacySelection(Player player, List<Player> allPlayers) {
alignment = BoxContainer.AlignmentMode.Center;
margins = new Margins(top: 200);
this.player = player;
this.allPlayers = allPlayers;
}

public override void _Ready() {
base._Ready();

int width = 530;
int height = 115 + 25 * player.playerRelationships.Keys.Count;
AddTexture(width, height);
AddBackground(width, height);
AddHeader("Pick the civilization...", 10);

int vOffset = 65;
foreach (KeyValuePair<ID, PlayerRelationship> kvp in player.playerRelationships) {
string status = kvp.Value.atWar ? "War" : "Peace";
AddButton($"{allPlayers.Find(x => x.id == kvp.Key).civilization.noun} (at {status})", vOffset, () => { });
vOffset += 25;
}

//Cancel/confirm buttons. Note the X button is thinner than the O button.
// TODO: Push this shared logic up into Popup.cs
ImageTexture circleTexture= Util.LoadTextureFromPCX("Art/X-o_ALLstates-sprite.pcx", 1, 1, 19, 19);
ImageTexture xTexture = Util.LoadTextureFromPCX("Art/X-o_ALLstates-sprite.pcx", 21, 1, 15, 19);
ImageTexture circleHover = Util.LoadTextureFromPCX("Art/X-o_ALLstates-sprite.pcx", 37, 1, 19, 19);
ImageTexture xHover = Util.LoadTextureFromPCX("Art/X-o_ALLstates-sprite.pcx", 57, 1, 15, 19);
ImageTexture circlePressed = Util.LoadTextureFromPCX("Art/X-o_ALLstates-sprite.pcx", 73, 1, 19, 19);
ImageTexture xPressed = Util.LoadTextureFromPCX("Art/X-o_ALLstates-sprite.pcx", 93, 1, 15, 19);
TextureButton confirmButton = new TextureButton();
confirmButton.TextureNormal = circleTexture;
confirmButton.TextureHover = circleHover;
confirmButton.TexturePressed = circlePressed;
confirmButton.SetPosition(new Vector2(width - 55, height - 47));
AddChild(confirmButton);
TextureButton cancelButton = new TextureButton();
cancelButton.TextureNormal = xTexture;
cancelButton.TextureHover = xHover;
cancelButton.TexturePressed = xPressed;
cancelButton.SetPosition(new Vector2(width - 30, height - 47));
AddChild(cancelButton);

// TODO: Do something when the confirm button is pressed.
cancelButton.Pressed += GetParent<PopupOverlay>().OnHidePopup;
}
}
29 changes: 29 additions & 0 deletions C7GameData/ImportCiv3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using QueryCiv3;
using QueryCiv3.Biq;
using C7GameData.Save;
using System.Reflection;
using System.ComponentModel;

/*
This will read a Civ3 sav into C7 native format for immediate use or saving to native JSON save
Expand Down Expand Up @@ -433,6 +435,22 @@ private void ImportSavLeaders() {
save.Players.Add(player);
i++;
}

// Now that we know all the players, fill in details about their
// relationship to each other.
i = 0;
foreach (QueryCiv3.Sav.LEAD leader in savData.Lead) {
List<int> contacts = leader.GetContact();
List<bool> warStatus = leader.GetWarStatuses();
for (int j = 0; j < contacts.Count; ++j) {
if (contacts[j] > 0) {
save.Players[i].playerRelationships.Add(save.Players[j].id.ToString(), new PlayerRelationship() {
atWar = warStatus[j],
});
}
}
++i;
}
}

private SavePlayer MakeSavePlayerFromCiv(Civilization civ, bool isBarbarian, bool isHuman, int cityNameIndex, string era) {
Expand Down Expand Up @@ -918,7 +936,18 @@ private static TileLocation GetTileFromSpiral(TileLocation start, int spiral) {

_ => throw new ArgumentOutOfRangeException("Invalid spiral value" + spiral),
};
}

// A handy utility for trying to reverse engineer various structs when
// comparing SAV files.
private void DumpObject(string label, object o) {
Console.WriteLine(label);
foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(o)) {
Console.WriteLine($"\t{descriptor.Name}={descriptor.GetValue(o)}");
}
foreach (System.Reflection.FieldInfo fieldInfo in o.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) {
Console.WriteLine($"\t{fieldInfo.Name}={fieldInfo.GetValue(o)}");
}
}
}
}
5 changes: 4 additions & 1 deletion C7GameData/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
using System.Collections.Generic;
using System.Linq;
using C7Engine.AI.StrategicAI;
using C7GameData.Save;

namespace C7GameData {

public class Player {
public ID id { get; internal set; }
public int colorIndex;
Expand All @@ -24,6 +24,9 @@ public class Player {
//Ordered list of priority data. First is most important.
public List<StrategicPriority> strategicPriorityData = new List<StrategicPriority>();

// A map from player id to the relationship this player has with the other player.
public Dictionary<ID, PlayerRelationship> playerRelationships = new();

// The list of techs known by this player.
public HashSet<ID> knownTechs = new();

Expand Down
14 changes: 14 additions & 0 deletions C7GameData/Save/SavePlayer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using System.Collections.Generic;

namespace C7GameData.Save {
// A class holding all the state of the relationship between two civs.
public class PlayerRelationship {
public bool atWar = false;
}

public class SavePlayer {
public ID id;
Expand All @@ -14,6 +18,9 @@ public class SavePlayer {

public List<TileLocation> tileKnowledge = new List<TileLocation>();

// A map from player id to the relationship this player has with the other player.
public Dictionary<string, PlayerRelationship> playerRelationships = new();

// The list of techs known by this player.
public HashSet<ID> knownTechs = new();

Expand Down Expand Up @@ -71,6 +78,9 @@ public Player ToPlayer(GameMap map, List<Civilization> civilizations) {
player.knownTechs.Add(techId);
}
}
foreach (KeyValuePair<string, PlayerRelationship> keyValuePair in this.playerRelationships) {
player.playerRelationships.Add(ID.FromString(keyValuePair.Key), keyValuePair.Value);
}

// Because of the custom setter we need to set the researched tech
// and then set the beakers and turns researched - otherwise they'd
Expand Down Expand Up @@ -105,6 +115,10 @@ public SavePlayer(Player player) {
gold = player.gold;
beakers = player.beakers;
turnsResearched = player.turnsResearched;

foreach (KeyValuePair<ID, PlayerRelationship> keyValuePair in player.playerRelationships) {
playerRelationships.Add(keyValuePair.Key.ToString(), keyValuePair.Value);
}
}
}
}
42 changes: 35 additions & 7 deletions QueryCiv3/SavSections/Lead.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;

namespace QueryCiv3.Sav {
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct LEAD_LEAD {
private fixed byte UnknownBuffer[4];
private int UnknownBuffer;
public int CancelledDeal;
private fixed byte UnknownBuffer2[8];
private long UnknownBuffer2;
public int CaughtSpy;
private fixed byte UnknownBuffer3[4];
private int UnknownBuffer3;
public int Tribute;
private fixed byte UnknownBuffer4[8];
private long UnknownBuffer4;
public int Gift;
private fixed byte UnknownBuffer5[4];
private int UnknownBuffer5;
public int ICBM;
public int ICBMOther;
private fixed byte UnknownBuffer6[24];
Expand Down Expand Up @@ -55,11 +56,11 @@ public unsafe struct LEAD {
public int Length;
public int PlayerID;
public int RaceID;
private fixed byte UnknownBuffer[4];
private int UnknownBuffer;
public int Power;
public int CapitalCity;
public int Difficulty;
private fixed byte UnknownBuffer2[8];
private long UnknownBuffer2;
public int GoldenAgeEndTurn; // -1 if GA not yet triggered

private fixed byte Flags[4];
Expand Down Expand Up @@ -103,13 +104,40 @@ public unsafe struct LEAD {
public fixed int RefuseContactForTurns[32];
private fixed byte UnknownBuffer8[128];
private fixed int WarWearinessPoints[32];

private fixed bool WarStatus[32];

// Returns a list of booleans, where the i'th boolean being true means
// this leader is at war with the i'th leader.
public List<bool> GetWarStatuses() {
List<bool> result = new();
for (int i = 0; i < 32; ++i) {
result.Add(WarStatus[i]);
}
return result;
}

private fixed bool Embassies[32];
private fixed bool Spies[32];
private fixed bool FailedSpyMission[32];
private fixed int BorderViolation[32];
private fixed int GoldPerTurnTo[32];

private fixed int Contact[32];

// Returns a list of integers, where the i'th integer being non-zero
// means this leader has contact with the i'th leader.
//
// Sometimes the values are 1 or 3, it isn't clear what that difference
// means.
public List<int> GetContact() {
List<int> result = new();
for (int i = 0; i < 32; ++i) {
result.Add(Contact[i]);
}
return result;
}

private fixed int Agreements[32];
private fixed int Alliances[32];
private fixed int Embargoes[32];
Expand Down

0 comments on commit 014ea6c

Please sign in to comment.