318 lines
No EOL
15 KiB
C#
318 lines
No EOL
15 KiB
C#
using CityGame.Classes.Rendering;
|
|
using CityGame.Classes.World;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using OrpticonGameHelper;
|
|
using OrpticonGameHelper.Classes.Elements;
|
|
using System.Threading;
|
|
using AStar;
|
|
using System.Threading.Tasks;
|
|
using System.Collections.Concurrent;
|
|
|
|
namespace CityGame.Classes.Entities
|
|
{
|
|
public class Car : Entity
|
|
{
|
|
public int grid = 1;
|
|
public long TimeUntilReroute = 5000;
|
|
protected long RerouteTimePassed = 0;
|
|
public static List<Car> Cars = new List<Car>();
|
|
public delegate void CarEvent(Car car);
|
|
public event CarEvent JourneyFinished;
|
|
public event CarEvent JourneyImpossible;
|
|
ISelectable target = null;
|
|
public ISelectable Target { get { return target; } set { target = value; Path = null; } }
|
|
public int NextTarget { get; set; } = 0;
|
|
public Point[]? Path { get; set; } = null;
|
|
public Point Point
|
|
{
|
|
get
|
|
{
|
|
return new Point((int)X / MainWindow.TileSize, (int)Y / MainWindow.TileSize);
|
|
}
|
|
set
|
|
{
|
|
X = (float)value.X * MainWindow.TileSize;
|
|
Y = (float)value.Y * MainWindow.TileSize;
|
|
}
|
|
}
|
|
public float Speed { get; set; } = 128;
|
|
float currentSpeed = 0;
|
|
public static ConcurrentDictionary<Tile, Car> OccupiedTilesFill = new ConcurrentDictionary<Tile, Car>();
|
|
public static ConcurrentDictionary<Tile, Car> OccupiedTiles = new ConcurrentDictionary<Tile, Car>();
|
|
public static ConcurrentDictionary<Tile, Car> OccupiedTilesFill2 = new ConcurrentDictionary<Tile, Car>();
|
|
public static ConcurrentDictionary<Tile, Car> OccupiedTiles2 = new ConcurrentDictionary<Tile, Car>();
|
|
protected string PNGFile = "NPCCar.png";
|
|
protected ColoredRectangle debugRect;
|
|
protected Tile lastTile;
|
|
protected List<LightSource> lights = new List<LightSource>();
|
|
protected LightSource PointLight;
|
|
public override OCanvas Render()
|
|
{
|
|
OCanvas canvas = new OCanvas();
|
|
Image car = new SourcedImage(PNGFile);
|
|
car.ZIndex = 99;
|
|
car.Effects.Add(selectedEffect);
|
|
|
|
canvas.Children.Add(car);
|
|
var light = new LightSource { Radius = 128, Angle = 64, Intensity = 2, Color = Color.White, Type = LightSourceType.Spotlight, Rotation = -90, RotationOrigin = new Point(MainWindow.TileSize / 2, MainWindow.TileSize / 2) };
|
|
var light2 = new LightSource { Radius = 128, Angle = 64, Intensity = 2, Color = Color.White, Type = LightSourceType.Spotlight, Rotation = -90, RotationOrigin = new Point(MainWindow.TileSize / 2, MainWindow.TileSize / 2) };
|
|
Canvas.SetLeft(light, 39);
|
|
Canvas.SetTop(light, 19);
|
|
Canvas.SetLeft(light2, 46);
|
|
Canvas.SetTop(light2, 19);
|
|
canvas.Children.Add(light);
|
|
canvas.Children.Add(light2);
|
|
|
|
var blight = new LightSource { Radius = 12, Angle = 12, Intensity = 0.5f, Color = Color.Red, Type = LightSourceType.PointLight, Rotation = 90, RotationOrigin = new Point(MainWindow.TileSize / 2, MainWindow.TileSize / 2) };
|
|
var blight2 = new LightSource { Radius = 12, Angle = 12, Intensity = 0.5f, Color = Color.Red, Type = LightSourceType.PointLight, Rotation = 90, RotationOrigin = new Point(MainWindow.TileSize / 2, MainWindow.TileSize / 2) };
|
|
Canvas.SetLeft(blight, 39);
|
|
Canvas.SetTop(blight, 46);
|
|
Canvas.SetLeft(blight2, 46);
|
|
Canvas.SetTop(blight2, 46);
|
|
canvas.Children.Add(blight);
|
|
canvas.Children.Add(blight2);
|
|
|
|
lights.Add(light);
|
|
lights.Add(light2);
|
|
lights.Add(blight);
|
|
lights.Add(blight2);
|
|
|
|
PointLight = new LightSource { Type = LightSourceType.PointLight, Color = Color.White, Radius = 24, Angle = 24, RotationOrigin = new Point(MainWindow.TileSize / 2, MainWindow.TileSize / 2) };
|
|
Canvas.SetLeft(PointLight, 43);
|
|
Canvas.SetTop(PointLight, 32);
|
|
|
|
canvas.Children.Add(PointLight);
|
|
|
|
return canvas;
|
|
}
|
|
public Car()
|
|
{
|
|
Cars.Add(this);
|
|
JourneyFinished += c => { };
|
|
JourneyImpossible += c => { };
|
|
Move += c => { };
|
|
}
|
|
public event CarEvent Move;
|
|
private int curveMode;
|
|
private float curveModePixelDuration;
|
|
private int curveModeStartedAt;
|
|
private bool pathfindingInProgress;
|
|
private async Task<Point[]> CalculatePathAsync(Point start, Point target, PathFinder pathfinder)
|
|
{
|
|
return await Task.Run(() =>
|
|
{
|
|
return pathfinder.FindPath(start.Convert(), target.Convert()).Select(x => x.Convert()).ToArray();
|
|
});
|
|
}
|
|
public override async void Tick(long deltaTime)
|
|
{
|
|
//deltaTime /= 10;
|
|
//deltaTime *= 500;
|
|
Tuple<TileType, string>[] fullBlockTiles = new Tuple<TileType, string>[]
|
|
{
|
|
new Tuple<TileType, string>(TileType.Road, "4c"),
|
|
new Tuple<TileType, string>(TileType.Road, "3c"),
|
|
new Tuple<TileType, string>(TileType.Road, "1"),
|
|
new Tuple<TileType, string>(TileType.Garage, null)
|
|
};
|
|
//if (this == MainWindow.Selected) Debug.WriteLine("Selected.");
|
|
Tile myTile = MainWindow.Grid[Point.X, Point.Y];
|
|
bool fullBlock = fullBlockTiles.Any(x => (x.Item1 == myTile.Type || (x.Item1 == TileType.Road && (myTile.Type == TileType.Path || myTile.Type == TileType.Highway || myTile.Type == TileType.Bridge || myTile.Type == TileType.HighwayBridge))) && (x.Item2 == myTile.Pattern.PatternCode || x.Item2 is null));
|
|
if (myTile.Type == TileType.Garage)
|
|
{
|
|
Rotation = ((Canvas)myTile.Element).Children[1].Rotation - 90;
|
|
lights.ForEach(x => x.Visible = false);
|
|
}
|
|
else lights.ForEach(x => x.Visible = true);
|
|
if (Target is not null)
|
|
{
|
|
if (pathfindingInProgress) return;
|
|
//if(Object is not null) Object.ToolTip = Target.ToString();
|
|
if (Path is null && !pathfindingInProgress)
|
|
{
|
|
pathfindingInProgress = true;
|
|
var pf = MainWindow.pathfinder;
|
|
if (grid == 2) pf = MainWindow.pathfinderDesperate;
|
|
|
|
Point start = Point;
|
|
Point target = new Point((int)(Target.X() / MainWindow.TileSize), (int)(Target.Y() / MainWindow.TileSize));
|
|
|
|
Path = await CalculatePathAsync(start, target, pf);
|
|
NextTarget = 0;
|
|
pathfindingInProgress = false;
|
|
}
|
|
if (new Point(Target.X() / 64, Target.Y() / 64) != Point) Move(this);
|
|
if (Path is null) return;
|
|
if (Path.Length == 0)
|
|
{
|
|
JourneyImpossible(this);
|
|
return;
|
|
}
|
|
Point nextTarget = Path[NextTarget];
|
|
if (X.CloselyEquals(nextTarget.X * MainWindow.TileSize) && Y.CloselyEquals(nextTarget.Y * MainWindow.TileSize))
|
|
{
|
|
lastTile = myTile;
|
|
NextTarget++;
|
|
}
|
|
if (NextTarget == Path.Length)
|
|
{
|
|
Path = null;
|
|
NextTarget = 0;
|
|
JourneyFinished(this);
|
|
if (Math.Round(Rotation) == 0 || Math.Round(Rotation) == 90)
|
|
{
|
|
if (!OccupiedTilesFill.ContainsKey(myTile)) OccupiedTilesFill.TryAdd(myTile, this);
|
|
if (!OccupiedTilesFill2.ContainsKey(myTile) && fullBlock) OccupiedTilesFill2.TryAdd(myTile, this);
|
|
}
|
|
if (Math.Round(Rotation) == 180 || Math.Round(Rotation) == 270)
|
|
{
|
|
if (!OccupiedTilesFill2.ContainsKey(myTile)) OccupiedTilesFill2.TryAdd(myTile, this);
|
|
if (!OccupiedTilesFill.ContainsKey(myTile) && fullBlock) OccupiedTilesFill.TryAdd(myTile, this);
|
|
}
|
|
return;
|
|
}
|
|
if (X.CloselyEquals(nextTarget.X * MainWindow.TileSize) && Y.CloselyEquals(nextTarget.Y * MainWindow.TileSize))
|
|
return;
|
|
float SpeedMulti = 1;
|
|
|
|
if (myTile.Type == TileType.Highway || myTile.Type == TileType.HighwayBridge) SpeedMulti = 2;
|
|
|
|
Vector2 travel = new Vector2((float)nextTarget.X * 64 - X, (float)nextTarget.Y * 64 - Y);
|
|
Vector2 direction = Vector2.Normalize(travel);
|
|
|
|
float degrees = (float)(Math.Atan2(direction.Y, direction.X) * (180 / Math.PI)) + 90;
|
|
Rotation = degrees;
|
|
Tile targetTile = MainWindow.Grid[nextTarget.X, nextTarget.Y];
|
|
Car blockingCar = null;
|
|
|
|
if (Math.Round(Rotation) == 0 || Math.Round(Rotation) == 90)
|
|
{
|
|
if (!OccupiedTilesFill.ContainsKey(myTile)) OccupiedTilesFill.TryAdd(myTile, this);
|
|
if (!OccupiedTilesFill2.ContainsKey(myTile) && fullBlock) OccupiedTilesFill2.TryAdd(myTile, this);
|
|
if (OccupiedTiles.ContainsKey(targetTile) && OccupiedTiles[targetTile] != this)
|
|
{
|
|
SpeedMulti = 0;
|
|
blockingCar = OccupiedTiles[targetTile];
|
|
}
|
|
}
|
|
if (Math.Round(Rotation) == 180 || Math.Round(Rotation) == 270)
|
|
{
|
|
if (!OccupiedTilesFill2.ContainsKey(myTile)) OccupiedTilesFill2.TryAdd(myTile, this);
|
|
if (!OccupiedTilesFill.ContainsKey(myTile) && fullBlock) OccupiedTilesFill.TryAdd(myTile, this);
|
|
if (OccupiedTiles2.ContainsKey(targetTile) && OccupiedTiles2[targetTile] != this)
|
|
{
|
|
SpeedMulti = 0;
|
|
blockingCar = OccupiedTiles2[targetTile];
|
|
}
|
|
}
|
|
|
|
if (SpeedMulti == 0 && blockingCar is not null)
|
|
{
|
|
RerouteTimePassed += deltaTime;
|
|
if (RerouteTimePassed > TimeUntilReroute)
|
|
{
|
|
var resetFunc = MainWindow.UpdatePathfinding(targetTile, 0, 3);
|
|
blockingCar.Move += resetFunc;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RerouteTimePassed = 0;
|
|
}
|
|
|
|
if (Path is not null && Path.Length > NextTarget + 1 && curveMode == 0)
|
|
{
|
|
var actPoint = Path[NextTarget + 1];
|
|
var afterCurveTile = MainWindow.Grid[actPoint.X, actPoint.Y];
|
|
// North
|
|
if (Rotation == 0 && afterCurveTile.X == nextTarget.X - 1 && afterCurveTile.Y == nextTarget.Y) curveMode = -1; // Left
|
|
else if (Rotation == 0 && afterCurveTile.X == nextTarget.X + 1 && afterCurveTile.Y == nextTarget.Y) curveMode = 1; // Right
|
|
|
|
// East
|
|
else if (Rotation == 90 && afterCurveTile.X == nextTarget.X && afterCurveTile.Y == nextTarget.Y - 1) curveMode = -1; // Left
|
|
else if (Rotation == 90 && afterCurveTile.X == nextTarget.X && afterCurveTile.Y == nextTarget.Y + 1) curveMode = 1; // Right
|
|
|
|
// South
|
|
else if (Rotation == 180 && afterCurveTile.X == nextTarget.X + 1 && afterCurveTile.Y == nextTarget.Y) curveMode = -1; // Left
|
|
else if (Rotation == 180 && afterCurveTile.X == nextTarget.X - 1 && afterCurveTile.Y == nextTarget.Y) curveMode = 1; // Right
|
|
|
|
// West
|
|
else if (Rotation == 270 && afterCurveTile.X == nextTarget.X && afterCurveTile.Y == nextTarget.Y + 1) curveMode = -1; // Left
|
|
else if (Rotation == 270 && afterCurveTile.X == nextTarget.X && afterCurveTile.Y == nextTarget.Y - 1) curveMode = 1; // Right
|
|
|
|
if (curveMode != 0)
|
|
{
|
|
curveModePixelDuration = MainWindow.TileSize * 2;
|
|
curveModeStartedAt = NextTarget;
|
|
}
|
|
}
|
|
|
|
var possibleDistance = Speed * deltaTime / 1000 * SpeedMulti;
|
|
var finalDistance = Math.Min(possibleDistance, travel.Length());
|
|
Vector2 travelFinal = direction * finalDistance;
|
|
|
|
UseVisualPosition = false;
|
|
if (curveMode != 0)
|
|
{
|
|
float rotationStart = MainWindow.TileSize * 1.25f;
|
|
float rotationEnd = MainWindow.TileSize * 0.75f;
|
|
|
|
curveModePixelDuration -= travelFinal.Length();
|
|
if (curveModePixelDuration <= rotationEnd) curveMode = 0;
|
|
else
|
|
{
|
|
UseVisualPosition = true;
|
|
|
|
if (curveModePixelDuration < rotationStart && curveModePixelDuration > rotationEnd)
|
|
{
|
|
float percentage = (curveModePixelDuration - rotationEnd) / (rotationStart - rotationEnd);
|
|
if (this == MainWindow.Selected) Debug.WriteLine(percentage);
|
|
float vRotDeg = 0;
|
|
if (NextTarget == curveModeStartedAt)
|
|
{
|
|
float percentage2 = percentage - 0.5f;
|
|
percentage2 *= 2;
|
|
vRotDeg = (45 - 45 * percentage2) * curveMode;
|
|
}
|
|
else
|
|
{
|
|
float percentage2 = percentage * 2;
|
|
vRotDeg = (-45 * percentage2) * curveMode;
|
|
}
|
|
if (this == MainWindow.Selected) Debug.WriteLine(vRotDeg);
|
|
visualX = X;
|
|
visualY = Y;
|
|
visualRotation = Rotation + vRotDeg;
|
|
}
|
|
else
|
|
{
|
|
visualX = X;
|
|
visualY = Y;
|
|
visualRotation = Rotation;
|
|
}
|
|
}
|
|
}
|
|
|
|
X += travelFinal.X;
|
|
Y += travelFinal.Y;
|
|
}
|
|
else
|
|
{
|
|
if (Math.Round(Rotation) == 0 || Math.Round(Rotation) == 90)
|
|
{
|
|
if (!OccupiedTilesFill.ContainsKey(myTile)) OccupiedTilesFill.TryAdd(myTile, this);
|
|
if (!OccupiedTilesFill2.ContainsKey(myTile) && fullBlock) OccupiedTilesFill2.TryAdd(myTile, this);
|
|
}
|
|
if (Math.Round(Rotation) == 180 || Math.Round(Rotation) == 270)
|
|
{
|
|
if (!OccupiedTilesFill2.ContainsKey(myTile)) OccupiedTilesFill2.TryAdd(myTile, this);
|
|
if (!OccupiedTilesFill.ContainsKey(myTile) && fullBlock) OccupiedTilesFill.TryAdd(myTile, this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |