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
43 changes: 42 additions & 1 deletion C7/Animations/AnimationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ public static string AnimationKey(MapUnit unit, MapUnit.AnimatedAction action, T
return AnimationKey(BaseAnimationKey(unit, action), direction);
}

public static string AnimationKey(AnimatedEffect effect, MapUnit.AnimatedAction action) {
return $"{effect.ToString()}_{action.ToString()}";
}

public static readonly Dictionary<string, ImageTexture> AnimationThumbnails = new();
public static readonly Dictionary<string, ImageTexture> AnimationTintThumbnails = new();

Expand Down Expand Up @@ -112,6 +116,10 @@ public IniData getUnitINIData(string unitTypeName) {
return getINIData(string.Format("Art/Units/{0}/{0}.INI", unitTypeName));
}

public string GetFlicFilePath(string rootPath, IniData iniData, MapUnit.AnimatedAction action) {
return rootPath + "/" + getFlicFileName(iniData, action);
}

public string getUnitFlicFilepath(MapUnit unit, MapUnit.AnimatedAction action) {
string directory = string.Format("Art/Units/{0}", unit.GetArtName());
IniData ini = getUnitINIData(unit.GetArtName());
Expand Down Expand Up @@ -169,6 +177,23 @@ public static void loadFlicAnimation(string path, string name, ref SpriteFrames
}
}

public static void loadFlicEffectAnimation(string path, string name, ref SpriteFrames frames, ref SpriteFrames tint) {
Flic flic = Util.LoadFlic(path);

for (int row = 0; row < flic.Images.GetLength(0); row++) {
string animationName = name;
frames.AddAnimation(animationName);
tint.AddAnimation(animationName);

for (int col = 0; col < flic.Images.GetLength(1); col++) {
byte[] frame = flic.Images[row,col];
(ImageTexture bl, ImageTexture tl) = Util.LoadTextureFromFlicData(frame, flic.Palette, flic.Width, flic.Height);
frames.AddFrame(animationName, bl, 0.5f); // TODO: frame duration is controlled by .ini
tint.AddFrame(animationName, tl, 0.5f); // TODO: frame duration is controlled by .ini
}
}
}

public bool LoadAnimation(MapUnit unit, MapUnit.AnimatedAction action) {
string name = BaseAnimationKey(unit.GetArtName(), action);
string testName = AnimationKey(name, TileDirection.NORTH);
Expand All @@ -180,11 +205,20 @@ public bool LoadAnimation(MapUnit unit, MapUnit.AnimatedAction action) {
return true;
}

public bool LoadAnimation(AnimatedEffect effect, MapUnit.AnimatedAction action, string flicFilePath) {
string name = AnimationKey(effect, action);
if (spriteFrames.HasAnimation(name) && tintFrames.HasAnimation(name)) {
return false;
}
loadFlicEffectAnimation(flicFilePath, name, ref this.spriteFrames, ref this.tintFrames);
return true;
}

private Dictionary<string, Util.FlicSheet> flicSheets = new Dictionary<string, Util.FlicSheet>();

public Util.FlicSheet getFlicSheet(string rootPath, IniData iniData, MapUnit.AnimatedAction action) {
Util.FlicSheet tr;
string pathKey = rootPath + "/" + getFlicFileName(iniData, action);
string pathKey = GetFlicFilePath(rootPath, iniData, action);
if (!flicSheets.TryGetValue(pathKey, out tr)) {
(tr, _) = Util.loadFlicSheet(pathKey);
flicSheets.Add(pathKey, tr);
Expand Down Expand Up @@ -229,6 +263,7 @@ public partial class C7Animation {
public string folderPath { get; private set; } // For example "Art/Units/Warrior" or "Art/Animations/Trajectory"
public string iniFileName { get; private set; }
private MapUnit unit;
public AnimatedEffect effect;
public MapUnit.AnimatedAction action { get; private set; }

public C7Animation(AnimationManager civ3AnimData, MapUnit unit, MapUnit.AnimatedAction action) {
Expand Down Expand Up @@ -264,6 +299,7 @@ public C7Animation(AnimationManager civ3AnimData, AnimatedEffect effect) {
this.folderPath = "Art/Animations/" + effectCategories[effect];
this.iniFileName = effectINIFileNames[effect];
this.action = MapUnit.AnimatedAction.DEATH;
this.effect = effect;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not a comment on this particular PR, but since you had some friction with it now, does this whole system look ok to you (AnimationManager, C7Animation, AnimationTracker, etc)?
Let me know if you have any comments, I am trying to refactor this on the side, make it more versatile and extendible

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Current effect animation feels very glued-on on top of the unit animation scheme. Rethinking/refactoring the animation system as a whole makes sense.

The bomber plane bombardment animation might be a good starting point as it's a complex sequence. If you can support that, then everything else is easy.

I kinda like the approach of having the animation scheduling and progress-dependent rendering be separate parts. It's a bit much initially, but I can see how it can be really flexible. Maybe we need a central scheduling scheme and then a few different renderers. Could have simpler tile-based ones for unit and effect, and then a more complex renderer that can handle moving the render target on screen.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we need to move away from tile-based animations altogether, and move to X/Y (screen?) coordinate animations. Tile based anything seems to be very restrictive as it is right now.
Stuff that are not doable with the current setup:

  1. Move unit to a tile, without hiding the units already on said tile, untill the unit has actually finished moving.
  2. Move half a tile to initiate a fight
  3. Move half a tile when retreating (basiaclly the same as above, on the opposite direction)
  4. Probably all Bomber-like animations

}

public IniData getINIData() {
Expand All @@ -278,6 +314,11 @@ public void loadSpriteAnimation() {
this.animationManager.LoadAnimation(this.unit, this.action);
}

public void loadEffectAnimation() {
var path = animationManager.GetFlicFilePath(folderPath, getINIData(), action);
this.animationManager.LoadAnimation(this.effect, this.action, path);
}

public void playSound() {
animationManager.playSound(folderPath, getINIData(), action);
}
Expand Down
97 changes: 86 additions & 11 deletions C7/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,32 @@ public class GotoInfo {
};
public GotoInfo gotoInfo = null;

public class BombardInfo {
public MapUnit bombardingUnit;
public Tile mouseTile;

public BombardInfo(MapUnit bombardingUnit) {
this.bombardingUnit = bombardingUnit;
}

public bool requiresWarDeclaration(Tile tile, out Player player) {
player = null;

var bombarder = bombardingUnit.owner;
var foreignUnits = tile.unitsOnTile.Where(x => x.owner != bombarder).ToList();
if (!foreignUnits.Any())
return false;

var targetPlayers = foreignUnits.Select(x => x.owner).Distinct();
var friendly= targetPlayers.Where(p => bombarder.IsAtPeaceWith(p)).ToList();
player = friendly.FirstOrDefault();
return friendly.Any();

// TODO: handle complex scenarios arising from multiple civs co-located on tile
}
};
public BombardInfo bombardInfo = null;

public class TileInfo {
public Tile targetTile;
public HashSet<Tile> coveredTiles = [];
Expand Down Expand Up @@ -525,6 +551,12 @@ private void OnSingleLeftMouseButtonClick(InputEventMouseButton eventMouseButton
if (gotoInfo != null) {
HandleGotoClick(gotoInfo);
setGotoMode(false);
} else if (bombardInfo != null) {
Tile tile = PositionToTile(eventMouseButton.Position);
if (bombardInfo.bombardingUnit.canBombardTile(tile)) {
HandleBombardClick(bombardInfo, tile);
setBombard(null);
}
} else {
// Select unit on tile at mouse location
HandleUnitSelection(eventMouseButton);
Expand Down Expand Up @@ -581,6 +613,8 @@ private void HandleRightClickOnTile(Tile tile, InputEventMouseButton eventMouseB
else if (!shiftDown && tile.cityAtTile?.owner == controller)
// There are no units, but this is the player's city.
new RightClickCityMenu(this, tile).Open(eventMouseButton.Position);
else if (bombardInfo != null)
setBombard(null);
else
ShowTileInfo(tile);

Expand Down Expand Up @@ -636,6 +670,8 @@ private void HandleMouseMotionInput(InputEventMouseMotion eventMouseMotion) {
OldPosition = eventMouseMotion.Position;
} else if (gotoInfo != null) {
gotoInfo = GetGotoInfo(eventMouseMotion.Position);
} else if (bombardInfo != null) {
bombardInfo.mouseTile = PositionToTile(eventMouseMotion.Position);
}
}

Expand Down Expand Up @@ -793,6 +829,11 @@ private void ProcessAction(string currentAction) {
return;
}

if (currentAction == C7Action.Escape && bombardInfo != null) {
setBombard(null);
return;
}

// never poll for actions if UI elements are visible
if (popupOverlay.Visible || cityScreen.Visible || advisor.Visible || diplomacy.Visible || palaceScene.Visible) {
return;
Expand Down Expand Up @@ -909,6 +950,15 @@ private void ProcessAction(string currentAction) {
});
}

if (currentAction == C7Action.UnitBombard) {
if (CurrentlySelectedUnit != MapUnit.NONE && (CurrentlySelectedUnit?.canBombard() ?? false)) {
EngineStorage.ReadGameData((GameData gameData) => {
MapUnit currentUnit = gameData.GetUnit(CurrentlySelectedUnit.id);
setBombard(currentUnit);
});
}
}

Terraform terraform = C7Action.ToTerraform(currentAction);

if (CurrentlySelectedUnit == MapUnit.NONE || CurrentlySelectedUnit == null
Expand Down Expand Up @@ -939,32 +989,41 @@ private void setGotoMode(bool isOn) {
}
}

private void setBombard(MapUnit bombardingUnit) {
bombardInfo = bombardingUnit == null ? null : new BombardInfo(bombardingUnit);
if (bombardingUnit == null)
Input.SetCustomMouseCursor(null);
}

private void HandleGotoClick(GotoInfo info) {
if (info == null || info.moveCost == -1) {
return;
}

EngineStorage.ReadGameData((GameData gameData) => {
int currentTurn = gameData.turn;

// If this move would require declaring war, display a popup that checks
// if the player really wants to declare war. If they do, declare the
// war for them, clear out the player, and call this method again.
if (info.requiresWarDeclarationOnPlayer != null) {
GotoInfo stashed = info;
popupOverlay.ShowPopup(new WarConfirmation(stashed.requiresWarDeclarationOnPlayer,
() => {
controller.DeclareWarOn(info.requiresWarDeclarationOnPlayer, currentTurn);
stashed.requiresWarDeclarationOnPlayer = null;
HandleGotoClick(stashed);
}), PopupOverlay.PopupCategory.Advisor);
return;
MaybeDeclareWar(stashed.requiresWarDeclarationOnPlayer, gameData.turn, () => {
stashed.requiresWarDeclarationOnPlayer = null;
HandleGotoClick(stashed);
});
} else {
new MsgSetUnitPath(CurrentlySelectedUnit.id, info.path).send();
}

new MsgSetUnitPath(CurrentlySelectedUnit.id, info.path).send();
});
}

private void MaybeDeclareWar(Player player, int currentTurn, Action callback) {
popupOverlay.ShowPopup(new WarConfirmation(player,
() => {
controller.DeclareWarOn(player, currentTurn);
callback();
}), PopupOverlay.PopupCategory.Advisor);
}

private GotoInfo GetGotoInfo(Vector2 mousePos) {
GotoInfo result = new();

Expand Down Expand Up @@ -1018,6 +1077,22 @@ private GotoInfo GetGotoInfo(Vector2 mousePos) {
return result;
}

private void HandleBombardClick(BombardInfo info, Tile tile) {
if (info == null || tile == null) {
return;
}

EngineStorage.ReadGameData((GameData gameData) => {
if (info.requiresWarDeclaration(tile, out var player)) {
MaybeDeclareWar(player, gameData.turn, () => {
new MsgBombard(CurrentlySelectedUnit.id, tile).send();
});
} else {
new MsgBombard(CurrentlySelectedUnit.id, tile).send();
}
});
}

/**
* User quit. We *may* want to do some things here like make a back-up save, or call the server and let it know we're bailing (esp. in MP).
**/
Expand Down
Loading
Loading