Skip to content
Merged
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
15 changes: 5 additions & 10 deletions C7/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,10 +278,8 @@ public void HandleEngineMessage(MessageToUI msg) {
turnsLeftToFastForward = 0;
break;
case MsgShowTemporaryPopup mSTP:
TemporaryPopup popup = new(mSTP.message, 1);
popup.SetPosition(mapView.screenLocationOfTile(mSTP.location, true) + new Vector2(0, -64));
AddChild(popup);
popup.ShowPopup();
Vector2 pos = mapView.screenLocationOfTile(mSTP.location, true);
TemporaryPopup.Show(this, mSTP.message, pos);
break;
case MsgUnitMoved mUUAAB:
EmitSignal(SignalName.UnitMoved, new ParameterWrapper<MapUnit>(mUUAAB.Unit));
Expand Down Expand Up @@ -520,10 +518,7 @@ private void HandleUnitSelection(InputEventMouseButton eventMouseButton) {

bool canMove = unitSelector.SetSelectedUnit(to_select);
if (!canMove) {
TemporaryPopup popup = new("This unit has already moved.", 1);
popup.SetPosition(eventMouseButton.Position + new Vector2(0, -64));
AddChild(popup);
popup.ShowPopup();
TemporaryPopup.Show(this, "This unit has already moved.", eventMouseButton.Position);
}
}

Expand Down Expand Up @@ -937,7 +932,7 @@ private GotoInfo GetGotoInfo(Vector2 mousePos) {
// declare war with the move) mark the path.
if (result.moveCost == -1
&& unit.location.distanceTo(tile) == 1
&& unit.CanEnterTile(tile, allowCombat: true, allowWarDeclaration: true)) {
&& unit.CanEnterTile(tile, TileProbe.DeclareWarProbe())) {
Queue<Tile> pathQueue = new();
pathQueue.Enqueue(tile);

Expand All @@ -949,7 +944,7 @@ private GotoInfo GetGotoInfo(Vector2 mousePos) {

// If we couldn't enter this tile without a war declaration,
// record which civ we need to declare war on.
if (!unit.CanEnterTile(tile, allowCombat: true, allowWarDeclaration: false)) {
if (!unit.CanEnterTile(tile, TileProbe.CombatProbe())) {
if (tile.cityAtTile != null) {
result.requiresWarDeclarationOnPlayer = tile.cityAtTile.owner;
} else {
Expand Down
2 changes: 1 addition & 1 deletion C7/Map/GotoLayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til
DrawStaticGoToCursor(looseView, tileCenter, 0, true);
}

if (gotoInfo.path != null && unit.CanEnterTile(gotoInfo.destinationTile, true, true)) {
if (gotoInfo.path != null && unit.CanEnterTile(gotoInfo.destinationTile, TileProbe.GotoProbe())) {
List<Tile> tiles = new List<Tile>();
tiles.Add(unitOriginTile);
tiles.AddRange(gotoInfo.path.path);
Expand Down
2 changes: 1 addition & 1 deletion C7/UIElements/Advisors/TechBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ private void ShowTooltip() {
smallFontTheme.SetColor("font_color", "Label", new Color(0.71f, 0.35f, 0.13f));

var customTheme = new Theme();
customTheme.SetStylebox("panel", "TooltipPanel", TemporaryPopup.PopupTechStyleBox());
customTheme.SetStylebox("panel", "TooltipPanel", TemporaryPopup.TooltipStyleBox());
customTheme.SetColor("font_color", "TooltipLabel", Colors.Black);
customTheme.SetFontSize("font_size", "TooltipLabel", 12);
this.Theme = customTheme;
Expand Down
25 changes: 24 additions & 1 deletion C7/UIElements/Popups/TemporaryPopup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public TemporaryPopup(string text, float durationInSec) {
}

// A stylebox that works well for temporary popups or tooltips in game.
public static StyleBoxFlat PopupStyleBox() {
public static StyleBoxFlat TooltipStyleBox() {
StyleBoxFlat styleBox = new();
styleBox.ContentMarginLeft = 5;
styleBox.ContentMarginRight = 5;
Expand All @@ -24,6 +24,16 @@ public static StyleBoxFlat PopupStyleBox() {
return styleBox;
}

public static StyleBoxFlat PopupStyleBox() {
StyleBoxFlat styleBox = new();
styleBox.ContentMarginLeft = 5;
styleBox.ContentMarginRight = 5;
styleBox.ContentMarginTop = 2;
styleBox.ContentMarginBottom = 2;
styleBox.BgColor = Color.FromHtml("1C1C1C99");
return styleBox;
}

public static StyleBoxFlat PopupTechStyleBox() {
StyleBoxFlat styleBox = new();
styleBox.ContentMarginLeft = 5;
Expand All @@ -39,6 +49,19 @@ public static StyleBoxFlat PopupTechStyleBox() {
return styleBox;
}

public static void Show(Node parent, string msg, Vector2 rootPosition) {
TemporaryPopup popup = new(msg, 2);
popup.SetPosition(rootPosition);
parent.AddChild(popup);

// Center the text, adjust it slightly above the target (tile) position.
// Note: Label size isn't defined until node has been added to the tree.
Vector2 offset = new(-(popup.Size.X / 2), -64);
popup.SetPosition(rootPosition + offset);

popup.ShowPopup();
}

public async void ShowPopup() {
// Wait until we hit our duration then destroy ourself.
await Task.Delay(durationInMillis);
Expand Down
5 changes: 1 addition & 4 deletions C7/UIElements/RightClickMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,7 @@ public override void _Input(InputEvent @event) {
}

public void ShowCannotMovePopup() {
TemporaryPopup popup = new("This unit has already moved.", 1);
popup.SetPosition(position + new Vector2(0, -64));
GetParent().AddChild(popup);
popup.ShowPopup();
TemporaryPopup.Show(GetParent(), "This unit has already moved.", position);
}
}

Expand Down
2 changes: 1 addition & 1 deletion C7/UIElements/UnitButtons/UnitButtons.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ private void AddNewButton(HBoxContainer row, string action) {
button.TooltipText = tooltipText;

var customTheme = new Theme();
customTheme.SetStylebox("panel", "TooltipPanel", TemporaryPopup.PopupStyleBox());
customTheme.SetStylebox("panel", "TooltipPanel", TemporaryPopup.TooltipStyleBox());
customTheme.SetColor("font_color", "TooltipLabel", Colors.White);
button.Theme = customTheme;
}
Expand Down
4 changes: 2 additions & 2 deletions C7Engine/AI/Pathing/PathingAlgorithmChooser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ public static PathingAlgorithm GetAlgorithm(MapUnit unit) {
// to allow pathing to attack. We don't want to try and path
// through opponents on the way to our destination though,
// as we can get stuck.
bool allowCombat = neighbor == destination;
return unit.CanEnterTile(neighbor, allowCombat);
var probe = neighbor == destination ? TileProbe.PathCombatProbe() : TileProbe.PathProbe();
return unit.CanEnterTile(neighbor, probe);
}
);
}
Expand Down
2 changes: 1 addition & 1 deletion C7Engine/AI/PlayerAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ private static UnitAI GetCombatAIIfUnitCanAttackNearbyBarbCamp(MapUnit unit, Pla
}

List<Tile> reachableBarbCampsTiles = player.tileKnowledge.AllKnownTiles()
.Where(t => unit.CanEnterTile(t, true) && t.hasBarbarianCamp).ToList();
.Where(t => unit.CanEnterTile(t, TileProbe.AiCombatProbe()) && t.hasBarbarianCamp).ToList();

Tile closestBarbCamp = Tile.NONE;
int closestBarbDistance = int.MaxValue;
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 @@ -81,7 +81,7 @@ public C7GameData.UnitAI.MoveResult PlayTurnImpl(Player player, MapUnit unit) {
}

// Move along the path, initiating combat if we reach our target.
return this.TryToMoveAlongPath(unit, ref data.path, allowCombat: true);
return this.TryToMoveAlongPath(unit, ref data.path, TileProbe.AiCombatProbe());
}

public string SummarizePlan() {
Expand Down
2 changes: 1 addition & 1 deletion C7Engine/AI/UnitAI/DefenderAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ C7GameData.UnitAI.MoveResult C7GameData.UnitAI.PlayTurnImpl(Player player, MapUn
return C7GameData.UnitAI.Result.Done;
} else {
log.Debug("Moving defender towards " + data.destination);
return this.TryToMoveAlongPath(unit, ref data.pathToDestination, allowCombat: false);
return this.TryToMoveAlongPath(unit, ref data.pathToDestination, TileProbe.AiMoveProbe());
}
}

Expand Down
2 changes: 1 addition & 1 deletion C7Engine/AI/UnitAI/EscortAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ UnitAI.MoveResult UnitAI.PlayTurnImpl(Player player, MapUnit unit) {

// Move to the unit we're escorting.
TilePath path = PathingAlgorithmChooser.GetAlgorithm(unit).PathFrom(unit.location, data.unitToEscort.location, unit);
result = this.TryToMoveAlongPath(unit, ref path, allowCombat: false);
result = this.TryToMoveAlongPath(unit, ref path, TileProbe.AiMoveProbe());
if (result != UnitAI.Result.InProgress) {
return result;
}
Expand Down
2 changes: 1 addition & 1 deletion C7Engine/AI/UnitAI/ExplorerAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ UnitAI.MoveResult UnitAI.PlayTurnImpl(Player player, MapUnit unit) {
return UnitAI.Result.Done;
}

return this.TryToMoveAlongPath(unit, ref data.pathToDestination, allowCombat: false);
return this.TryToMoveAlongPath(unit, ref data.pathToDestination, TileProbe.AiMoveProbe());
}

public string SummarizePlan() {
Expand Down
2 changes: 1 addition & 1 deletion C7Engine/AI/UnitAI/SettlerAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ C7GameData.UnitAI.MoveResult UnitAI.PlayTurnImpl(Player player, MapUnit unit) {
unit.movementPoints.onConsumeAll();
return C7GameData.UnitAI.Result.InProgress;
} else {
return this.TryToMoveAlongPath(unit, ref data.pathToDestination, allowCombat: false);
return this.TryToMoveAlongPath(unit, ref data.pathToDestination, TileProbe.AiMoveProbe());
}
break;
case SettlerAIData.SettlerGoal.JOIN_CITY:
Expand Down
2 changes: 1 addition & 1 deletion C7Engine/AI/UnitAI/WorkerAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ UnitAI.MoveResult UnitAI.PlayTurnImpl(Player player, MapUnit unit) {
return PerformWorkerMove(unit, improvement);
}

return this.TryToMoveAlongPath(unit, ref data.pathToDestination, allowCombat: false);
return this.TryToMoveAlongPath(unit, ref data.pathToDestination, TileProbe.AiMoveProbe());
}

private static Terraform? GetTileImprovement(Tile t, MapUnit unit) {
Expand Down
6 changes: 3 additions & 3 deletions C7Engine/AI/UnitAiExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ public static class UnitAiExtension {
// Attempts to move the supplied unit along the given path.
//
// `path` is a ref so that the path can be recalculated if necessary.
public static MoveResult TryToMoveAlongPath(this UnitAI unitAi, MapUnit unit, ref TilePath path, bool allowCombat) {
public static MoveResult TryToMoveAlongPath(this UnitAI unitAi, MapUnit unit, ref TilePath path, TileProbe probe) {
if (!unit.movementPoints.canMove) {
return UnitAI.Result.InProgress;
}
Tile nextTile = path.Next();
if (nextTile == Tile.NONE || !unit.CanEnterTile(nextTile, allowCombat)) {
if (nextTile == Tile.NONE || !unit.CanEnterTile(nextTile, probe)) {
log.Information($"Attempting to repath {unit} from {unit.location} to {path.destination}");
// Attempt to repath. If we succeed, return inprogress so we get
// called again.
path = PathingAlgorithmChooser.GetAlgorithm(unit).PathFrom(unit.location, path.destination, unit);
if ((path?.PathLength() ?? -1) == -1 || path.PeekNext() == Tile.NONE || !unit.CanEnterTile(path.PeekNext(), allowCombat)) {
if ((path?.PathLength() ?? -1) == -1 || path.PeekNext() == Tile.NONE || !unit.CanEnterTile(path.PeekNext(), probe)) {
return UnitAI.Result.Error;
}

Expand Down
86 changes: 74 additions & 12 deletions C7Engine/C7GameData/MapUnit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ public async Task<CombatResult> fight(MapUnit defender) {
// TODO: Defender retreat behavior requires some more work. There's an issue for it here:
// https://github.com/C7-Game/Prototype/issues/274
Tile retreatDestination = defender.location.neighbors[attacker.facingDirection];
if ((retreatDestination != Tile.NONE) && defender.CanEnterTile(retreatDestination, false)) {
if ((retreatDestination != Tile.NONE) && defender.CanEnterTile(retreatDestination, TileProbe.PathProbe())) {
await defender.move(attacker.facingDirection, true);
result = CombatResult.DefenderRetreated;
break;
Expand Down Expand Up @@ -493,13 +493,8 @@ public void OnEnterTile(Tile tile) {
}
}

public bool CanEnterTile(Tile tile, bool allowCombat) {
return CanEnterTile(tile, allowCombat, allowWarDeclaration: false);
}

// Like above, but allows specifying that we want to handle the case
// where a war declaration could be made before the move.
public bool CanEnterTile(Tile tile, bool allowCombat, bool allowWarDeclaration) {
// Generalized check to see whether a given tile is accessible to the unit in a given context.
public bool CanEnterTile(Tile tile, TileProbe probe) {
if (this.owner.isHuman && !this.owner.HasExploredTile(tile))
return true;

Expand All @@ -521,10 +516,27 @@ public bool CanEnterTile(Tile tile, bool allowCombat, bool allowWarDeclaration)
if (this.IsLandUnit() && !tile.IsLand())
return false;

if (!HasRank()) {
if (HasHostileCity(tile, owner)) {
if (probe.RaiseNotice) {
new MsgShowTemporaryPopup($"Only combat units can capture cities and improvements.", location).send();
return false;
}
return true;
}
if (HasHostileUnits(tile, owner)) {
if (probe.RaiseNotice) {
new MsgShowTemporaryPopup($"Non-combat units may not attack.", location).send();
return false;
}
return true;
}
}

// If we allow declaring war on this move, then it doesn't matter if
// there are units belonging to another player on the tile.
// TODO: unbreakable alliances
if (allowWarDeclaration) {
if (probe.AllowWarDeclaration) {
return true;
}

Expand All @@ -541,7 +553,7 @@ public bool CanEnterTile(Tile tile, bool allowCombat, bool allowWarDeclaration)
foreach (MapUnit other in tile.unitsOnTile) {
if (other.owner != owner) {
if (!other.owner.IsAtPeaceWith(owner))
return allowCombat && unitType.attack > 0;
return probe.AllowCombat && unitType.attack > 0;
return false;
}
}
Expand All @@ -550,13 +562,25 @@ public bool CanEnterTile(Tile tile, bool allowCombat, bool allowWarDeclaration)
// Check for cities belonging to other civs.
if (tile.cityAtTile != null && tile.cityAtTile.owner != owner) {
if (!this.owner.isHuman || this.owner.tileKnowledge.isActiveTile(tile))
return allowCombat;
return probe.AllowCombat;
return false;
}

return true;
}

private static bool HasHostileUnits(Tile tile, Player player) {
foreach (MapUnit other in tile.unitsOnTile) {
if (player != other.owner && !player.IsAtPeaceWith(other.owner))
return true;
}
return false;
}

private static bool HasHostileCity(Tile tile, Player player) {
return tile.HasCity && !player.IsAtPeaceWith(tile.cityAtTile.owner);
}

/// <summary>
/// Moves the unit in the given direction
/// </summary>
Expand All @@ -568,7 +592,7 @@ public bool CanEnterTile(Tile tile, bool allowCombat, bool allowWarDeclaration)
public async Task<bool> move(TileDirection dir, bool wait = false) {
(int dx, int dy) = dir.toCoordDiff();
Tile newLoc = EngineStorage.gameData.map.tileAt(dx + location.XCoordinate, dy + location.YCoordinate);
if ((newLoc != Tile.NONE) && CanEnterTile(newLoc, true) && (movementPoints.canMove)) {
if ((newLoc != Tile.NONE) && CanEnterTile(newLoc, TileProbe.MoveProbe()) && (movementPoints.canMove)) {
facingDirection = dir;
wake();

Expand Down Expand Up @@ -857,4 +881,42 @@ public List<UnitAction> GetAvailableActions() {
return result;
}
}

public struct TileProbe {
public bool AllowCombat { get; init; }
public bool AllowWarDeclaration { get; init; }
public bool RaiseNotice { get; init; }

public static TileProbe AiMoveProbe() {
return new TileProbe() { AllowCombat = false, AllowWarDeclaration = false, RaiseNotice = false };
}

public static TileProbe AiCombatProbe() {
return new TileProbe() { AllowCombat = true, AllowWarDeclaration = false, RaiseNotice = false };
}

public static TileProbe PathProbe() {
return new TileProbe() { AllowCombat = false, RaiseNotice = false };
}

public static TileProbe PathCombatProbe() {
return new TileProbe() { AllowCombat = true, RaiseNotice = false };
}

public static TileProbe MoveProbe() {
return new TileProbe() { AllowCombat = true, RaiseNotice = true };
}

public static TileProbe CombatProbe() {
return new TileProbe() { AllowCombat = true, AllowWarDeclaration = false, RaiseNotice = false };
}

public static TileProbe GotoProbe() {
return new TileProbe() { AllowCombat = true, AllowWarDeclaration = true, RaiseNotice = false };
}

public static TileProbe DeclareWarProbe() {
return new TileProbe() { AllowCombat = true, AllowWarDeclaration = true, RaiseNotice = false };
}
}
}
6 changes: 5 additions & 1 deletion C7Engine/C7GameData/Tile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,11 @@ public void MaybeAwardForestClearingShields() {
int shieldsAwarded = EngineStorage.gameData.rules.ForestValueInShields;
c.shieldsStored += shieldsAwarded;
c.shieldsStored = Math.Min(c.shieldsStored, c.owner.ShieldCost(c.itemBeingProduced));
new MsgShowTemporaryPopup($"{shieldsAwarded} shields awarded for clearing forests", other).send();

if (c.owner.isHuman) {
new MsgShowTemporaryPopup($"{shieldsAwarded} shields awarded for clearing forests", other).send();
}

return;
}
}
Expand Down