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
1 change: 1 addition & 0 deletions C7/Lua/rules/civ3.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ return {
--]]
terraforms = require "civ3.terraforms",
terrain_improvements = require "civ3.terrain_improvements",
inflows = require "civ3.inflows"
}
124 changes: 124 additions & 0 deletions C7/Lua/rules/civ3/inflows.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
local function rules()
return GAME_DATA().rules
end

local inflows = {}

-- to match an item in a list based on a predicate
local function any(list, predicate)
for _, v in ipairs(list) do
if predicate(v) then
return true
end
end
return false
end

local function doubles_wealth_production(tech)
return tech.DoublesWealthProduction == true
end

-- context is [ Player player, City city ]
-- this is the actual (minimal) implementation we would do for Wealth, for conquests
local function extra_commerce_calculation(context)
local player = context.player
local city = context.city

local useful_shields = city.CurrentProductionYield().useful
local known_techs = player.GetKnownTechs()
local double_effect = any(known_techs, doubles_wealth_production)
local ratio = double_effect and (rules().ShieldCostPerGold / 2) or rules().ShieldCostPerGold

return math.max(1, useful_shields / ratio)
end

-- Any and all of the table values below should return an integer
-- that will be added to the respective base value.
--
-- for example commerce will be added to the overall commerce income, culture to the current culture per turn.
--
-- maintenance, unitsupport and corruption are the ones that will be subtracted by their current base value
-- so if you want MORE corruption/maintenance/unit support, the number should be negative

inflows.result = {
wealth = {
commerce = function(context)
return extra_commerce_calculation(context)
end,
},
-- add new inflow(s) as the example below.
-- The respective json entry with the yieldCalculation having the path to this new item
-- should be added in the json save file, under inflows
-- not all fields like commerce, culture, science etc are mandatory, as long as they reflect the ones in the json

--placeholder = {
-- commerce = function(context)
-- -- replace with a hadcoded value, a method call, etc
-- return 0
-- end,
-- culture = function(context)
-- -- replace with a hadcoded value, a method call, etc
-- return 0
-- end,
-- science = function(context)
-- -- replace with a hadcoded value, a method call, etc
-- return 0
-- end,
-- happiness = function(context)
-- -- replace with a hadcoded value, a method call, etc
-- return 0
-- end,
-- maintenance = function(context)
-- -- replace with a hadcoded value, a method call, etc
-- return 0
-- end,
-- unitsupport = function(context)
-- -- replace with a hadcoded value, a method call, etc
-- return 0
-- end,
-- corruption = function(context)
-- -- replace with a hadcoded value, a method call, etc
-- return 0
-- end,
--},


-- json example of an entry

--{
-- "name": "Placeholder", <- this is the display name in the production box
-- "iconRowIndex": 29,
-- "localYield": [
-- {
-- "yieldType": "commerce",
-- "yieldCalculation": "inflows.result.placeholder.commerce"
-- },
-- {
-- "yieldType": "culture",
-- "yieldCalculation": "inflows.result.placeholder.culture"
-- },
-- {
-- "yieldType": "science",
-- "yieldCalculation": "inflows.result.placeholder.science"
-- },
-- {
-- "yieldType": "happiness",
-- "yieldCalculation": "inflows.result.placeholder.happiness"
-- },
-- {
-- "yieldType": "maintenance",
-- "yieldCalculation": "inflows.result.placeholder.maintenance"
-- },
-- {
-- "yieldType": "unitsupport",
-- "yieldCalculation": "inflows.result.placeholder.unitsupport"
-- },
-- {
-- "yieldType": "corruption",
-- "yieldCalculation": "inflows.result.placeholder.corruption"
-- }
-- ]
--}
}

return inflows
10 changes: 5 additions & 5 deletions C7/Lua/texture_configs/civ3/building_icons.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ local building_icons = {
}

function building_icons.small:map_object_to_sprite(building)
if (building:GetType().Name ~= "Building") then
error "Expected a Building object"
if (building:GetType().Name ~= "Building" and building.GetType().Name ~= "Inflow") then
error("Expected a Building or a Inflow object, got " .. type(building:GetType().Name))
end

local y = 1 + 33 * (1 + building.iconRowIndex)
local y = 33 + (SMALL_ICON_HEIGHT + 1) * building.iconRowIndex

return {
path = self.extra_data.path,
Expand All @@ -30,8 +30,8 @@ function building_icons.small:map_object_to_sprite(building)
end

function building_icons.large:map_object_to_sprite(building)
if (building:GetType().Name ~= "Building") then
error "Expected a Building object"
if (building:GetType().Name ~= "Building" and building.GetType().Name ~= "Inflow") then
error("Expected a Building or a Inflow object, got " .. type(building:GetType().Name))
end

local y = 33 + (LARGE_ICON_HEIGHT + 1) * building.iconRowIndex
Expand Down
20 changes: 18 additions & 2 deletions C7/Text/c7-static-map-save-standalone.json
Original file line number Diff line number Diff line change
Expand Up @@ -71317,6 +71317,18 @@
]
}
],
"inflows": [
{
"name": "Wealth",
"iconRowIndex": 29,
"localYield": [
{
"yieldType": "commerce",
"yieldCalculation": "inflows.result.wealth.commerce"
}
]
}
],
"players": [
{
"id": "player-1",
Expand Down Expand Up @@ -73497,9 +73509,10 @@
"scoutUnitType": "Scout",
"maxRankOfWorkableTiles": 2,
"maxRankOfBarbarianCampTiles": 2,
"defaultDealDuration": 20,
"defaultDealDuration": 20,
"treasuryInterestRate": 0.05,
"maxInterest": 50
"maxInterest": 50,
"shieldCostPerGold": 4
},
"techs": [
{
Expand Down Expand Up @@ -73989,6 +74002,9 @@
"y": 276,
"prerequisites": [
"tech-32"
],
"flags": [
"doublesWealthProduction"
]
},
{
Expand Down
20 changes: 18 additions & 2 deletions C7/Text/c7-static-map-save.json
Original file line number Diff line number Diff line change
Expand Up @@ -73890,6 +73890,18 @@
]
}
],
"inflows": [
{
"name": "Wealth",
"iconRowIndex": 29,
"localYield": [
{
"yieldType": "commerce",
"yieldCalculation": "inflows.result.wealth.commerce"
}
]
}
],
"players": [
{
"id": "player-1",
Expand Down Expand Up @@ -76070,9 +76082,10 @@
"scoutUnitType": "Scout",
"maxRankOfWorkableTiles": 2,
"maxRankOfBarbarianCampTiles": 2,
"defaultDealDuration": 20,
"defaultDealDuration": 20,
"treasuryInterestRate": 0.05,
"maxInterest": 50
"maxInterest": 50,
"shieldCostPerGold": 4
},
"techs": [
{
Expand Down Expand Up @@ -76562,6 +76575,9 @@
"y": 276,
"prerequisites": [
"tech-32"
],
"flags": [
"doublesWealthProduction"
]
},
{
Expand Down
16 changes: 13 additions & 3 deletions C7/UIElements/CityScreen/CityScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,8 @@ private void RenderProductionDetails(GameData gameData, City city) {
child.QueueFree();
}

int marginTop = 35;

if (city.itemBeingProduced is UnitPrototype up) {
AnimationManager animationManager = mapView.game.animationController.civ3AnimData.forUnit(up, MapUnit.AnimatedAction.DEFAULT).animationManager;
ShaderMaterial material = TextureLoader.GetShaderMaterialForUnit(city.owner.colorIndex);
Expand All @@ -601,7 +603,7 @@ private void RenderProductionDetails(GameData gameData, City city) {
// Add the base sprite.
Sprite2D baseImageSprite = new();
baseImageSprite.Texture = baseImage;
baseImageSprite.Position = new Vector2(productionButton.TextureNormal.GetWidth() / 2, 35);
baseImageSprite.Position = new Vector2(productionButton.TextureNormal.GetWidth() / 2.0f, marginTop);
productionButton.AddChild(baseImageSprite);

// Add the tint sprite, hooking up the shader.
Expand All @@ -613,7 +615,12 @@ private void RenderProductionDetails(GameData gameData, City city) {
} else if (city.itemBeingProduced is Building b) {
Sprite2D icon = new();
icon.Texture = TextureLoader.Load("building_icons.large", b, useCache: true);
icon.Position = new Vector2(productionButton.TextureNormal.GetWidth() / 2, 35);
icon.Position = new Vector2(productionButton.TextureNormal.GetWidth() / 2.0f, marginTop);
productionButton.AddChild(icon);
} else if (city.itemBeingProduced is Inflow inflow) {
Sprite2D icon = new();
icon.Texture = TextureLoader.Load("building_icons.large", inflow, useCache: true);
icon.Position = new Vector2(productionButton.TextureNormal.GetWidth() / 2.0f, marginTop);
productionButton.AddChild(icon);
}

Expand All @@ -626,6 +633,7 @@ private void RenderProductionDetails(GameData gameData, City city) {
EngineStorage.ReadGameData((GameData gameData) => {
city.SetItemBeingProduced(p);
RenderProductionDetails(gameData, city);
RenderCulture(city);
});
});
}
Expand Down Expand Up @@ -664,11 +672,13 @@ private void RenderShieldBox(int shieldCost, int shieldsInBox) {
child.QueueFree();
}

int itemsPerColumn = (int)Math.Ceiling((float)shieldCost / shieldsInBoxContainer.Columns);
if (itemsPerColumn == 0) return;

int width = (int)shieldsInBoxContainer.GetParent<CenterContainer>().Size.X;
int height = (int)shieldsInBoxContainer.GetParent<CenterContainer>().Size.Y;

shieldsInBoxContainer.Columns = (int)Math.Ceiling(Math.Sqrt(shieldCost));
int itemsPerColumn = (int)Math.Ceiling((float)shieldCost / shieldsInBoxContainer.Columns);
int iconSize = Math.Min(height / itemsPerColumn, width / shieldsInBoxContainer.Columns);

for (int i = 0; i < Math.Min(shieldCost, shieldsInBox); ++i) {
Expand Down
11 changes: 9 additions & 2 deletions C7/UIElements/CityScreen/ProductionMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public override void _Ready() {

// Load the font we'll use.
FontFile font = ResourceLoader.Load<FontFile>("res://Fonts/NotoSans-Regular.ttf", null, ResourceLoader.CacheMode.Ignore);
font.FixedSize = 12;
font.FixedSize = 10;
fontTheme.DefaultFont = font;
}

Expand All @@ -36,6 +36,9 @@ public void AddItems(GameData gameData, City city, Action<IProducible> choosePro
AddChild(tree);
tree.Columns = 2;
tree.Size = new Vector2(203, 360);
tree.SetColumnExpand(0, true);
tree.SetColumnExpand(1, false);
tree.SetColumnCustomMinimumWidth(1, 50);
TradingTree.ConfigureTreeTheme(tree, fontTheme);

TreeItem root = TradingTree.CreateTreeRoot(tree);
Expand All @@ -46,11 +49,15 @@ public void AddItems(GameData gameData, City city, Action<IProducible> choosePro
TreeItem child = tree.CreateItem(root);
string text = $"{option.name}";
if (option is UnitPrototype proto) {
text += $" {proto.attack}.{proto.defense}.{proto.movement}";
string attackDesc = (proto.bombard > 0) ? $"{proto.attack}({proto.bombard})" : proto.attack.ToString();
text += $" {attackDesc}.{proto.defense}.{proto.movement}";
}
child.SetText(0, text);
child.SetText(1, $"{buildTime} turns");
child.SetIcon(0, RightClickChooseProductionMenu.GetProducibleIcon(option));
child.SetCustomMinimumHeight(40);
child.SetAutowrapMode(0, TextServer.AutowrapMode.WordSmart);
tree.SetColumnTitleAlignment(1, HorizontalAlignment.Right);
itemMapping[child] = option;
}

Expand Down
2 changes: 2 additions & 0 deletions C7/UIElements/RightClickMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ public static ImageTexture GetProducibleIcon(IProducible producible) {
return TextureLoader.Load("unit_icons", proto, useCache: true);
} else if (producible is Building b) {
return TextureLoader.Load("building_icons.small", b, useCache: true);
} else if (producible is Inflow inflow) {
return TextureLoader.Load("building_icons.small", inflow, useCache: true);
} else {
return null;
}
Expand Down
10 changes: 10 additions & 0 deletions C7Engine/AI/ChooseProducible.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,21 @@ private static float ScoreProducible(ProducibleStats stats, City city, Player pl
return ScoreUnit(stats, city, player, unit);
} else if (option is Building building) {
return ScoreBuilding(stats, city, player, building);
} else if (option is Inflow inflow) {
return ScoreInflow(stats, city, player, inflow);
} else {
throw new Exception($"Unexpected producible: {option}");
}
}

private static float ScoreInflow(ProducibleStats stats, City city, Player player, Inflow inflow) {
// TODO: score this properly, right now we give a very low score to avoid auto picking this
// I am not sure how civ III does this, although I guess we should consider stuff like
// no more buildings to build, reached unit cap, very bad economy with no other options,
// the WealthNever and WealthOften flags from RACE, etc
return -1000f;
}

private static float ScoreUnit(ProducibleStats stats, City city, Player player, UnitPrototype unit) {
bool isSettler = unit.actions.Contains(UnitAction.BuildCity);
bool isWorker = unit.isWorker;
Expand Down
Loading