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
9 changes: 4 additions & 5 deletions C7/UIElements/NewGame/PlayerSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ public partial class PlayerSetup : Control {
SaveGame save;
GameSetup gameSetup = new();

// TODO: read this from the rules based on the world size
const int NUM_OPPONENTS = 7;

// Called when the node enters the scene tree for the first time.
public override void _Ready() {
save = GetSave();
Expand Down Expand Up @@ -106,7 +103,9 @@ private void BackToMainMenu() {

private void AddOpponentSelectors() {
// TODO: The number of opponents should come from the rule set.
for (int i = 0; i < NUM_OPPONENTS; ++i) {
int num_opponents = GetNode<GlobalSingleton>("/root/GlobalSingleton").WorldCharacteristics.worldSize.numberOfCivs - 1;

for (int i = 0; i < num_opponents; ++i) {
OptionButton optionButton = new();
StyleBoxFlat styleBox = new() {
BorderColor = Color.Color8(150, 150, 150, 220),
Expand Down Expand Up @@ -134,7 +133,7 @@ private void AddOpponentSelectors() {
opponentListContainer.AddChild(container);
container.AddChild(optionButton);

container.CustomMinimumSize = new Vector2(312.0f / opponentListContainer.Columns, 315.0f / NUM_OPPONENTS);
container.CustomMinimumSize = new Vector2(312.0f / opponentListContainer.Columns, 315.0f / num_opponents);
optionButton.CustomMinimumSize = new Vector2(290.0f / opponentListContainer.Columns, optionButton.CustomMinimumSize.Y);

foreach (Civilization civ in save.Civilizations) {
Expand Down
96 changes: 80 additions & 16 deletions C7/UIElements/NewGame/WorldSetup.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Godot;
using System;
using System.Threading;
using System.Collections.Generic;
using C7GameData;
using C7Engine;
using C7GameData.Save;
Expand Down Expand Up @@ -76,6 +77,60 @@ public partial class WorldSetup : Control {
WorldCharacteristics.Temperature temp = WorldCharacteristics.Temperature.Temperate;
WorldCharacteristics.Climate clim = WorldCharacteristics.Climate.Normal;

// Maybe we refactor all this data later into a separate JSON/config file?
// I wasn't sure if it fit well into the MapGenerator.cs enum-style since it's a lot of integer data to store

private int sizeSelected = 2; // Correlates to standard size being default
private readonly Random sizeRandomizer = new Random();
private readonly List<WorldSize> sizeOptions = new List<WorldSize>
{
// Tiny, index 0
new WorldSize() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Where are these values for the WorldSize variants coming from?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From the Civ 3 wiki + hardcoded game values: https://civilization.fandom.com/wiki/Map_(Civ3)

Albeit, they are indeed coded as "magic numbers" right now.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know if @TomWerner had something specific in mind when he opened the issue, besides making easier for modders to play around with the sizes, etc, and that's why he said that this should be saved in the json save file.
The other thing would be dynamic names for the buttons (Tiny, Small, etc) for all the World Sizes, vanilla & custom.
I would prefer if we wrote that to the json as well, but the current code doesn't break anything I tested so far with .biq, .sav and .json files.
If this were to be accepted, a TODO would have to go in there for a future refactor so that it can go in the json.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you can point me to the right JSON file to save the configs in (or the directory where I should make a new config file) I'll happily refactor it. I agree that direction is better modular design.

Copy link
Contributor

Choose a reason for hiding this comment

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

The json file is actually two files c7-static-map-save.json and c7-static-map-save-standalone.json
These files are used to launch a new game (for now, today Yegor opened a PR that merges them into one actually, but still valid), the first for the original version and the other one for the standalone with seperate graphics. I can't explain the whole thing here, but I would suggest you take some time, and try to understand how the creating/loading works.
There is a class called CreateGame.cs that you can have a look at and start to unravel the whole thing. Most of the code's variables/methods/classes are well named, so if you can't find something through code, you can search the text for what would be the most likely name, and chances are you are going to find it.
I would also suggest to use a debugger when you want to examine things in detail, and also have a look at the existing unit tests and even write your own if you want.
If you have any questions, discord is better for this kind of thing I believe

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, we don't want to be hardcoding these.

This information should be present in the WSIZ or WCHR (world size/world characteristic) fields. We grab some data from them in ImportCiv3 right now:

private static void SetMapDimensions(SavData civ3Save, SaveGame save) {
if (civ3Save is not null && civ3Save.Wrld.Height > 0 && civ3Save.Wrld.Width > 0) {
save.Map.tilesTall = civ3Save.Wrld.Height;
save.Map.tilesWide = civ3Save.Wrld.Width;
save.Map.techRate = civ3Save.Bic.Wsiz[civ3Save.Wrld.WsizID].TechRate;
save.Map.optimalNumberOfCities = civ3Save.Bic.Wsiz[civ3Save.Wrld.WsizID].OptimalNumberOfCities;
}
}
private static void SetMapDimensions(BiqData biq, SaveGame save) {
if (biq is not null && biq.Wmap is not null && biq.Wmap.Length > 0) {
save.Map.tilesTall = biq.Wmap[0].Height;
save.Map.tilesWide = biq.Wmap[0].Width;
save.Map.techRate = biq.Wsiz[biq.Wchr[0].WorldSize].TechRate;
save.Map.optimalNumberOfCities = biq.Wsiz[biq.Wchr[0].WorldSize].OptimalNumberOfCities;
}
}

If we pull from there (and save it in the JSON files) then it's available to modders, existing civ3 mods that change the world sizes and such will work correctly, and it'll generally work nicely.

In case you're a printf debugging guy, you can use dotnet test ../EngineTests/EngineTests.csproj -v n to run the tests and see print statements you add, otherwise a debugger works great too. The WSIZ/WCHR classes should map to the civ3 editor pretty closely if you want to inspect the values from vanilla civ3.

Copy link
Contributor

Choose a reason for hiding this comment

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

One other printf debugging note, there's this nice DumpObject function in ImportCiv3.cs that I've used with some of the civ3 structs before.

Copy link
Contributor

@stavrosfa stavrosfa Jan 15, 2026

Choose a reason for hiding this comment

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

Good to know!

In Rider, when running in Debug, you can right click on a variable and select Evaluate Expression, where you can run something like

System.Text.Json.JsonSerializer.Serialize(biq.Prto[4])

and it will turn the object into a json, without having to modify the code, or if you have forgotten something, you can always run this as long as the var is in the current scope.

This probably doesn't cover private fields like DumpObject, but still quite helpful for larger objects

width = 60,
height = 60,
numberOfCivs = 4,
distanceBetweenCivs = 11,
techRate = 160,
optimalNumberOfCities = 14,
},
// Small, index 1
new WorldSize() {
width = 80,
height = 80,
numberOfCivs = 6,
distanceBetweenCivs = 11,
techRate = 200,
optimalNumberOfCities = 17,
},
// Standard, index 2
new WorldSize() {
width = 100,
height = 100,
numberOfCivs = 8,
distanceBetweenCivs = 12,
techRate = 240,
optimalNumberOfCities = 20,
},
// Large, index 3
new WorldSize() {
width = 130,
height = 130,
numberOfCivs = 12,
distanceBetweenCivs = 18,
techRate = 320,
optimalNumberOfCities = 28,
},
// Huge, index 4
new WorldSize() {
width = 160,
height = 160,
numberOfCivs = 16,
distanceBetweenCivs = 24,
techRate = 400,
optimalNumberOfCities = 36,
},
};

// Called when the node enters the scene tree for the first time.
public override void _Ready() {
background.Texture = TextureLoader.Load("world_setup.background");
Expand Down Expand Up @@ -267,14 +322,30 @@ public override void _Ready() {
billion4Large.Visible = true;
billion4.ButtonPressed = true;

// TODO: handle different map sizes properly (including loading the
// optimal city number, etc)
tinySize.Visible = false;
smallSize.Visible = false;
standardSize.Visible = false;
largeSize.Visible = false;
hugeSize.Visible = false;
randomSize.Visible = false;
tinySize.Pressed += () => {
sizeSelected = 0;
};

smallSize.Pressed += () => {
sizeSelected = 1;
};

standardSize.Pressed += () => {
sizeSelected = 2;
};

largeSize.Pressed += () => {
sizeSelected = 3;
};

hugeSize.Pressed += () => {
sizeSelected = 4;
};

randomSize.Pressed += () => {
sizeSelected = sizeRandomizer.Next(sizeOptions.Count);
};

}

private void ResetLandformGraphics() {
Expand Down Expand Up @@ -322,14 +393,7 @@ private void CreateGame() {
age = age,
climate = clim,
temperature = temp,
worldSize = new WorldSize() {
width = 100,
height = 100,
numberOfCivs = 8,
distanceBetweenCivs = 12,
techRate = 240,
optimalNumberOfCities = 20,
},
worldSize = sizeOptions[sizeSelected],
terrainTypes = save.TerrainTypes,
resources = save.Resources,
defaultGovernment = save.Governments.Find(x => x.defaultType),
Expand Down
12 changes: 6 additions & 6 deletions C7/UIElements/NewGame/world_setup.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ text = "WORLD SIZE"
horizontal_alignment = 1

[node name="TinySize" type="CheckButton" parent="Background"]
visible = false
visible = true
layout_mode = 0
offset_left = 167.0
offset_top = 115.0
Expand All @@ -130,7 +130,7 @@ text = "Tiny"
alignment = 2

[node name="SmallSize" type="CheckButton" parent="Background"]
visible = false
visible = true
layout_mode = 0
offset_left = 160.0
offset_top = 137.0
Expand All @@ -148,7 +148,7 @@ text = "Small"
alignment = 2

[node name="StandardSize" type="CheckButton" parent="Background"]
visible = false
visible = true
layout_mode = 0
offset_left = 136.0
offset_top = 159.0
Expand All @@ -167,7 +167,7 @@ text = "Standard"
alignment = 2

[node name="LargeSize" type="CheckButton" parent="Background"]
visible = false
visible = true
layout_mode = 0
offset_left = 162.0
offset_top = 183.0
Expand All @@ -185,7 +185,7 @@ text = "Large"
alignment = 2

[node name="HugeSize" type="CheckButton" parent="Background"]
visible = false
visible = true
layout_mode = 0
offset_left = 163.0
offset_top = 208.0
Expand All @@ -203,7 +203,7 @@ text = "Huge"
alignment = 2

[node name="RandomSize" type="CheckButton" parent="Background"]
visible = false
visible = true
layout_mode = 0
offset_left = 140.0
offset_top = 231.0
Expand Down