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
5 changes: 2 additions & 3 deletions C7/UIElements/Diplomacy/DealScreen.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using C7Engine;
using C7GameData;
using Godot;
using System;
using System.Collections.Generic;
using ConvertCiv3Media;
using static C7GameData.PlayerRelationship;

// At a high level the deal screen has 4 parts; 2 "TradingTree"s per player, and
// 2 "TradeOfferUi"s per player.
Expand Down Expand Up @@ -73,7 +72,7 @@ private void CreateUI() {
EngineStorage.ReadGameData((GameData gD) => {
Player opponentPlayer = gD.players.Find(x => x.id == opponentPlayerId);
Player humanPlayer = gD.players.Find(x => x.id == humanPlayerId);
bool playersAtWar = humanPlayer.playerRelationships[opponentPlayer.id].atWar;
bool playersAtWar = AtWar(humanPlayer, opponentPlayer);
GetParent<Diplomacy>().AddLeaderHeadAndLabel(this, opponentPlayer, fontTheme);

// Figure out which technologies can be traded by each player, if any.
Expand Down
6 changes: 1 addition & 5 deletions C7/UIElements/Popups/DiplomacySelection.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
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 {
Expand All @@ -29,7 +25,7 @@ public override void _Ready() {

int vOffset = 65;
foreach (KeyValuePair<ID, PlayerRelationship> kvp in player.playerRelationships) {
string status = kvp.Value.atWar ? "War" : "Peace";
string status = kvp.Value.AtWar() ? "War" : "Peace";
AddButton($"{allPlayers.Find(x => x.id == kvp.Key).civilization.noun} (at {status})", vOffset, () => {
Node parent = GetParent();
parent.EmitSignal(PopupOverlay.SignalName.HidePopup);
Expand Down
6 changes: 3 additions & 3 deletions C7Engine/AI/ChooseProducible.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using C7Engine.AI.StrategicAI;
using Serilog;
using C7GameData;
using static C7GameData.PlayerRelationship;

namespace C7Engine {
public class ChooseProducible {
Expand Down Expand Up @@ -52,7 +52,7 @@ private static float ScoreProducible(ProducibleStats stats, City city, Player pl
private static float ScoreUnit(ProducibleStats stats, City city, Player player, UnitPrototype unit) {
bool isSettler = unit.actions.Contains(UnitAction.BuildCity);
bool isWorker = unit.isWorker;
bool atWar = PlayerAI.PlayerIsAtWarWithSomeone(player);
bool atWar = IsInAnyWar(player, EngineStorage.gameData.players);
bool cityGuarded = city.location.unitsOnTile.Count(u => u.CanDefendOnLand()) > 0;
bool hasUnescortedSettler = HasUnescortedSettler(city);
var (totalUnits, allowedUnits, unitSupportCost) = player.TotalUnitsAllowedUnitsAndSupportCost();
Expand Down Expand Up @@ -179,7 +179,7 @@ private static float ScoreUnit(ProducibleStats stats, City city, Player player,
}

private static float ScoreBuilding(ProducibleStats stats, City city, Player player, Building building) {
bool atWar = PlayerAI.PlayerIsAtWarWithSomeone(player);
bool atWar = IsInAnyWar(player, EngineStorage.gameData.players);

float score = 0;

Expand Down
19 changes: 3 additions & 16 deletions C7Engine/AI/PlayerAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using C7Engine.AI.UnitAI;
using Serilog;
using System.Diagnostics;
using static C7GameData.PlayerRelationship;

namespace C7Engine {
public class PlayerAI {
Expand Down Expand Up @@ -167,7 +168,7 @@ public static UnitAI GetAIForUnit(MapUnit unit, Player player) {
}

// Special case: we're at war.
if (PlayerIsAtWarWithSomeone(player)) {
if (IsInAnyWar(player, EngineStorage.gameData.players)) {
// Priority 1: ensure we don't have any unguarded cities.
//
// If this is an offensive unit only go defend if there are
Expand Down Expand Up @@ -227,20 +228,6 @@ public static UnitAI GetAIForUnit(MapUnit unit, Player player) {
return new DefenderAI(DefenderAI.MakeAiDataForDefendAtRiskCity(unit, player, minDefenders: int.MaxValue));
}

public static bool PlayerIsAtWarWithSomeone(Player player) {
foreach (KeyValuePair<ID, PlayerRelationship> p in player.playerRelationships) {
if (p.Value.atWar) {
Player other = EngineStorage.gameData.players.Find(x => x.id == p.Key);
if (other.isBarbarians) {
continue;
}

return true;
}
}
return false;
}

private static UnitAI GetCombatAIIfUnitCanAttackNearbyBarbCamp(MapUnit unit, Player player) {
if (unit.unitType.attack <= 0) {
return null;
Expand Down Expand Up @@ -278,7 +265,7 @@ private static async Task AttemptTrading(Player us) {
foreach (Player them in EngineStorage.gameData.players) {
// We can't trade with players we don't know or players we're at
// war with.
if (!us.playerRelationships.ContainsKey(them.id) || us.playerRelationships[them.id].atWar) {
if (!us.playerRelationships.ContainsKey(them.id) || AtWar(us, them)) {
continue;
}

Expand Down
2 changes: 1 addition & 1 deletion C7Engine/AI/StrategicAI/WarPriority.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public override void CalculateWeightAndMetadata(Player player) {
foreach (KeyValuePair<ID, PlayerRelationship> p in player.playerRelationships) {
// TODO: Make sure having seen barbarians doesn't prevent us from
// declaring new wars.
if (p.Value.atWar) {
if (p.Value.AtWar()) {
this.calculatedWeight = 1000;
return;
}
Expand Down
2 changes: 1 addition & 1 deletion C7Engine/AI/UnitAI/CombatAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private static HashSet<ID> GetPlayersAtWarWith(Player player) {
}

foreach (KeyValuePair<ID, PlayerRelationship> p in player.playerRelationships) {
if (p.Value.atWar) {
if (p.Value.AtWar()) {
enemyIds.Add(p.Key);
}
}
Expand Down
21 changes: 12 additions & 9 deletions C7Engine/C7GameData/ImportCiv3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -502,14 +502,15 @@ private void ImportSavLeaders() {
i = 0;
foreach (QueryCiv3.Sav.LEAD leader in savData.Lead) {
List<int> contacts = leader.GetContact();
List<bool> warStatus = leader.GetWarStatuses();
List<int> refuseContactForTurns = leader.GetRefuseContactForTurns();
for (int j = 0; j < contacts.Count; ++j) {
if (contacts[j] > 0) {
QueryCiv3.Sav.LEAD_LEAD relationship = savData.ReputationRelationship[i][j];
save.Players[i].playerRelationships.Add(save.Players[j].id.ToString(), new PlayerRelationship() {
atWar = warStatus[j],
warDeclarationCount = relationship.WarDeclarationCount,
// I don't think there is a way to figure this out for .sav or .biq files
// so by default we set this to 0 for these games
warDeclarationWithRoPActiveCount = 0,
wasSneakAttacked = relationship.WasSneakAttacked == 1,
refuseContactUntilTurn =
refuseContactForTurns[j] > 0 ?
Expand Down Expand Up @@ -749,17 +750,19 @@ private void ImportSavLeaders() {
}

foreach (SavePlayer savePlayer in save.Players) {
int other = 0;
log.Information($"- - - - - - - - - - - - - - - - - - {savePlayer.civilization} - - - - - - - - - - - - - - - - - - ");
foreach (PlayerRelationship pr in savePlayer.playerRelationships.Values) {
other++;
foreach (MultiTurnDeal mtd in pr.multiTurnDeals) {
string against = mtd.dealSubType == DealSubType.MilitaryAlliance || mtd.dealSubType == DealSubType.TradeEmbargo ? $"(against: {save.Players.First(p => p.id == mtd.againstPlayer).civilization}({mtd.againstPlayer}))" : "";
foreach (KeyValuePair<string, PlayerRelationship> pr in savePlayer.playerRelationships) {
if (pr.Value.multiTurnDeals.Count == 0) {
log.Information($"{savePlayer} is at war with {save.Players.First(c => c.id.ToString() == pr.Key)}");
continue;
}
foreach (MultiTurnDeal mtd in pr.Value.multiTurnDeals) {
string against = mtd.dealSubType == DealSubType.MilitaryAlliance || mtd.dealSubType == DealSubType.TradeEmbargo ? $"(against: {save.Players.First(p => p.id == mtd.againstPlayer)})" : "";
string gpt = mtd.dealSubType == DealSubType.GoldPerTurn? $"({mtd.goldPerTurn} gold per turn)" : "";
string rpt = mtd.dealSubType == DealSubType.LuxuryPerTurn || mtd.dealSubType == DealSubType.ResourcePerTurn ? $"({mtd.resourcePerTurn} per turn)" : "";
log.Information($"{savePlayer.civilization}({savePlayer.id}) " +
log.Information($"{savePlayer} " +
$"has {mtd.dealSubType} " +
$"with {save.Players[other].civilization}({save.Players[other].id}) " +
$"with {save.Players.First(c => c.id.ToString() == pr.Key)} " +
$"for another {mtd.TurnsRemaining(save.TurnNumber)} turns {mtd.turnStartDeal}-{mtd.turnEndDeal}" +
$" {against} {gpt} {rpt} {mtd.dealDetails}");
}
Expand Down
98 changes: 45 additions & 53 deletions C7Engine/C7GameData/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
using System.Collections.Generic;
using System.Linq;
using C7Engine.AI.StrategicAI;
using C7GameData.Save;
using C7Engine;
using Serilog;
using static C7GameData.EraUtils;
using static C7GameData.MultiTurnDeal;
using static C7GameData.PlayerRelationship;

namespace C7GameData {

Expand Down Expand Up @@ -219,76 +220,70 @@ public bool HasExploredTile(Tile tile) {
}

public bool IsAtPeaceWith(Player other) {
// Evaluate this before checking for barbarians so barbarians don't
// attack themselves.
if (other == this) {
return true;
}

if (other.isBarbarians || this.isBarbarians) {
return false;
}

if (playerRelationships.ContainsKey(other.id)) {
return !playerRelationships[other.id].atWar;
}
return true;
return !AtWar(this, other);
}

public void EnsureRelationshipExists(Player other) {
if (isBarbarians || other.isBarbarians)
if (this.isBarbarians || other.isBarbarians || this.id == other.id || this.defeated || other.defeated)
return;

if (!playerRelationships.ContainsKey(other.id)) {
playerRelationships.Add(other.id, new PlayerRelationship());
}
if (!other.playerRelationships.ContainsKey(this.id)) {
other.playerRelationships.Add(this.id, new PlayerRelationship());
// If the mutual relationship is not established it means that the 2 civs
// were not aware of each other (aka they just met), and therefore cannot be at war.
// Initialize the relationship and establish peace automatically between them.
if (!this.playerRelationships.ContainsKey(other.id) || !other.playerRelationships.ContainsKey(this.id)) {
this.playerRelationships.TryAdd(other.id, new PlayerRelationship());
other.playerRelationships.TryAdd(this.id, new PlayerRelationship());
RegisterMultiTurnDeal(this, other, DEFAULT_PEACE);

log.Information($"Established first contact and relationship between players {this} and {other}");
}
}

public void DeclareWarOn(Player other, int currentTurn) {
EnsureRelationshipExists(other);

playerRelationships[other.id].atWar = true;

PlayerRelationship pr = other.playerRelationships[this.id];
pr.atWar = true;
pr.warDeclarationCount += 1;

// Check to see if there was a sneak attack - we consider a sneak
// attack any attack where the player's units were inside the
// borders of the civ they're declaring war on.
foreach (Tile t in other.tileKnowledge.knownTiles) {
if (t.owningCity == null || t.owningCity.owner != other) {
continue;
}
if (t.unitsOnTile.Count == 0) {
continue;
}
if (t.unitsOnTile[0].owner == this) {
pr.wasSneakAttacked = true;
break;
}
}

bool isSneakAttack = IsASneakAttackOn(other);

// TODO: take into account broken right of passage, or other deals, etc?
// Perhaps we need a dedicated method to calculate this.
//
// Refuse contact from the aggressor civ until enough turns have
// elapsed. The exact civ3 mechanism here is unknown, so we just
// pick some reasonable random number. To penalize sneak attacks we
// use a higher upper bound.
pr.refuseContactUntilTurn = currentTurn + new Random().Next(5, pr.wasSneakAttacked ? 16 : 12);
int refuseContactUntilTurn = currentTurn + new Random().Next(5, isSneakAttack ? 16 : 12);

DeclareWar(this, other, isSneakAttack, refuseContactUntilTurn);

// Whenever war is declared, re-evaluate priorities.
turnsUntilPriorityReevaluation = 0;
other.turnsUntilPriorityReevaluation = 0;
}

private bool IsASneakAttackOn(Player other) {
foreach (Tile location in other.tileKnowledge.knownTiles) {
if (location.owningCity == null || location.owningCity.owner != other) {
continue;
}
if (location.unitsOnTile.Count == 0) {
continue;
}
if (location.unitsOnTile[0].owner == this) {
return true;
}
}

return false;
}

public bool WillAcceptCommunicationFrom(Player other, int currentTurn) {
EnsureRelationshipExists(other);

PlayerRelationship pr = playerRelationships[other.id];
if (!pr.atWar) {
if (!AtWar(this, other)) {
return true;
}
return currentTurn >= pr.refuseContactUntilTurn;
Expand All @@ -299,12 +294,6 @@ public bool SitsOutFirstTurn() {
return isBarbarians;
}

// TODO : This is a placeholder so that we can factor this in when calculating movement costs
// since multiturn deals are not yet implemented
public bool HasRightOfPassageAgreementWith(Player other) {
return false;
}

public static bool CanMoveFreely(Player player, Tile sourceTile, Tile targetTile) {
if (!player.HasExploredTile(targetTile))
return true;
Expand All @@ -322,7 +311,12 @@ public static bool CanMoveFreely(Player player, Tile sourceTile, Tile targetTile
// All the other cases are either from or to "enemy" tiles
// and without a RoP agreement the cost is never reduced.
// check other && RoP
if (player.HasRightOfPassageAgreementWith(targetTileOwner)) {
if (sourceTileOwner != null && sourceTileOwner != player
&& HaveActiveRightOfPassage(player, sourceTileOwner)) {
return true;
}
if (targetTileOwner != null && targetTileOwner != player
&& HaveActiveRightOfPassage(player, targetTileOwner)) {
return true;
}

Expand All @@ -349,7 +343,7 @@ public int RemainingCities() {

public override string ToString() {
if (civilization != null)
return civilization.cityNames.First();
return $"{civilization.name} [{this.id}]";
return "";
}

Expand Down Expand Up @@ -434,9 +428,7 @@ public void ExecuteDeal(GameData gameData, Player other, TradeOffer theirOffer,
log.Information($" {this} gives {ourOffer.ToString()}, worth {ourOffer.GoldEquivalentFor(gameData, other)} gold");
log.Information($" {other} gives {theirOffer.ToString()}, worth {theirOffer.GoldEquivalentFor(gameData, this)} gold)");
if (theirOffer.partOfPeaceTreaty) {
log.Information($" {this} is now at peace with {other}");
this.playerRelationships[other.id].atWar = false;
other.playerRelationships[this.id].atWar = false;
SignPeaceAfterWar(this, other, gameData);
}

if (ourOffer.gold.HasValue) {
Expand Down
Loading