commit 5a73410b54a09eaf83069bbb5d20793295c01b6c Author: James Turk Date: Fri Sep 6 19:47:51 2024 -0400 old experiments in writing C# games diff --git a/Dakota/.gitignore b/Dakota/.gitignore new file mode 100644 index 0000000..add57be --- /dev/null +++ b/Dakota/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ \ No newline at end of file diff --git a/Dakota/.gitmodules b/Dakota/.gitmodules new file mode 100644 index 0000000..acc7e36 --- /dev/null +++ b/Dakota/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Nez"] + path = Nez + url = git@github.com:prime31/Nez.git diff --git a/Dakota/.idea/.idea.Dakota/.idea/.gitignore b/Dakota/.idea/.idea.Dakota/.idea/.gitignore new file mode 100644 index 0000000..3ee979a --- /dev/null +++ b/Dakota/.idea/.idea.Dakota/.idea/.gitignore @@ -0,0 +1,11 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/.idea.Dakota.iml +/projectSettingsUpdater.xml +/modules.xml +/contentModel.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/Dakota/.idea/.idea.Dakota/.idea/encodings.xml b/Dakota/.idea/.idea.Dakota/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/Dakota/.idea/.idea.Dakota/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Dakota/.idea/.idea.Dakota/.idea/indexLayout.xml b/Dakota/.idea/.idea.Dakota/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/Dakota/.idea/.idea.Dakota/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Dakota/.idea/.idea.Dakota/.idea/vcs.xml b/Dakota/.idea/.idea.Dakota/.idea/vcs.xml new file mode 100644 index 0000000..bee80b2 --- /dev/null +++ b/Dakota/.idea/.idea.Dakota/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Dakota/Dakota.sln b/Dakota/Dakota.sln new file mode 100644 index 0000000..84e8990 --- /dev/null +++ b/Dakota/Dakota.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dakota", "Dakota\Dakota.csproj", "{2FF89EAA-6572-40AB-8A4D-BCE3275F113F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nez.MG38", "Nez\Nez.Portable\Nez.MG38.csproj", "{0A2DAF9B-E04C-4624-BA85-82CEA0F7D1F6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DakotaTests", "DakotaTests\DakotaTests.csproj", "{24B9130D-9930-43D2-A3EA-8D3055355502}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2FF89EAA-6572-40AB-8A4D-BCE3275F113F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2FF89EAA-6572-40AB-8A4D-BCE3275F113F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2FF89EAA-6572-40AB-8A4D-BCE3275F113F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2FF89EAA-6572-40AB-8A4D-BCE3275F113F}.Release|Any CPU.Build.0 = Release|Any CPU + {0A2DAF9B-E04C-4624-BA85-82CEA0F7D1F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A2DAF9B-E04C-4624-BA85-82CEA0F7D1F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A2DAF9B-E04C-4624-BA85-82CEA0F7D1F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A2DAF9B-E04C-4624-BA85-82CEA0F7D1F6}.Release|Any CPU.Build.0 = Release|Any CPU + {24B9130D-9930-43D2-A3EA-8D3055355502}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24B9130D-9930-43D2-A3EA-8D3055355502}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24B9130D-9930-43D2-A3EA-8D3055355502}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24B9130D-9930-43D2-A3EA-8D3055355502}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Dakota/Dakota.sln.DotSettings b/Dakota/Dakota.sln.DotSettings new file mode 100644 index 0000000..68e7dbc --- /dev/null +++ b/Dakota/Dakota.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Dakota/Dakota.sln.DotSettings.user b/Dakota/Dakota.sln.DotSettings.user new file mode 100644 index 0000000..28eba16 --- /dev/null +++ b/Dakota/Dakota.sln.DotSettings.user @@ -0,0 +1,12 @@ + + <SessionState ContinuousTestingMode="0" Name="All tests from &lt;DakotaTests&gt; #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <Project Location="/Users/james/Desktop/Dakota/DakotaTests" Presentation="&lt;DakotaTests&gt;" /> +</SessionState> + <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from &lt;DakotaTests&gt; #3" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <Project Location="/Users/james/Desktop/Dakota/DakotaTests" Presentation="&lt;DakotaTests&gt;" /> +</SessionState> + <SessionState ContinuousTestingMode="0" Name="All tests from &lt;DakotaTests&gt;" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <Project Location="/Users/james/Desktop/Dakota/DakotaTests" Presentation="&lt;DakotaTests&gt;" /> +</SessionState> + True + True \ No newline at end of file diff --git a/Dakota/Dakota/Content/Content.mgcb b/Dakota/Dakota/Content/Content.mgcb new file mode 100644 index 0000000..ce06e48 --- /dev/null +++ b/Dakota/Dakota/Content/Content.mgcb @@ -0,0 +1,27 @@ + +#----------------------------- Global Properties ----------------------------# + +/outputDir:bin/$(Platform) +/intermediateDir:obj/$(Platform) +/platform:DesktopGL +/config: +/profile:Reach +/compress:False + +#-------------------------------- References --------------------------------# + + +#---------------------------------- Content ---------------------------------# + +#begin Sprites/Ship00.png +/importer:TextureImporter +/processor:TextureProcessor +/processorParam:ColorKeyColor=255,0,255,255 +/processorParam:ColorKeyEnabled=True +/processorParam:GenerateMipmaps=False +/processorParam:PremultiplyAlpha=True +/processorParam:ResizeToPowerOfTwo=False +/processorParam:MakeSquare=False +/processorParam:TextureFormat=Color +/build:Sprites/Ship00.png + diff --git a/Dakota/Dakota/Content/Sprites/Ship00.png b/Dakota/Dakota/Content/Sprites/Ship00.png new file mode 100644 index 0000000..7041822 Binary files /dev/null and b/Dakota/Dakota/Content/Sprites/Ship00.png differ diff --git a/Dakota/Dakota/Dakota.csproj b/Dakota/Dakota/Dakota.csproj new file mode 100644 index 0000000..fa163f9 --- /dev/null +++ b/Dakota/Dakota/Dakota.csproj @@ -0,0 +1,33 @@ + + + WinExe + netcoreapp3.1 + false + false + + + app.manifest + Icon.ico + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Dakota/Dakota/Data/GameConstants.cs b/Dakota/Dakota/Data/GameConstants.cs new file mode 100644 index 0000000..d1995dd --- /dev/null +++ b/Dakota/Dakota/Data/GameConstants.cs @@ -0,0 +1,31 @@ +using Microsoft.Xna.Framework; + +namespace Dakota.Data +{ + public static class GameConstants + { + public const int UiRenderLayerBg = 910; + public const int UiRenderLayerFg = 900; + public const int BackgroundRenderLayer = 50; + + public static Color ColorBlack = Color.Black; + + // Space UI + public static Color SpaceUiColor = Color.White; + public static Color SpaceHudFontColor = new Color(0, 255, 0); + + // Space Gameplay + public const float ShipAcceleration = 100; + public const float ShipTurnSpeed = 10; + + // Space Colors + public static Color ShipColor = Color.White; + + public static Color BoostColor = new Color(255, 128, 0); + public static Color AsteroidColor = new Color(128, 128, 128); + public static Color FriendlyWeaponColor = new Color(0, 255, 0); + public static Color HostileWeaponColor = new Color(255, 0, 255); + public static Color SpaceCommandColor = new Color(128, 128, 255); + public static Color NoFlyZoneColor = new Color(200, 0, 0); + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Data/PrimaryWeapon.cs b/Dakota/Dakota/Data/PrimaryWeapon.cs new file mode 100644 index 0000000..3e3dc6a --- /dev/null +++ b/Dakota/Dakota/Data/PrimaryWeapon.cs @@ -0,0 +1,24 @@ +using Dakota.Space; + +namespace Dakota.Data +{ + public enum ProjectileType + { + Bullet, + Beam + } + + public class PrimaryWeapon + { + public static PrimaryWeapon Gun = new PrimaryWeapon + {Name = "MASS DRIVER", Projectile = ProjectileType.Bullet, Speed = 200f, Duration = 2}; + + public static PrimaryWeapon Beam = new PrimaryWeapon + {Name = "PHASE BEAM", Projectile = ProjectileType.Beam, Speed = 100f, Duration = 0.2f}; + + public float Duration; + public string Name; + public ProjectileType Projectile; + public float Speed; + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Data/ZoneData.cs b/Dakota/Dakota/Data/ZoneData.cs new file mode 100644 index 0000000..7d48e1e --- /dev/null +++ b/Dakota/Dakota/Data/ZoneData.cs @@ -0,0 +1,58 @@ +namespace Dakota.Data +{ + public class Zone + { + public int Height; + public bool IsCurvedX; + + public bool IsCurvedY; + // a rectangular region in space + + public int UniversalX; + public int UniversalY; + public int Width; + + public Zone() + { + UniversalX = 0; + UniversalY = 0; + Width = 3000; + Height = 3000; + IsCurvedX = false; + IsCurvedY = false; + } + } + + public static class ZoneData + { + private static Zone[,] _zones = new Zone[26, 26]; + + public static Zone GetZone(int x, int y) + { + if (_zones[x, y] is null) + { + var zone = new Zone + { + UniversalX = x, + UniversalY = y, + Width = 3000, + Height = 3000 + }; + + // Act 1, Scene 1 + if (x == 16 && y == 13) + { + zone.Width = 1000; + zone.Height = 1000; + } + + if (x == 3 && y == 3) zone.IsCurvedX = true; + + if (x == 3 && y > 8 && y < 16) zone.IsCurvedY = true; + _zones[x, y] = zone; + } + + return _zones[x, y]; + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Game1.cs b/Dakota/Dakota/Game1.cs new file mode 100644 index 0000000..cc7001a --- /dev/null +++ b/Dakota/Dakota/Game1.cs @@ -0,0 +1,14 @@ +using Dakota.Space; +using Nez; + +namespace Dakota +{ + public class Game1 : Core + { + protected override void Initialize() + { + base.Initialize(); + Scene = new SpaceScene(); + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Icon.bmp b/Dakota/Dakota/Icon.bmp new file mode 100644 index 0000000..2b48165 Binary files /dev/null and b/Dakota/Dakota/Icon.bmp differ diff --git a/Dakota/Dakota/Icon.ico b/Dakota/Dakota/Icon.ico new file mode 100644 index 0000000..7d9dec1 Binary files /dev/null and b/Dakota/Dakota/Icon.ico differ diff --git a/Dakota/Dakota/Missions/Mission1.cs b/Dakota/Dakota/Missions/Mission1.cs new file mode 100644 index 0000000..f79fbdd --- /dev/null +++ b/Dakota/Dakota/Missions/Mission1.cs @@ -0,0 +1,118 @@ +using Dakota.Data; +using Dakota.Space; +using Microsoft.Xna.Framework; +using Nez; + +namespace Dakota.Missions +{ + public class Mission1 : SpaceMission + { + /* + * 1) fly into boundary: add asteroids & boundary + * 2) destroy one asteroid: OK 3 more + * 3) three more: zone map + * 4) fly to highlighted zone + * this is a metroidvania shooter? + */ + + private NoFlyZone _noFlyZone; + private string _status; + private const int SmallAsteroidGoal = 16; + + public override string GetMissionStatus() + { + return _status; + } + + public override void OnStart() + { + ZoneShiftDisabled = true; + SpaceScene.CurrentZone = ZoneData.GetZone(16, 13); + SpaceScene.PlayerWarpTo(new Vector2(500, 500)); + SpaceScene.Primary = PrimaryWeapon.Gun; + + _status = "EXPLORE AREA"; + + AddBoundary("inner", + new Rectangle(100, 100, SpaceScene.CurrentZone.Width - 100, SpaceScene.CurrentZone.Height - 100)); + _noFlyZone = SpaceScene.CreateEntity("no fly zone").AddComponent(new NoFlyZone(100)); + _noFlyZone.Enabled = false; + + Core.Schedule(0.1f, + timer => SpaceScene.DialogBox.ShowDialog( + "THIS IS YOUR FIRST TIME FLYING \nOUTSIDE SIMULATION.\nTRY AND FLY AROUND A BIT.", 10)); + } + + internal void AddEmitters() + { + SpaceScene.AsteroidEmitter.EmitSpeed = 5; + SpaceScene.AsteroidEmitter.AddEmitter(new Vector2(0, 0)); + SpaceScene.AsteroidEmitter.AddEmitter(new Vector2(1000, 1000)); + SpaceScene.AsteroidEmitter.AddEmitter(new Vector2(0, 1000)); + SpaceScene.AsteroidEmitter.AddEmitter(new Vector2(1000, 0)); + SpaceScene.AsteroidEmitter.AddEmitter(new Vector2(500, 0)); + SpaceScene.AsteroidEmitter.AddEmitter(new Vector2(0, 500)); + SpaceScene.AsteroidEmitter.AddEmitter(new Vector2(1000, 500)); + SpaceScene.AsteroidEmitter.AddEmitter(new Vector2(500, 1000)); + } + + public override void OnExitRegion(string name) + { + if (!_noFlyZone.Enabled) + { + SpaceScene.DialogBox.ShowDialog("IT IS EASY TO GET LOST IN SPACE\nLET'S STAY CLOSE FOR NOW", 3); + _noFlyZone.Enabled = true; + _status = "DESTROY ASTEROIDS"; + AddEmitters(); + } + + AutopilotReverse(150); + } + + public override void OnAsteroidDestroyed(AsteroidSize size) + { + base.OnAsteroidDestroyed(size); + + if (size == AsteroidSize.LARGE && AsteroidsDestroyed[AsteroidSize.LARGE] == 1) + { + SpaceScene.DialogBox.ShowDialog("GREAT JOB! BE SURE TO BREAK UP THE REST OF IT", 3); + } + else if (size == AsteroidSize.MEDIUM && AsteroidsDestroyed[AsteroidSize.MEDIUM] % 3 == 0) + { + SpaceScene.DialogBox.ShowDialog("KEEP IT UP!", 3); + } + else if (size == AsteroidSize.SMALL) + { + if (AsteroidsDestroyed[AsteroidSize.SMALL] >= 4 && + AsteroidsDestroyed[AsteroidSize.SMALL] < SmallAsteroidGoal) + { + var percent = (int) ((float) AsteroidsDestroyed[AsteroidSize.SMALL] / SmallAsteroidGoal * 100); + _status = $"DESTROY ASTEROIDS\n{percent}"; + } + + switch (AsteroidsDestroyed[AsteroidSize.SMALL]) + { + case 4: + SpaceScene.DialogBox.ShowDialog("OK ONE DOWN, PHEW!\nTAKE OUT 3 MORE", 3); + break; + case 10: + SpaceScene.DialogBox.ShowDialog("JUST A FEW MORE!", 3); + break; + case SmallAsteroidGoal: + SpaceScene.DialogBox.ShowDialog("ALL DONE! LET'S LOOK AT YOUR MAP.", 3); + break; + } + } + } + + public override void OnPlayerHit() + { + base.OnPlayerHit(); + + if (PlayerHitCount == 1) + SpaceScene.DialogBox.ShowDialog("WATCH OUT, THESE SHIPS ARE EXPENSIVE", 3); + else if (PlayerHitCount == 10) + SpaceScene.DialogBox.ShowDialog("YOU ARE TERRIBLE AT THIS", 3); + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Missions/SpaceMission.cs b/Dakota/Dakota/Missions/SpaceMission.cs new file mode 100644 index 0000000..4d4b85e --- /dev/null +++ b/Dakota/Dakota/Missions/SpaceMission.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using Dakota.Data; +using Dakota.Space; +using Dakota.Utils; +using Microsoft.Xna.Framework; +using Nez; +using Random = Nez.Random; + +namespace Dakota.Missions +{ + public class SpaceMission : SceneComponent + { + protected SpaceScene SpaceScene => (SpaceScene) Scene; + protected int PlayerHitCount; + protected Dictionary AsteroidsDestroyed; + public bool ZoneShiftDisabled; + private readonly Dictionary _boundaries; + private readonly Dictionary _lastFrame; + private Vector2? _autopilotDestination; + + protected SpaceMission() + { + ZoneShiftDisabled = false; + AsteroidsDestroyed = new Dictionary(); + foreach (AsteroidSize size in Enum.GetValues(typeof(AsteroidSize))) AsteroidsDestroyed[size] = 0; + _boundaries = new Dictionary(); + _lastFrame = new Dictionary(); + } + + protected void AddBoundary(string name, Rectangle rect) + { + _boundaries[name] = rect; + _lastFrame[name] = false; + } + + protected void AutopilotTo(Vector2 to) + { + _autopilotDestination = to; + SpaceScene.CenterText.SetText("AUTOPILOT ENGAGED", 3); + } + + protected void AutopilotReverse(int magnitude) + { + var backwards = -Vector2.Normalize(SpaceScene.Player.GetComponent().Velocity); + AutopilotTo(SpaceScene.Player.Position + backwards * magnitude); + } + + #region Hooks + + // these methods can be overriden on missions to control behavior + + public virtual string GetMissionStatus() + { + return ""; + } + + public virtual void OnStart() + { + } + + public virtual void OnAsteroidDestroyed(AsteroidSize size) + { + AsteroidsDestroyed[size] += 1; + } + + public virtual void OnPlayerHit() + { + PlayerHitCount += 1; + } + + public virtual void OnEnterRegion(string name) + { + } + + public virtual void OnExitRegion(string name) + { + } + + #endregion + + public override void Update() + { + // check boundaries + foreach (var (name, rect) in _boundaries) + { + var thisFrame = rect.Contains(SpaceScene.Player.Position); + if (thisFrame && !_lastFrame[name]) + OnEnterRegion(name); + if (!thisFrame && _lastFrame[name]) + OnExitRegion(name); + _lastFrame[name] = thisFrame; + } + + // autopilot adjustments + if (_autopilotDestination != null) + { + var dist = SpaceScene.Player.Position - (Vector2) _autopilotDestination; + + if (dist.Length() < 35) + { + _autopilotDestination = null; + SpaceScene.CenterText.SetText("AUTOPILOT DISENGAGED", 3); + SpaceScene.Player.GetComponent().Velocity = Vector2.Zero; + SpaceScene.Player.GetComponent().Thrust(0); + } + else + { + var targetAngle = DakotaUtils.ToAngle(dist); + if (dist.Length() > 100) + targetAngle += (float) Math.PI; + + if (SpaceScene.Player.Rotation - targetAngle > 0.1f) + SpaceScene.Player.Rotation -= GameConstants.ShipTurnSpeed * Time.DeltaTime; + else if (targetAngle - SpaceScene.Player.Rotation > 0.1f) + SpaceScene.Player.Rotation += GameConstants.ShipTurnSpeed * Time.DeltaTime; + else + SpaceScene.Player.GetComponent().Thrust(.01f); + } + } + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Program.cs b/Dakota/Dakota/Program.cs new file mode 100644 index 0000000..45c4953 --- /dev/null +++ b/Dakota/Dakota/Program.cs @@ -0,0 +1,16 @@ +using System; + +namespace Dakota +{ + public static class Program + { + [STAThread] + private static void Main() + { + using (var game = new Game1()) + { + game.Run(); + } + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Renderables/DialogBox.cs b/Dakota/Dakota/Renderables/DialogBox.cs new file mode 100644 index 0000000..d4fbc70 --- /dev/null +++ b/Dakota/Dakota/Renderables/DialogBox.cs @@ -0,0 +1,58 @@ +using Dakota.Data; +using Microsoft.Xna.Framework; +using Nez; + +namespace Dakota.Renderables +{ + public class DialogBox : RenderableComponent, IUpdatable + { + public override float Width => 1000; + public override float Height => 100; + + private const int Padding = 10; + private const int PhotoBoxSize = 72; + + private float _durationRemaining; + private VectorText _vt; + private StaticBox _sb; + + public override void OnAddedToEntity() + { + RenderLayer = GameConstants.UiRenderLayerBg; + _vt = Entity.AddComponent(new VectorText("", 20, GameConstants.SpaceUiColor)); + _vt.LocalOffset = new Vector2(Padding * 2 + PhotoBoxSize, Padding); + + _sb = Entity.AddComponent(new StaticBox(72, 72, GameConstants.SpaceCommandColor)); + _sb.LocalOffset = new Vector2(Padding, Padding); + } + + public override void Render(Batcher batcher, Camera camera) + { + batcher.DrawRect(Entity.Position, Width, Height, GameConstants.ColorBlack); + batcher.DrawHollowRect(Entity.Position, Width - 5, Height, GameConstants.SpaceUiColor); + batcher.DrawHollowRect(Entity.Position.X + Padding, Entity.Position.Y + Padding, + PhotoBoxSize, PhotoBoxSize, GameConstants.SpaceUiColor); + } + + public void Update() + { + _durationRemaining -= Time.DeltaTime; + if (_durationRemaining < 0) + { + Enabled = false; + _vt.Enabled = false; + _sb.Enabled = false; + } + } + + public void ShowDialog(string text, float duration) + { + _durationRemaining = duration; + _vt.Text = text; + _vt.Color = GameConstants.SpaceCommandColor; + Enabled = true; + _vt.Enabled = true; + _sb.Enabled = true; + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Renderables/StaticBox.cs b/Dakota/Dakota/Renderables/StaticBox.cs new file mode 100644 index 0000000..43159f0 --- /dev/null +++ b/Dakota/Dakota/Renderables/StaticBox.cs @@ -0,0 +1,35 @@ +using Dakota.Data; +using Microsoft.Xna.Framework; +using Nez; + +namespace Dakota.Renderables +{ + public class StaticBox : RenderableComponent + { + public override float Width => _width; + public override float Height => _height; + + private int _width; + private int _height; + + public StaticBox(int width, int height, Color color) + { + _width = width; + _height = height; + Color = color; + + RenderLayer = GameConstants.UiRenderLayerBg; + } + + public override void Render(Batcher batcher, Camera camera) + { + for (var x = 0; x < Width; ++x) + for (var y = 0; y < Height; ++y) + if (Random.Chance(0.2f)) + batcher.DrawPixel( + Entity.Position.X + LocalOffset.X + x, + Entity.Position.Y + LocalOffset.Y + y, + Color); + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Renderables/TimedVectorText.cs b/Dakota/Dakota/Renderables/TimedVectorText.cs new file mode 100644 index 0000000..8286f24 --- /dev/null +++ b/Dakota/Dakota/Renderables/TimedVectorText.cs @@ -0,0 +1,29 @@ +using Microsoft.Xna.Framework; +using Nez; + +namespace Dakota.Renderables +{ + public class TimedVectorText : VectorText, IUpdatable + { + private float _durationRemaining; + + public TimedVectorText(int size, Color color) : base("", size, color) + { + } + + public void Update() + { + if (Text.Length > 0) + { + _durationRemaining -= Time.DeltaTime; + if (_durationRemaining < 0) Text = ""; + } + } + + public void SetText(string text, float duration) + { + Text = text; + _durationRemaining = duration; + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Renderables/VectorSprite.cs b/Dakota/Dakota/Renderables/VectorSprite.cs new file mode 100644 index 0000000..f4d9b1c --- /dev/null +++ b/Dakota/Dakota/Renderables/VectorSprite.cs @@ -0,0 +1,37 @@ +using Microsoft.Xna.Framework; +using Nez; +using Nez.PhysicsShapes; + +namespace Dakota.Renderables +{ + public class VectorSprite : RenderableComponent, IUpdatable + { + private Vector2[] _polygon; + private Vector2[] _rotatedPolygon; + + public override float Width => 100; + public override float Height => 100; + + public VectorSprite(Vector2[] polygon, Color color) + { + _polygon = polygon; + _rotatedPolygon = new Vector2[polygon.Length]; + Color = color; + } + + public override void OnAddedToEntity() + { + Polygon.RotatePolygonVerts(Entity.Rotation, _polygon, _rotatedPolygon); + } + + public override void Render(Batcher batcher, Camera camera) + { + Polygon.RotatePolygonVerts(Entity.Rotation, _polygon, _rotatedPolygon); + batcher.DrawPolygon(Entity.Position, _rotatedPolygon, Color, true, 1); + } + + public void Update() + { + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Renderables/VectorText.cs b/Dakota/Dakota/Renderables/VectorText.cs new file mode 100644 index 0000000..9e90386 --- /dev/null +++ b/Dakota/Dakota/Renderables/VectorText.cs @@ -0,0 +1,458 @@ +using System; +using System.Collections.Generic; +using Dakota.Data; +using Microsoft.Xna.Framework; +using Nez; + +namespace Dakota.Renderables +{ + public class VectorText : RenderableComponent + { + private const char Unprintable = '~'; + + private static Dictionary _font = new Dictionary(); + public int Size; + public string Text; + public float Thickness = 1.0f; + + static VectorText() + { + // each entry corresponds to the vectors for a given glyph + // lines are drawn from point to point the way one would if writing + // a null will "lift the pen" so to speak, starting a new set of + // lines not connected to the prior set + _font[Unprintable] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(0, 1), + new Vector2(1, 1), + new Vector2(1, 0), + new Vector2(0, 0), + new Vector2(1, 1), + null, + new Vector2(0, 1), + new Vector2(1, 0) + }; + _font['A'] = new Vector2?[] + { + new Vector2(0, 1), + new Vector2(0.5f, 0), + new Vector2(1, 1), + null, + new Vector2(0.2f, 0.5f), + new Vector2(0.7f, 0.5f) + }; + _font['B'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(0, 1), + new Vector2(1, 0.8f), + new Vector2(1, 0.6f), + new Vector2(0, 0.5f), + new Vector2(1, 0.4f), + new Vector2(1, 0.2f), + new Vector2(0, 0) + }; + _font['C'] = new Vector2?[] + { + new Vector2(1, 0), + new Vector2(0, 0), + new Vector2(0, 1), + new Vector2(1, 1) + }; + _font['D'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(0, 1), + new Vector2(0.5f, 1), + new Vector2(1, 0.6f), + new Vector2(1, 0.4f), + new Vector2(0.5f, 0), + new Vector2(0, 0) + }; + _font['E'] = new Vector2?[] + { + new Vector2(1, 0), + new Vector2(0, 0), + new Vector2(0, 1), + new Vector2(1, 1), + null, + new Vector2(0, 0.5f), + new Vector2(1, 0.5f) + }; + _font['F'] = new Vector2?[] + { + new Vector2(1, 0), + new Vector2(0, 0), + new Vector2(0, 1), + null, + new Vector2(0, 0.5f), + new Vector2(1, 0.5f) + }; + _font['G'] = new Vector2?[] + { + new Vector2(1, 0.2f), + new Vector2(1, 0), + new Vector2(0, 0), + new Vector2(0, 1), + new Vector2(1, 1), + new Vector2(1, 0.7f), + new Vector2(0.5f, 0.7f) + }; + _font['H'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(0, 1), + null, + new Vector2(1, 0), + new Vector2(1, 1), + null, + new Vector2(0, 0.5f), + new Vector2(1, 0.5f) + }; + _font['I'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(1, 0), + null, + new Vector2(0, 1), + new Vector2(1, 1), + null, + new Vector2(0.5f, 0), + new Vector2(0.5f, 1) + }; + _font['J'] = new Vector2?[] + { + new Vector2(1, 0), + new Vector2(1, 1), + new Vector2(0.5f, 1), + new Vector2(0, 0.7f) + }; + _font['K'] = new Vector2?[] + { + new Vector2(1, 0), + new Vector2(0, 0.5f), + new Vector2(1, 1), + null, + new Vector2(0, 0), + new Vector2(0, 1) + }; + _font['L'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(0, 1), + new Vector2(1, 1) + }; + _font['M'] = new Vector2?[] + { + new Vector2(0, 1), + new Vector2(0, 0), + new Vector2(0.5f, 0.2f), + new Vector2(1, 0), + new Vector2(1, 1) + }; + _font['N'] = new Vector2?[] + { + new Vector2(0, 1), + new Vector2(0, 0), + new Vector2(1, 1), + new Vector2(1, 0) + }; + _font['O'] = new Vector2?[] + { + new Vector2(1, 0), + new Vector2(0, 0), + new Vector2(0, 1), + new Vector2(1, 1), + new Vector2(1, 0) + }; + _font['P'] = new Vector2?[] + { + new Vector2(0, 1), + new Vector2(0, 0), + new Vector2(1, 0), + new Vector2(1, 0.4f), + new Vector2(0, 0.4f) + }; + _font['Q'] = new Vector2?[] + { + new Vector2(1, 0), + new Vector2(0, 0), + new Vector2(0, 1), + new Vector2(1, 1), + new Vector2(1, 0), + null, + new Vector2(1, 1), + new Vector2(0.5f, 0.6f) + }; + _font['R'] = new Vector2?[] + { + new Vector2(0, 1), + new Vector2(0, 0), + new Vector2(1, 0), + new Vector2(1, 0.4f), + new Vector2(0, 0.4f), + new Vector2(1, 1) + }; + _font['S'] = new Vector2?[] + { + new Vector2(1, 0), + new Vector2(0, 0), + new Vector2(0, 0.5f), + new Vector2(1, 0.5f), + new Vector2(1, 1), + new Vector2(0, 1) + }; + _font['T'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(1, 0), + null, + new Vector2(0.5f, 0), + new Vector2(0.5f, 1) + }; + _font['U'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(0, 1), + new Vector2(1, 1), + new Vector2(1, 0) + }; + _font['V'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(0.5f, 1), + new Vector2(1, 0) + }; + _font['W'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(0, 1), + new Vector2(0.5f, 0.7f), + new Vector2(1, 1), + new Vector2(1, 0) + }; + _font['X'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(1, 1), + null, + new Vector2(1, 0), + new Vector2(0, 1) + }; + _font['Y'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(0.5f, 0.4f), + new Vector2(1, 0), + null, + new Vector2(0.5f, 0.4f), + new Vector2(0.5f, 1) + }; + _font['Z'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(1, 0), + new Vector2(0, 1), + new Vector2(1, 1) + }; + _font['0'] = new Vector2?[] + { + new Vector2(1, 0), + new Vector2(0, 0), + new Vector2(0, 1), + new Vector2(1, 1), + new Vector2(1, 0), + new Vector2(0, 1) + }; + _font['1'] = new Vector2?[] + { + new Vector2(0.5f, 1), + new Vector2(0.5f, 0), + new Vector2(0.3f, 0.2f) + }; + _font['2'] = new Vector2?[] + { + new Vector2(0, 0.2f), + new Vector2(0, 0), + new Vector2(1, 0), + new Vector2(0, 1), + new Vector2(1, 1) + }; + _font['3'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(1, 0), + new Vector2(0, 0.5f), + new Vector2(1, 0.5f), + new Vector2(1, 1), + new Vector2(0, 1) + }; + _font['4'] = new Vector2?[] + { + new Vector2(0.8f, 1), + new Vector2(0.8f, 0), + new Vector2(0, 0.5f), + new Vector2(1, 0.5f) + }; + _font['5'] = new Vector2?[] + { + new Vector2(1, 0), + new Vector2(0, 0), + new Vector2(0, 0.5f), + new Vector2(1, 0.5f), + new Vector2(0, 1) + }; + _font['6'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(0, 1), + new Vector2(1, 1), + new Vector2(1, 0.6f), + new Vector2(0, 0.6f) + }; + _font['7'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(1, 0), + new Vector2(0, 1) + }; + _font['8'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(1, 0), + new Vector2(0, 1), + new Vector2(1, 1), + new Vector2(0, 0) + }; + _font['9'] = new Vector2?[] + { + new Vector2(1, 1), + new Vector2(1, 0), + new Vector2(0, 0), + new Vector2(0, 0.4f), + new Vector2(1, 0.4f) + }; + _font[' '] = new Vector2?[] { }; + _font['.'] = new Vector2?[] + { + new Vector2(0.5f, 0.9f), + new Vector2(0.6f, 0.9f), + new Vector2(0.6f, 1), + new Vector2(0.5f, 1), + new Vector2(0.5f, 0.9f) + }; + _font['!'] = new Vector2?[] + { + new Vector2(0.55f, 0), + new Vector2(0.55f, 0.8f), + null, + new Vector2(0.5f, 0.9f), + new Vector2(0.6f, 0.9f), + new Vector2(0.6f, 1), + new Vector2(0.5f, 1), + new Vector2(0.5f, 0.9f) + }; + _font['?'] = new Vector2?[] + { + new Vector2(0, 0), + new Vector2(1, 0), + new Vector2(1, 0.4f), + new Vector2(0.55f, 0.4f), + new Vector2(0.55f, 0.8f), + null, + new Vector2(0.5f, 0.9f), + new Vector2(0.6f, 0.9f), + new Vector2(0.6f, 1), + new Vector2(0.5f, 1), + new Vector2(0.5f, 0.9f) + }; + _font[','] = new Vector2?[] + { + new Vector2(0.7f, 0.6f), + new Vector2(0.6f, 1) + }; + _font['\''] = new Vector2?[] + { + new Vector2(0.7f, 0), + new Vector2(0.6f, 0.3f) + }; + _font[':'] = new Vector2?[] + { + new Vector2(0.5f, 0.9f), + new Vector2(0.6f, 0.9f), + new Vector2(0.6f, 1), + new Vector2(0.5f, 1), + new Vector2(0.5f, 0.9f), + null, + new Vector2(0.5f, 0), + new Vector2(0.6f, 0), + new Vector2(0.6f, 0.1f), + new Vector2(0.5f, 0.1f), + new Vector2(0.5f, 0) + }; + _font['-'] = new Vector2?[] + { + new Vector2(0.2f, 0.5f), + new Vector2(0.8f, 0.5f) + }; + _font['+'] = new Vector2?[] + { + new Vector2(0.2f, 0.5f), + new Vector2(0.8f, 0.5f), + null, + new Vector2(0.5f, 0.2f), + new Vector2(0.5f, 0.8f) + }; + } + + public VectorText(string text, int size, Color color) + { + Text = text; + Size = size; + Color = color; + RenderLayer = GameConstants.UiRenderLayerFg; + } + + // approximate + public override float Width => Size * Text.Length; + public override float Height => Size; + + public override void Render(Batcher batcher, Camera camera) + { + var spacing = 5; + var curOffset = new Vector2(-Size, 0); + + foreach (var ch in Text) + { + Vector2?[] letter; + if (ch == '\n') + { + curOffset.X = -Size; + curOffset.Y += Size + spacing; + continue; + } + else if (_font.ContainsKey(ch)) + { + letter = _font[ch]; + curOffset.X += Size + spacing; + } + else + { + letter = _font[Unprintable]; + curOffset.X += Size + spacing; + Console.WriteLine($"UNPRINTABLE: {ch}"); + } + + for (var i = 0; i < letter.Length - 1; i++) + if (letter[i] != null && letter[i + 1] != null) + batcher.DrawLine( + (Vector2) letter[i] * Size + curOffset + Entity.Position + LocalOffset, + (Vector2) letter[i + 1] * Size + curOffset + Entity.Position + LocalOffset, + Color, + Thickness + ); + } + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/ScreenSpin.cs b/Dakota/Dakota/ScreenSpin.cs new file mode 100644 index 0000000..3099fb1 --- /dev/null +++ b/Dakota/Dakota/ScreenSpin.cs @@ -0,0 +1,28 @@ +using Microsoft.Xna.Framework; +using Nez; + +namespace Dakota +{ + public class ScreenSpin : Component, IUpdatable + { + private readonly Vector2 _around; + private float _speed; + + public ScreenSpin(Vector2 around, float speed) + { + _speed = speed; + _around = around; + } + + public void Update() + { + Entity.Scene.Camera.Rotation += _speed * Time.DeltaTime; + if (Entity.Scene.Camera.Rotation > 5) Entity.RemoveComponent(this); + } + + public override void OnAddedToEntity() + { + Entity.Scene.Camera.Position = _around; + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Space/Asteroid.cs b/Dakota/Dakota/Space/Asteroid.cs new file mode 100644 index 0000000..c409a14 --- /dev/null +++ b/Dakota/Dakota/Space/Asteroid.cs @@ -0,0 +1,87 @@ +using System; +using Dakota.Data; +using Dakota.Renderables; +using Dakota.Utils; +using Nez; +using Random = Nez.Random; +using Vector2 = Microsoft.Xna.Framework.Vector2; + +namespace Dakota.Space +{ + public enum AsteroidSize + { + SMALL, + MEDIUM, + LARGE, + HUGE + } + + public class Asteroid : Component, ITriggerListener + { + public AsteroidSize Size; + + public Asteroid(AsteroidSize size = AsteroidSize.LARGE) + { + Size = size; + } + + void ITriggerListener.OnTriggerEnter(Collider other, Collider self) + { + if (other.Entity.Name == "bullet" && !other.Entity.IsDestroyed) + { + var scene = (SpaceScene) Entity.Scene; + scene.HitAsteroid(self.Entity, other.Entity.Position); + other.Entity.Destroy(); + } + + // need to only process A-A collisions once, so use ID comparison + if (other.Entity.Name == "asteroid" && other.Entity.Id > self.Entity.Id) + { + other.CollidesWith(self, out var collisionResult); + self.Entity.Position += collisionResult.MinimumTranslationVector; + var p1 = self.Entity.Position; + var v1 = self.Entity.GetComponent().Velocity; + var p2 = other.Entity.Position; + var v2 = other.Entity.GetComponent().Velocity; + var diff1 = p1 - p2; + var diff2 = p2 - p1; + var newV1 = v1 - Vector2.Dot(v1 - v2, diff1) / diff1.LengthSquared() * diff1; + var newV2 = v2 - Vector2.Dot(v2 - v1, diff2) / diff2.LengthSquared() * diff2; + self.Entity.GetComponent().Velocity = newV1; + other.Entity.GetComponent().Velocity = newV2; + } + } + + void ITriggerListener.OnTriggerExit(Collider other, Collider self) + { + } + + public override void OnAddedToEntity() + { + var numPoints = Random.NextInt(3) + 7; + var polygon = new Vector2[numPoints]; + for (var i = 0; i < numPoints; i++) + { + var angle = (float) ((float) i / numPoints * 2 * Math.PI); + var radius = Radius() + Random.MinusOneToOne() * Radius() * 0.2f; + polygon[i] = radius * DakotaUtils.VecFromAngle(angle); + } + + // need to clone polygon here so original is intact for drawing rotations + Entity.AddComponent(new PolygonCollider((Vector2[]) polygon.Clone())).IsTrigger = true; + Entity.AddComponent(new VectorSprite(polygon, GameConstants.AsteroidColor)); + } + + public float Radius() + { + return Size switch + { + AsteroidSize.HUGE => 128, + AsteroidSize.LARGE => 72, + AsteroidSize.MEDIUM => 48, + AsteroidSize.SMALL => 24, + _ => 256 + }; + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Space/BaddieEmitter.cs b/Dakota/Dakota/Space/BaddieEmitter.cs new file mode 100644 index 0000000..f691ab7 --- /dev/null +++ b/Dakota/Dakota/Space/BaddieEmitter.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using Dakota.Utils; +using Microsoft.Xna.Framework; +using Nez; +using Random = Nez.Random; + +namespace Dakota.Space +{ + public class BaddieEmitter : SceneComponent, IUpdatable + { + private List _emitterPoints; + private float _nextEmission; + public float EmitSpeed; + + public BaddieEmitter() + { + _emitterPoints = new List(); + EmitSpeed = 0; + _nextEmission = 0; + } + + public void AddEmitter(Vector2 pos) + { + _emitterPoints.Add(pos); + } + + public void Emit() + { + var scene = (SpaceScene) Scene; + var posIndex = Random.NextInt(_emitterPoints.Count); + var pos = _emitterPoints[posIndex]; + var angle = DakotaUtils.ToAngle(new Vector2(scene.CurrentZone.Width / 2 - pos.X, + scene.CurrentZone.Height / 2 - pos.Y)); + var vel = DakotaUtils.VecFromAngle(angle) * (Random.NextFloat(30) + 30); + scene.AddAsteroid(pos, AsteroidSize.LARGE, vel); + } + + public override void Update() + { + if (EmitSpeed > 0) + { + if (_nextEmission == 0) + _nextEmission = EmitSpeed; + _nextEmission -= Time.DeltaTime; + if (_nextEmission < 0) + { + _nextEmission += EmitSpeed; + Emit(); + } + } + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Space/GalacticMap.cs b/Dakota/Dakota/Space/GalacticMap.cs new file mode 100644 index 0000000..dd076e4 --- /dev/null +++ b/Dakota/Dakota/Space/GalacticMap.cs @@ -0,0 +1,68 @@ +using Dakota.Data; +using Microsoft.Xna.Framework; +using Nez; + +namespace Dakota.Space +{ + public class GalacticMap : RenderableComponent + { + private const float GridSize = 25f; + public override float Width => 500; + public override float Height => 520; + + public GalacticMap() + { + RenderLayer = GameConstants.UiRenderLayerBg; + } + + public void FillBox(Batcher batcher, int x, int y, Color color) + { + batcher.DrawRect( + GridSize * x + 1 + Entity.Position.X, + GridSize * y + 2 + Entity.Position.Y, + GridSize - 3, + GridSize - 3, + color); + } + + public override void Render(Batcher batcher, Camera camera) + { + var xSectors = 26; + var ySectors = 26; + + // draw horizontal lines + for (var i = 0; i <= ySectors; i++) + { + batcher.DrawLine( + 0 + Entity.Position.X, + i * GridSize + Entity.Position.Y, + xSectors * GridSize + Entity.Position.X, + i * GridSize + Entity.Position.Y, + GameConstants.SpaceUiColor); + + // draw vertical lines + for (var j = 0; j <= xSectors; j++) + { + batcher.DrawLine( + j * GridSize + Entity.Position.X, + 0 + Entity.Position.Y, + j * GridSize + Entity.Position.X, + ySectors * GridSize + Entity.Position.Y, + GameConstants.SpaceUiColor); + + if (i < ySectors && j < xSectors) + { + if (ZoneData.GetZone(j, i).IsCurvedX) + FillBox(batcher, j, i, Color.Purple); + if (ZoneData.GetZone(j, i).IsCurvedY) + FillBox(batcher, j, i, Color.Brown); + } + } + } + + // color current zone + var scene = (SpaceScene) Entity.Scene; + FillBox(batcher, scene.CurrentZone.UniversalX, scene.CurrentZone.UniversalY, Color.White); + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Space/NoFlyZone.cs b/Dakota/Dakota/Space/NoFlyZone.cs new file mode 100644 index 0000000..c207398 --- /dev/null +++ b/Dakota/Dakota/Space/NoFlyZone.cs @@ -0,0 +1,36 @@ +using Dakota.Data; +using Nez; + +namespace Dakota.Space +{ + public class NoFlyZone : RenderableComponent + { + public override float Width => 800; + public override float Height => 800; + + private int _padding; + private RectangleF _innerRect; + private RectangleF _outerRect; + + public NoFlyZone(int padding) + { + _padding = padding; + } + + public override void OnAddedToEntity() + { + var scene = (SpaceScene) Entity.Scene; + _innerRect = new RectangleF(_padding, _padding, scene.CurrentZone.Width - _padding, + scene.CurrentZone.Height - _padding); + _outerRect = new RectangleF(_padding - 10, _padding - 10, scene.CurrentZone.Width - _padding + 20, + scene.CurrentZone.Height - _padding + 20); + } + + public override void Render(Batcher batcher, Camera camera) + { + // terrible looking, what should this look like? + batcher.DrawHollowRect(_innerRect, GameConstants.NoFlyZoneColor); + batcher.DrawHollowRect(_outerRect, GameConstants.NoFlyZoneColor); + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Space/PlayerShip.cs b/Dakota/Dakota/Space/PlayerShip.cs new file mode 100644 index 0000000..9ad91d5 --- /dev/null +++ b/Dakota/Dakota/Space/PlayerShip.cs @@ -0,0 +1,159 @@ +using System; +using Dakota.Data; +using Dakota.Renderables; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using Nez; +using Nez.Particles; + +namespace Dakota.Space +{ + public enum ShipUpgrades + { + InertialThrusters, // reverse-thrust to create false feeling of inertia + HyperspaceStabilizer // reduce likelihood of hyperspace malfunction + } + + public enum ShipWeapons + { + PhaseBlaster, // simple blaster + LaserBeam, // steady pulsed beam + RailGun // unstable spray + } + + // one idea is to have these enter R&D and you can get an imperfect version that gets better as you use it + // if you're gonna have all these sweet upgrades, better have fun shit to fly near + + public class PlayerShip : Component, IUpdatable, ITriggerListener + { + private VirtualButton _fireInput; + private VirtualButton _secondaryFireInput; + private VirtualIntegerAxis _rotateInput; + private VirtualButton _thrustInput; + private VirtualButton _warpInput; + private float _emitCounter; + private ParticleEmitter _emitter; + private ParticleEmitterConfig _emitterConfig; + private VectorSprite _booster; + private float _autothrustTime; + + void ITriggerListener.OnTriggerEnter(Collider other, Collider self) + { + if (other.Entity.Name == "asteroid") + { + var scene = (SpaceScene) Entity.Scene; + scene.PlayerHit(); + scene.HitAsteroid(other.Entity, self.Entity.Position); + } + } + + void ITriggerListener.OnTriggerExit(Collider other, Collider self) + { + } + + private bool ThrustOn => _thrustInput.IsDown || _autothrustTime > 0; + + public void Thrust(float time) + { + _autothrustTime = time; + } + + public void Update() + { + var obj = Entity.GetComponent(); + if (_fireInput.IsPressed) ((SpaceScene) Entity.Scene).FirePrimary(); + if (_warpInput.IsPressed) ((SpaceScene) Entity.Scene).Warp(); + + obj.SetAcceleration(ThrustOn ? GameConstants.ShipAcceleration : 0); + _booster.Enabled = ThrustOn; + + if (ThrustOn) + _emitCounter += Time.DeltaTime; + while (_emitCounter > 0.01f) + { + _emitter.Emit(1); + _emitCounter -= 0.01f; + } + + obj.TurnVelocity = _rotateInput.Value * GameConstants.ShipTurnSpeed; + + _emitterConfig.Angle = MathHelper.ToDegrees(Entity.Rotation) + 90; + _emitter.LocalOffset = new Vector2(-16 * (float) Math.Sin(Entity.Rotation), + 16 * (float) Math.Cos(Entity.Rotation)); + } + + public override void OnAddedToEntity() + { + Entity.AddComponent(new SpaceObject()); + + var shipPolygon = new[] + { + new Vector2(0, -16), + new Vector2(-16, 16), + new Vector2(0, 10), + new Vector2(16, 16) + }; + Entity.AddComponent(new VectorSprite(shipPolygon, GameConstants.ShipColor)); + + var boostPoly = new[] + { + new Vector2(-16, 16), + new Vector2(-8, 24), + new Vector2(0, 12), + new Vector2(8, 24), + new Vector2(16, 16) + }; + _booster = Entity.AddComponent(new VectorSprite(boostPoly, GameConstants.BoostColor)); + + SetupInput(); + + // don't need indent for collider, also need own copy + // since PolygonCollider modifies the array for rotations + var colliderPolygon = new[] + { + new Vector2(0, -16), + new Vector2(-16, 16), + new Vector2(16, 16) + }; + Entity.AddComponent(new PolygonCollider(colliderPolygon)).IsTrigger = true; + + var ec = new ParticleEmitterConfig(); + ec.SourcePositionVariance = new Vector2(2, 2); + ec.Speed = 30; + ec.SpeedVariance = 1; + ec.ParticleLifespan = 3; + ec.ParticleLifespanVariance = 1; + ec.Angle = 180; + ec.AngleVariance = 0; + ec.StartColor = Color.Orange; + ec.FinishColor = Color.Transparent; + ec.StartParticleSize = 4; + ec.FinishParticleSize = 0; + ec.MaxParticles = 200; + ec.EmitterType = ParticleEmitterType.Gravity; + _emitterConfig = ec; + _emitter = Entity.AddComponent(new ParticleEmitter(ec)); + _emitter.LocalOffset = new Vector2(0, 16); + } + + private void SetupInput() + { + _fireInput = new VirtualButton(); + _fireInput.Nodes.Add(new VirtualButton.KeyboardKey(Keys.Space)); + _fireInput.SetRepeat(0.5f, 0.5f); + + _secondaryFireInput = new VirtualButton(); + _secondaryFireInput.Nodes.Add(new VirtualButton.KeyboardKey(Keys.LeftShift)); + + _thrustInput = new VirtualButton(); + _thrustInput.Nodes.Add(new VirtualButton.KeyboardKey(Keys.Up)); + + _warpInput = new VirtualButton(); + _warpInput.Nodes.Add(new VirtualButton.KeyboardKey(Keys.X)); + + _rotateInput = new VirtualIntegerAxis(); + _rotateInput.Nodes.Add(new VirtualAxis.KeyboardKeys(VirtualInput.OverlapBehavior.TakeNewer, Keys.Left, + Keys.Right)); + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Space/Projectile.cs b/Dakota/Dakota/Space/Projectile.cs new file mode 100644 index 0000000..44301ad --- /dev/null +++ b/Dakota/Dakota/Space/Projectile.cs @@ -0,0 +1,71 @@ +using Dakota.Data; +using Microsoft.Xna.Framework; +using Nez; + +namespace Dakota.Space +{ + public class Projectile : RenderableComponent, IUpdatable + { + private float _duration; + + private bool _playerFired; + private ProjectileType _type; + private Vector2 _velocity; + + public Projectile(ProjectileType type, bool playerFired, float duration) + { + _type = type; + _playerFired = playerFired; + _duration = duration; + } + + public override float Width => 2; + public override float Height => 2; + + public void Update() + { + _duration -= Time.DeltaTime; + if (_duration <= 0) + Entity.Destroy(); + } + + public override void OnAddedToEntity() + { + _velocity = Entity.GetComponent().Velocity; + + Collider collider; + switch (_type) + { + case ProjectileType.Bullet: + collider = Entity.AddComponent(new CircleCollider(1)); + break; + case ProjectileType.Beam: + collider = Entity.AddComponent(new PolygonCollider(new[] + { + new Vector2(0, 0), + _velocity * 2 + })); + break; + default: + collider = Entity.AddComponent(new CircleCollider(1)); + break; + } + + collider.IsTrigger = true; + } + + public override void Render(Batcher batcher, Camera camera) + { + var color = _playerFired ? GameConstants.FriendlyWeaponColor : GameConstants.HostileWeaponColor; + switch (_type) + { + case ProjectileType.Bullet: + batcher.DrawCircle(Entity.Position, 1, color); + break; + case ProjectileType.Beam: + batcher.DrawLine(Entity.Position, Entity.Position + _velocity * 2, color); + break; + } + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Space/SpaceControls.cs b/Dakota/Dakota/Space/SpaceControls.cs new file mode 100644 index 0000000..c0a200c --- /dev/null +++ b/Dakota/Dakota/Space/SpaceControls.cs @@ -0,0 +1,47 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using Nez; + +namespace Dakota.Space +{ + public class SpaceControls : SceneComponent, IUpdatable + { + private VirtualButton _pauseInput; + + private VirtualButton _debugInput; + + public SpaceControls() + { + _pauseInput = new VirtualButton(); + _pauseInput.Nodes.Add(new VirtualButton.KeyboardKey(Keys.P)); + + _debugInput = new VirtualButton(); + _debugInput.Nodes.Add(new VirtualButton.KeyboardKey(Keys.A)); + } + + public override void Update() + { + if (Scene is SpaceScene ss) + { + if (_pauseInput.IsPressed) ss.TogglePause(); + + if (_debugInput.IsPressed) + { + // horizontal + //ss.AddAsteroid(new Vector2(400, 400), AsteroidSize.LARGE, new Vector2(100, 0)); + //ss.AddAsteroid(new Vector2(900, 400), AsteroidSize.LARGE, new Vector2(-100, 0)); + + // vertical + //ss.AddAsteroid(new Vector2(400, 0), AsteroidSize.LARGE, new Vector2(0, 100)); + //ss.AddAsteroid(new Vector2(400, 900), AsteroidSize.LARGE, new Vector2(0, -100)); + + ss.AddAsteroid(new Vector2(0, 0), AsteroidSize.LARGE, new Vector2(100, 100)); + //ss.AddAsteroid(new Vector2(300, 300), AsteroidSize.LARGE, new Vector2(100, 100)); + ss.AddAsteroid(new Vector2(300, 0), AsteroidSize.LARGE, new Vector2(-100, 100)); + + //ss.AsteroidEmitter.Emit(); + } + } + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Space/SpaceHud.cs b/Dakota/Dakota/Space/SpaceHud.cs new file mode 100644 index 0000000..31317e0 --- /dev/null +++ b/Dakota/Dakota/Space/SpaceHud.cs @@ -0,0 +1,96 @@ +using Dakota.Data; +using Dakota.Renderables; +using Microsoft.Xna.Framework; +using Nez; + +namespace Dakota.Space +{ + public class SpaceHud : RenderableComponent, IUpdatable + { + private const int FrameSize = 30; + private VectorText _acceleration; + private VectorText _primary; + private VectorText _spaceCoords; + private VectorText _velocity; + private VectorText _objective; + + private VectorText _zoneCoords; + public override float Width => 280; + public override float Height => 720; + + public void Update() + { + var scene = (SpaceScene) Entity.Scene; + var obj = scene.Player.GetComponent(); + _primary.Text = scene.Primary.Name; + _objective.Text = scene.CurrentMission.GetMissionStatus(); + _zoneCoords.Text = $"Z: {scene.CurrentZone.UniversalX} {scene.CurrentZone.UniversalY}"; + _spaceCoords.Text = $"P: {scene.Player.Position.X:0} {scene.Player.Position.Y:0}"; + _velocity.Text = $"V: {obj.Velocity.X:0} {obj.Velocity.Y:0}"; + _acceleration.Text = $"A: {obj.Acceleration.X:0} {obj.Acceleration.Y:0}"; + } + + public override void OnAddedToEntity() + { + RenderLayer = GameConstants.UiRenderLayerBg; + + var map = Entity.AddComponent(new SpaceHudMap()); + map.LocalOffset = new Vector2(40, 500); + map.RenderLayer = GameConstants.UiRenderLayerBg; + + var line = Entity.AddComponent(new VectorText("PRIMARY", 12, GameConstants.SpaceHudFontColor)); + line.LocalOffset = new Vector2(40, 40); + _primary = line = Entity.AddComponent(new VectorText("", 12, GameConstants.SpaceHudFontColor)); + line.LocalOffset = new Vector2(40, 60); + line = Entity.AddComponent(new VectorText("SECONDARY", 12, GameConstants.SpaceHudFontColor)); + line.LocalOffset = new Vector2(40, 100); + line = Entity.AddComponent(new VectorText("SMART BOMB", 12, GameConstants.SpaceHudFontColor)); + line.LocalOffset = new Vector2(40, 120); + line = Entity.AddComponent(new VectorText("WARP CORE", 12, GameConstants.SpaceHudFontColor)); + line.LocalOffset = new Vector2(40, 200); + line = Entity.AddComponent(new VectorText("DISABLED", 12, GameConstants.SpaceHudFontColor)); + line.LocalOffset = new Vector2(40, 220); + line = Entity.AddComponent(new VectorText("OBJECTIVE", 12, GameConstants.SpaceHudFontColor)); + line.LocalOffset = new Vector2(40, 250); + _objective = line = Entity.AddComponent(new VectorText("", 6, GameConstants.SpaceHudFontColor)); + line.LocalOffset = new Vector2(40, 270); + + line = Entity.AddComponent(new VectorText("COORDINATES", 12, GameConstants.SpaceHudFontColor)); + line.LocalOffset = new Vector2(40, 350); + _zoneCoords = line = Entity.AddComponent(new VectorText("", 12, GameConstants.SpaceHudFontColor)); + line.LocalOffset = new Vector2(40, 370); + _spaceCoords = line = Entity.AddComponent(new VectorText("", 12, GameConstants.SpaceHudFontColor)); + line.LocalOffset = new Vector2(40, 390); + _velocity = line = Entity.AddComponent(new VectorText("", 12, GameConstants.SpaceHudFontColor)); + line.LocalOffset = new Vector2(40, 410); + _acceleration = line = Entity.AddComponent(new VectorText("", 12, GameConstants.SpaceHudFontColor)); + line.LocalOffset = new Vector2(40, 430); + } + + public override void Render(Batcher batcher, Camera camera) + { + batcher.DrawRect(Entity.Position, Width, Height, GameConstants.ColorBlack); + batcher.DrawHollowRect(Entity.Position, Width, Height, GameConstants.SpaceUiColor); + batcher.DrawHollowRect( + Entity.Position + new Vector2(FrameSize, FrameSize), + Width - FrameSize * 2, Height - FrameSize * 2, + GameConstants.SpaceUiColor); + batcher.DrawLine( + Entity.Position, + Entity.Position + new Vector2(FrameSize, FrameSize), + GameConstants.SpaceUiColor); + batcher.DrawLine( + Entity.Position + new Vector2(0, Height), + Entity.Position + new Vector2(FrameSize, Height - FrameSize), + GameConstants.SpaceUiColor); + batcher.DrawLine( + Entity.Position + new Vector2(Width, Height), + Entity.Position + new Vector2(Width - FrameSize, Height - FrameSize), + GameConstants.SpaceUiColor); + batcher.DrawLine( + Entity.Position + new Vector2(Width, 0), + Entity.Position + new Vector2(Width - FrameSize, FrameSize), + GameConstants.SpaceUiColor); + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Space/SpaceHudMap.cs b/Dakota/Dakota/Space/SpaceHudMap.cs new file mode 100644 index 0000000..18dd72e --- /dev/null +++ b/Dakota/Dakota/Space/SpaceHudMap.cs @@ -0,0 +1,79 @@ +using System.Reflection; +using Dakota.Data; +using Nez; + +namespace Dakota.Space +{ + public class SpaceHudMap : RenderableComponent, IUpdatable + { + private const int Regions = 5; + private int[,] _regionCount; + private int _playerRegionX; + private int _playerRegionY; + public override float Width => 100; + public override float Height => 100; + + // Rudimentary + // System Scanner + // System Scanner+ + + public void Update() + { + var scene = (SpaceScene) Entity.Scene; + _regionCount = new int[Regions, Regions]; + + var xDiv = scene.CurrentZone.Width / Regions; + var yDiv = scene.CurrentZone.Height / Regions; + + _playerRegionX = (int) (scene.Player.Position.X / xDiv); + _playerRegionY = (int) (scene.Player.Position.Y / yDiv); + + // reset asteroid counts + for (var i = 0; i < Regions; i++) + for (var j = 0; j < Regions; j++) + _regionCount[i, j] = 0; + // count asteroids + foreach (var asteroid in scene.FindComponentsOfType()) + { + var x = (int) (asteroid.Entity.Position.X / xDiv); + var y = (int) (asteroid.Entity.Position.Y / yDiv); + if (x < 0 || x >= Regions || y < 0 || y >= Regions) + continue; + _regionCount[x, y] += 1; + } + } + + public override void Render(Batcher batcher, Camera camera) + { + var unitSize = (Height - 1) / Regions; + + batcher.DrawHollowRect(Entity.Position.X + LocalOffset.X, + Entity.Position.Y + LocalOffset.Y, + Width, + Height, + GameConstants.SpaceHudFontColor); + + + for (var i = 0; i < Regions; i++) + for (var j = 0; j < Regions; j++) + { + var color = GameConstants.ColorBlack; + if (_regionCount[i, j] >= 1) + color = GameConstants.AsteroidColor; + batcher.DrawRect( + Entity.Position.X + LocalOffset.X + unitSize * i, + Entity.Position.Y + LocalOffset.Y + unitSize * j, + unitSize, + unitSize, + color); + } + + batcher.DrawRect( + Entity.Position.X + LocalOffset.X + unitSize * _playerRegionX + 5, + Entity.Position.Y + LocalOffset.Y + unitSize * _playerRegionY + 5, + unitSize - 10, + unitSize - 10, + GameConstants.ShipColor); + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Space/SpaceObject.cs b/Dakota/Dakota/Space/SpaceObject.cs new file mode 100644 index 0000000..e4eecc1 --- /dev/null +++ b/Dakota/Dakota/Space/SpaceObject.cs @@ -0,0 +1,77 @@ +using System; +using Dakota.Utils; +using Microsoft.Xna.Framework; +using Nez; + +namespace Dakota.Space +{ + public class SpaceObject : Component, IUpdatable + { + private Mover _mover; + public float TurnVelocity; + public Vector2 Velocity; + public Vector2 Acceleration; + + public SpaceObject() + { + Velocity = new Vector2(0, 0); + Acceleration = new Vector2(0, 0); + } + + public void Update() + { + var scene = (SpaceScene) Entity.Scene; + + Entity.Rotation += Time.DeltaTime * TurnVelocity; + Velocity += Time.DeltaTime * Acceleration; + _mover.Move( + Time.DeltaTime * Velocity + 0.5f * Time.DeltaTime * Time.DeltaTime * Acceleration, + out _ + ); + + // enforce zone boundaries + var newPos = Entity.Position; + if (Entity.Position.X < 0) + { + if (scene.CurrentZone.IsCurvedX) + newPos.X += scene.CurrentZone.Width; + else if (Entity.Name == "player") + newPos = scene.ZoneShift(-1, 0); + } + else if (Entity.Position.X > scene.CurrentZone.Width) + { + if (scene.CurrentZone.IsCurvedX) + newPos.X -= scene.CurrentZone.Width; + else if (Entity.Name == "player") + newPos = scene.ZoneShift(1, 0); + } + + if (Entity.Position.Y < 0) + { + if (scene.CurrentZone.IsCurvedY) + newPos.Y += scene.CurrentZone.Height; + else if (Entity.Name == "player") + newPos = scene.ZoneShift(0, -1); + } + else if (Entity.Position.Y > scene.CurrentZone.Height) + { + if (scene.CurrentZone.IsCurvedY) + newPos.Y -= scene.CurrentZone.Height; + else if (Entity.Name == "player") + newPos = scene.ZoneShift(0, 1); + } + + Entity.Position = newPos; + } + + public override void OnAddedToEntity() + { + _mover = Entity.AddComponent(new Mover()); + } + + public void SetAcceleration(float accel) + { + Acceleration = accel * DakotaUtils.VecFromAngle(Entity.Rotation); + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Space/SpaceScene.cs b/Dakota/Dakota/Space/SpaceScene.cs new file mode 100644 index 0000000..f2eb180 --- /dev/null +++ b/Dakota/Dakota/Space/SpaceScene.cs @@ -0,0 +1,226 @@ +using System; +using Dakota.Data; +using Dakota.Missions; +using Dakota.Renderables; +using Dakota.Utils; +using Microsoft.Xna.Framework; +using Nez; +using Nez.Particles; +using Random = Nez.Random; + +namespace Dakota.Space +{ + internal enum GameMode + { + Active, + Paused + } + + public class SpaceScene : Scene + { + private const int HudWidth = 280; + + private const int PauseFreezeTag = 100; + + // entities + private Entity _galacticMap; + public Entity Player; + public DialogBox DialogBox; + public TimedVectorText CenterText { get; } + public BaddieEmitter AsteroidEmitter; + + // game data + private GameMode _mode; + public SpaceMission CurrentMission; + public Zone CurrentZone; + public PrimaryWeapon Primary; + + public SpaceScene() + { + // set up screen & camera + SetDesignResolution(1280, 720, SceneResolutionPolicy.ShowAllPixelPerfect); + Screen.SetSize(1280, 720); + ClearColor = GameConstants.ColorBlack; + // render background, then everything else, then UI layers + AddRenderer(new ScreenSpaceRenderer(0, GameConstants.BackgroundRenderLayer)); + AddRenderer(new RenderLayerExcludeRenderer(10, GameConstants.UiRenderLayerBg, + GameConstants.BackgroundRenderLayer, GameConstants.UiRenderLayerFg)); + AddRenderer(new ScreenSpaceRenderer(100, GameConstants.UiRenderLayerBg, GameConstants.UiRenderLayerFg)); + Core.ExitOnEscapeKeypress = false; + if (Environment.GetEnvironmentVariable("DEBUG_RENDERING") != null) + Core.DebugRenderEnabled = true; + + // create player and focus camera + Player = CreateEntity("player"); + Player.SetTag(PauseFreezeTag); + Player.AddComponent(new PlayerShip()); + var follow = Camera.Entity.AddComponent(new FollowCamera(Player)); + follow.FocusOffset = new Vector2(-100, 0); + + AddSceneComponent(new SpaceControls()); + AsteroidEmitter = AddSceneComponent(new BaddieEmitter()); + + var starField = CreateEntity("StarField").AddComponent(new StarField()); + starField.RenderLayer = GameConstants.BackgroundRenderLayer; + + _galacticMap = CreateEntity("GalacticMap", new Vector2(10, 10)); + _galacticMap.AddComponent(); + + // create UI + var hud = CreateEntity("HUD"); + hud.Position = new Vector2(Screen.Width - HudWidth, 0); + hud.AddComponent(new SpaceHud()); + DialogBox = CreateEntity("dialog box", new Vector2(1, Screen.Height - 100)) + .AddComponent(new DialogBox()); + CenterText = CreateEntity("center text", new Vector2(300, 300)) + .AddComponent(new TimedVectorText(20, GameConstants.SpaceHudFontColor)); + + CurrentMission = new Mission1(); + AddSceneComponent(CurrentMission); + CurrentMission.OnStart(); + + _mode = GameMode.Paused; + TogglePause(); + } + + public void TogglePause() + { + if (_mode == GameMode.Active) + { + _mode = GameMode.Paused; + _galacticMap.Enabled = true; + + foreach (var e in FindEntitiesWithTag(PauseFreezeTag)) + e.Enabled = false; + } + else + { + _mode = GameMode.Active; + _galacticMap.Enabled = false; + + foreach (var e in FindEntitiesWithTag(PauseFreezeTag)) + e.Enabled = true; + } + } + + public void PlayerHit() + { + CurrentMission.OnPlayerHit(); + } + + public void PlayerWarpTo(Vector2 pos) + { + Player.Position = pos; + Camera.Position = Player.Position - Camera.Entity.GetComponent().FocusOffset; + } + + public Vector2 ZoneShift(int xShift, int yShift) + { + if (CurrentMission.ZoneShiftDisabled) + return Player.Position; + + var newZone = ZoneData.GetZone( + CurrentZone.UniversalX + xShift, + CurrentZone.UniversalY + yShift + ); + + var newPos = Player.Position; + + // wrap around or scale to new area (so middle = middle) + newPos.X = xShift switch + { + -1 => newZone.Width, + 1 => 0, + _ => newPos.X / CurrentZone.Width * newZone.Width + }; + newPos.Y = yShift switch + { + -1 => newZone.Height, + 1 => 0, + _ => newPos.Y / CurrentZone.Height * newZone.Height + }; + + CurrentZone = newZone; + return newPos; + } + + public Entity AddAsteroid(Vector2 position, AsteroidSize size, Vector2? velocity = null) + { + var asteroid = CreateEntity("asteroid", position); + asteroid.SetTag(PauseFreezeTag); + asteroid.AddComponent(new Asteroid(size)); + var so = asteroid.AddComponent(new SpaceObject()); + so.TurnVelocity = Random.MinusOneToOne() * 3; + so.Velocity = velocity ?? Random.NextUnitVector() * 20; + return asteroid; + } + + public void HitAsteroid(Entity asteroid, Vector2 hitLocation) + { + var cur = asteroid.GetComponent(); + var angle = DakotaUtils.ToAngle(asteroid.Position - hitLocation) + (float) Math.PI; + // ~ 135 degrees + var angle1 = DakotaUtils.VecFromAngle(angle - 2.4f) * 50; + var angle2 = DakotaUtils.VecFromAngle(angle + 2.4f) * 50; + + switch (cur.Size) + { + case AsteroidSize.HUGE: + AddAsteroid(asteroid.Position, AsteroidSize.LARGE, angle1); + AddAsteroid(asteroid.Position + angle2, AsteroidSize.LARGE, angle2); + break; + case AsteroidSize.LARGE: + AddAsteroid(asteroid.Position, AsteroidSize.MEDIUM, angle1); + AddAsteroid(asteroid.Position + angle2, AsteroidSize.MEDIUM, angle2); + break; + case AsteroidSize.MEDIUM: + AddAsteroid(asteroid.Position, AsteroidSize.SMALL, angle1); + AddAsteroid(asteroid.Position + angle2, AsteroidSize.SMALL, angle2); + break; + case AsteroidSize.SMALL: + break; + default: + throw new ArgumentOutOfRangeException(); + } + + var ec = new ParticleEmitterConfig(); + ec.SourcePositionVariance = new Vector2(cur.Radius(), cur.Radius()); + ec.Speed = 30; + ec.SpeedVariance = 1; + ec.ParticleLifespan = 10; + ec.ParticleLifespanVariance = 1; + ec.Angle = 90; + ec.AngleVariance = 360; + ec.StartColor = GameConstants.AsteroidColor; + ec.FinishColor = Color.Transparent; + ec.StartParticleSize = 4; + ec.FinishParticleSize = 0; + ec.MaxParticles = 200; + ec.EmitterType = ParticleEmitterType.Gravity; + var ee = CreateEntity("dust emitter"); + ee.Position = asteroid.Position; + ee.AddComponent(new ParticleEmitter(ec)).Emit(100); + // TODO: ensure this gets removed? + + asteroid.Destroy(); + CurrentMission.OnAsteroidDestroyed(cur.Size); + } + + public void FirePrimary() + { + var playerHeading = DakotaUtils.VecFromAngle(Player.Rotation); + var bullet = CreateEntity("bullet", Player.Position + playerHeading * 20); + bullet.SetTag(PauseFreezeTag); + bullet.AddComponent(new Projectile(Primary.Projectile, true, Primary.Duration)); + var so = bullet.AddComponent(new SpaceObject()); + so.Velocity = playerHeading * Primary.Speed; + if (Primary.Projectile != ProjectileType.Beam) + so.Velocity += Player.GetComponent().Velocity; + } + + public void Warp() + { + // TODO + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Space/StarField.cs b/Dakota/Dakota/Space/StarField.cs new file mode 100644 index 0000000..15c3680 --- /dev/null +++ b/Dakota/Dakota/Space/StarField.cs @@ -0,0 +1,46 @@ +using Microsoft.Xna.Framework; +using Nez; +using Random = Nez.Random; + +namespace Dakota.Space +{ + public class Star + { + public Vector2 Position; + public Color StarColor; + + public Star() + { + Position = new Vector2(Random.NextFloat() * 2, Random.NextFloat() * 2); + var intensity = Random.NextFloat(0.5f) + 0.7f; + StarColor = new Color(intensity, intensity, intensity); + } + } + + public class StarField : RenderableComponent + { + private readonly Star[] _stars; + public override float Width => 1000; + public override float Height => 720; + + public StarField() + { + _stars = new Star[500]; + for (int i = 0; i < _stars.Length; i++) + _stars[i] = new Star(); + } + + public override void Render(Batcher batcher, Camera camera) + { + var pos = ((SpaceScene) (Entity.Scene)).Player.Position; + // TODO: scale by zone size + pos /= 3000; + // TODO: consider more parallax + + foreach (var star in _stars) + { + batcher.DrawPixel((star.Position.X - pos.X) * Width, (star.Position.Y - pos.Y) * Height, star.StarColor); + } + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/Utils/DakotaUtils.cs b/Dakota/Dakota/Utils/DakotaUtils.cs new file mode 100644 index 0000000..9158c52 --- /dev/null +++ b/Dakota/Dakota/Utils/DakotaUtils.cs @@ -0,0 +1,18 @@ +using System; +using Microsoft.Xna.Framework; + +namespace Dakota.Utils +{ + public static class DakotaUtils + { + public static float ToAngle(Vector2 v) + { + return (float) Math.Atan2(v.Y, v.X) + (float) (Math.PI / 2); + } + + public static Vector2 VecFromAngle(float angle) + { + return new Vector2((float) Math.Sin(angle), (float) -Math.Cos(angle)); + } + } +} \ No newline at end of file diff --git a/Dakota/Dakota/app.manifest b/Dakota/Dakota/app.manifest new file mode 100644 index 0000000..9450150 --- /dev/null +++ b/Dakota/Dakota/app.manifest @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + + + diff --git a/Dakota/DakotaTests/DakotaTests.csproj b/Dakota/DakotaTests/DakotaTests.csproj new file mode 100644 index 0000000..919a289 --- /dev/null +++ b/Dakota/DakotaTests/DakotaTests.csproj @@ -0,0 +1,23 @@ + + + + net5.0 + + false + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/Dakota/DakotaTests/TestDakotaUtils.cs b/Dakota/DakotaTests/TestDakotaUtils.cs new file mode 100644 index 0000000..4937a46 --- /dev/null +++ b/Dakota/DakotaTests/TestDakotaUtils.cs @@ -0,0 +1,48 @@ +using System; +using Dakota.Utils; +using Microsoft.Xna.Framework; +using NUnit.Framework; + +namespace DakotaTests +{ + public class DakotaUtilTests + { + [SetUp] + public void Setup() + { + } + + private Vector2 UpVector = new(0, -1); + private Vector2 RightVector = new(1, 0); + private Vector2 DownVector = new(0, 1); + private Vector2 LeftVector = new(-1, 0); + private const float Epsilon = 0.00001f; + private const float UpAngle = 0f; + private const float RightAngle = (float) Math.PI / 2f; + private const float DownAngle = (float) Math.PI; + private const float LeftAngle = (float) Math.PI * 1.5f; + + [Test] + public void TestToAngle() + { + // going around the clock + Assert.AreEqual(UpAngle, DakotaUtils.ToAngle(UpVector)); + Assert.AreEqual(RightAngle, DakotaUtils.ToAngle(RightVector)); + Assert.AreEqual(DownAngle, DakotaUtils.ToAngle(DownVector)); + Assert.AreEqual(LeftAngle, DakotaUtils.ToAngle(LeftVector)); + } + + [Test] + public void TestFromAngle() + { + Assert.AreEqual(UpVector.X, DakotaUtils.VecFromAngle(UpAngle).X, Epsilon); + Assert.AreEqual(UpVector.Y, DakotaUtils.VecFromAngle(UpAngle).Y, Epsilon); + Assert.AreEqual(RightVector.X, DakotaUtils.VecFromAngle(RightAngle).X, Epsilon); + Assert.AreEqual(RightVector.Y, DakotaUtils.VecFromAngle(RightAngle).Y, Epsilon); + Assert.AreEqual(DownVector.X, DakotaUtils.VecFromAngle(DownAngle).X, Epsilon); + Assert.AreEqual(DownVector.Y, DakotaUtils.VecFromAngle(DownAngle).Y, Epsilon); + Assert.AreEqual(LeftVector.X, DakotaUtils.VecFromAngle(LeftAngle).X, Epsilon); + Assert.AreEqual(LeftVector.Y, DakotaUtils.VecFromAngle(LeftAngle).Y, Epsilon); + } + } +} \ No newline at end of file diff --git a/Dakota/Sprites/Ship00.aseprite b/Dakota/Sprites/Ship00.aseprite new file mode 100644 index 0000000..a581653 Binary files /dev/null and b/Dakota/Sprites/Ship00.aseprite differ diff --git a/Inky/.gitignore b/Inky/.gitignore new file mode 100644 index 0000000..add57be --- /dev/null +++ b/Inky/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ \ No newline at end of file diff --git a/Inky/.gitmodules b/Inky/.gitmodules new file mode 100644 index 0000000..acc7e36 --- /dev/null +++ b/Inky/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Nez"] + path = Nez + url = git@github.com:prime31/Nez.git diff --git a/Inky/.idea/.idea.Inky/.idea/.gitignore b/Inky/.idea/.idea.Inky/.idea/.gitignore new file mode 100644 index 0000000..16468ca --- /dev/null +++ b/Inky/.idea/.idea.Inky/.idea/.gitignore @@ -0,0 +1,11 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/projectSettingsUpdater.xml +/modules.xml +/.idea.Inky.iml +/contentModel.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/Inky/.idea/.idea.Inky/.idea/encodings.xml b/Inky/.idea/.idea.Inky/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/Inky/.idea/.idea.Inky/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Inky/.idea/.idea.Inky/.idea/indexLayout.xml b/Inky/.idea/.idea.Inky/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/Inky/.idea/.idea.Inky/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Inky/.idea/.idea.Inky/.idea/vcs.xml b/Inky/.idea/.idea.Inky/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/Inky/.idea/.idea.Inky/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Inky/Inky.sln b/Inky/Inky.sln new file mode 100644 index 0000000..dd6bd5b --- /dev/null +++ b/Inky/Inky.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inky", "Inky\Inky.csproj", "{807420D8-9B51-4BB4-9C79-B07CF1FB27A4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nez.MG38", "Nez\Nez.Portable\Nez.MG38.csproj", "{4DEC583E-A7C7-4671-B977-EA999AE58C50}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {807420D8-9B51-4BB4-9C79-B07CF1FB27A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {807420D8-9B51-4BB4-9C79-B07CF1FB27A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {807420D8-9B51-4BB4-9C79-B07CF1FB27A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {807420D8-9B51-4BB4-9C79-B07CF1FB27A4}.Release|Any CPU.Build.0 = Release|Any CPU + {4DEC583E-A7C7-4671-B977-EA999AE58C50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4DEC583E-A7C7-4671-B977-EA999AE58C50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4DEC583E-A7C7-4671-B977-EA999AE58C50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4DEC583E-A7C7-4671-B977-EA999AE58C50}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Inky/Inky/Content/Content.mgcb b/Inky/Inky/Content/Content.mgcb new file mode 100644 index 0000000..ddc4c36 --- /dev/null +++ b/Inky/Inky/Content/Content.mgcb @@ -0,0 +1,15 @@ + +#----------------------------- Global Properties ----------------------------# + +/outputDir:bin/$(Platform) +/intermediateDir:obj/$(Platform) +/platform:DesktopGL +/config: +/profile:Reach +/compress:False + +#-------------------------------- References --------------------------------# + + +#---------------------------------- Content ---------------------------------# + diff --git a/Inky/Inky/DemoControls.cs b/Inky/Inky/DemoControls.cs new file mode 100644 index 0000000..af32872 --- /dev/null +++ b/Inky/Inky/DemoControls.cs @@ -0,0 +1,48 @@ +using Microsoft.Xna.Framework.Input; +using Nez; + +namespace Inky +{ + public class DemoControls : SceneComponent, IUpdatable + { + private VirtualButton _left; + private VirtualButton _right; + private float _eyeDirection = 1; + + public DemoControls() + { + _left = new VirtualButton(); + _left.Nodes.Add(new VirtualButton.KeyboardKey(Keys.Left)); + _right = new VirtualButton(); + _right.Nodes.Add(new VirtualButton.KeyboardKey(Keys.Right)); + } + + public override void Update() + { + var lanky = Scene.Entities.FindEntity("inky").GetComponent(); + if (_left.IsDown) + { + lanky.LegAlignment = Alignment.Left; + lanky.ArmAlignment = Alignment.Left; + lanky.HeadShift -= 1f * Time.DeltaTime; + } + else if (_right.IsDown) + { + lanky.LegAlignment = Alignment.Right; + lanky.ArmAlignment = Alignment.Right; + lanky.HeadShift += 1f * Time.DeltaTime; + } + else + { + lanky.EyeShift += _eyeDirection * Time.DeltaTime; + if (lanky.EyeShift < -0.99) _eyeDirection = 1; + else if (lanky.EyeShift > 0.99) _eyeDirection = -1; + lanky.MouthShift += _eyeDirection * Time.DeltaTime; + lanky.LegWobble += _eyeDirection * Time.DeltaTime; + + lanky.LegAlignment = Alignment.Center; + lanky.ArmAlignment = Alignment.Center; + } + } + } +} \ No newline at end of file diff --git a/Inky/Inky/Game1.cs b/Inky/Inky/Game1.cs new file mode 100644 index 0000000..21a5db7 --- /dev/null +++ b/Inky/Inky/Game1.cs @@ -0,0 +1,13 @@ +using Nez; + +namespace Inky +{ + public class Game1 : Core + { + protected override void Initialize() + { + base.Initialize(); + Scene = new InkyScene(); + } + } +} \ No newline at end of file diff --git a/Inky/Inky/GameConstants.cs b/Inky/Inky/GameConstants.cs new file mode 100644 index 0000000..d3825e2 --- /dev/null +++ b/Inky/Inky/GameConstants.cs @@ -0,0 +1,11 @@ +using Microsoft.Xna.Framework; + +namespace Inky +{ + public class GameConstants + { + public const int UiRenderLayerBg = 910; + public const int UiRenderLayerFg = 900; + public const int BackgroundRenderLayer = 50; + } +} \ No newline at end of file diff --git a/Inky/Inky/Icon.bmp b/Inky/Inky/Icon.bmp new file mode 100644 index 0000000..2b48165 Binary files /dev/null and b/Inky/Inky/Icon.bmp differ diff --git a/Inky/Inky/Icon.ico b/Inky/Inky/Icon.ico new file mode 100644 index 0000000..7d9dec1 Binary files /dev/null and b/Inky/Inky/Icon.ico differ diff --git a/Inky/Inky/Inky.csproj b/Inky/Inky/Inky.csproj new file mode 100644 index 0000000..d085378 --- /dev/null +++ b/Inky/Inky/Inky.csproj @@ -0,0 +1,33 @@ + + + WinExe + netcoreapp3.1 + false + false + + + app.manifest + Icon.ico + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Inky/Inky/InkyScene.cs b/Inky/Inky/InkyScene.cs new file mode 100644 index 0000000..437c2a6 --- /dev/null +++ b/Inky/Inky/InkyScene.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.Xna.Framework; +using Nez; + +namespace Inky +{ + public class InkyScene : Scene + { + public InkyScene() + { + // set up screen & camera + SetDesignResolution(1280, 720, SceneResolutionPolicy.ShowAllPixelPerfect); + Screen.SetSize(1280, 720); + ClearColor = Color.White; + // render background, then everything else, then UI layers + AddRenderer(new ScreenSpaceRenderer(0, GameConstants.BackgroundRenderLayer)); + AddRenderer(new RenderLayerExcludeRenderer(10, GameConstants.UiRenderLayerBg, + GameConstants.BackgroundRenderLayer, GameConstants.UiRenderLayerFg)); + AddRenderer(new ScreenSpaceRenderer(100, GameConstants.UiRenderLayerBg, GameConstants.UiRenderLayerFg)); + Core.ExitOnEscapeKeypress = false; + if (Environment.GetEnvironmentVariable("DEBUG_RENDERING") != null) + Core.DebugRenderEnabled = true; + + var inky1 = CreateEntity("inky"); + inky1.AddComponent().Color = Color.Black; + inky1.Position = new Vector2(300, 300); + + var worm = CreateEntity("worm"); + //worm.AddComponent().Color = Color.Black; + worm.AddComponent(); + worm.Position = new Vector2(600, 400); + + AddSceneComponent(); + } + } +} \ No newline at end of file diff --git a/Inky/Inky/Lanky.cs b/Inky/Inky/Lanky.cs new file mode 100644 index 0000000..9ab1cb4 --- /dev/null +++ b/Inky/Inky/Lanky.cs @@ -0,0 +1,156 @@ +using System; +using Nez; +using Random = Nez.Random; + +namespace Inky +{ + public enum Alignment + { + Left, + Right, + Center + } + + public class Lanky : RenderableComponent + { + public override float Height => BodyHeight + HeadHeight + LegLength; + public override float Width => BodyWidth; + + public readonly int LegLength; + public readonly int ArmLength; + public readonly int BodyWidth; + public readonly int BodyHeight; + public readonly int HeadWidth; + public readonly int HeadHeight; + + public Alignment LegAlignment; + public Alignment ArmAlignment; + + private float _headShift; + public float HeadShift + { + get => _headShift; + set => _headShift = Math.Clamp(value, -1, 1); + } + + private float _eyeShift; + public float EyeShift + { + get => _eyeShift; + set => _eyeShift = Math.Clamp(value, -1, 1); + } + + private float _mouthShift; + public float MouthShift + { + get => _mouthShift; + set => _mouthShift = Math.Clamp(value, -1, 1); + } + + private float _mouthWidth; + public float MouthWidth + { + get => _mouthWidth; + set => _mouthWidth = Math.Clamp(value, 0, 1); + } + + private float _legWobble; + + public float LegWobble + { + get => _legWobble; + set => _legWobble = Math.Clamp(value, -1, 1); + } + + public Lanky() + { + // readonly + BodyHeight = 100; + LegLength = (int)((Random.NextFloat(0.6f) + 0.6f) * BodyHeight); + ArmLength = (int)((Random.NextFloat(0.3f) + 0.3f) * BodyHeight); + BodyWidth = (int)((Random.NextFloat(0.5f) + 0.5f) * BodyHeight); + HeadWidth = 40; + HeadHeight = 40; + + LegAlignment = Alignment.Center; + ArmAlignment = Alignment.Center; + MouthWidth = 0.5f; + } + + public override void Render(Batcher batcher, Camera camera) + { + var thickness = 3f; + // body + batcher.DrawHollowRect( + Entity.Position.X - BodyWidth/2f, + Entity.Position.Y - BodyHeight/2f, + BodyWidth, BodyHeight, Color, thickness + ); + + // legs + var legInset = BodyWidth * 0.15f; + var legBodyY = Entity.Position.Y + BodyHeight / 2f; + // center positions + var leftLegX = Entity.Position.X - BodyWidth / 2f + legInset; + var rightLegX = Entity.Position.X + BodyWidth / 2f - legInset; + + if (LegAlignment == Alignment.Left) + rightLegX = leftLegX + legInset; + else if (LegAlignment == Alignment.Right) + leftLegX = rightLegX - legInset; + + var leftWobble = LegWobble * LegLength * 0.1f; + var rightWobble = -LegWobble * LegLength * 0.1f; + + batcher.DrawLine(leftLegX, legBodyY, leftLegX, legBodyY + LegLength + leftWobble, Color); + batcher.DrawLine(rightLegX, legBodyY, rightLegX, legBodyY + LegLength + rightWobble, Color); + + // arms + var armShift = 10; + var armY = Entity.Position.Y - BodyHeight / 2f + armShift; + float leftArmX = 0; + float rightArmX = 0; + switch (ArmAlignment) + { + case Alignment.Left: + leftArmX = Entity.Position.X - BodyWidth / 2f + armShift; + rightArmX = leftArmX + armShift; + break; + case Alignment.Right: + leftArmX = Entity.Position.X + BodyWidth / 2f - armShift * 2; + rightArmX = leftArmX + armShift; + break; + case Alignment.Center: + leftArmX = Entity.Position.X - armShift * 2; + rightArmX = Entity.Position.X + armShift * 2; + break; + } + batcher.DrawLine(leftArmX, armY, leftArmX, armY + ArmLength, Color); + batcher.DrawLine(rightArmX, armY, rightArmX, armY + ArmLength, Color); + + // head + var headX = Entity.Position.X + (BodyWidth / 2f) * HeadShift - HeadWidth/2f; + var headY = Entity.Position.Y - BodyHeight / 2f - HeadHeight; + batcher.DrawHollowRect( + headX, + Entity.Position.Y - BodyHeight/2f - HeadHeight, + HeadWidth, HeadHeight, Color, thickness + ); + var eyeInset = EyeShift * (HeadWidth * 0.3f); + var eyeSpace = HeadWidth * 0.15f; + var eyeWidth = 2; + batcher.DrawHollowRect(headX + HeadWidth/2f + eyeInset - eyeSpace - eyeWidth, headY + eyeSpace, eyeWidth, eyeWidth, Color, thickness); + batcher.DrawHollowRect(headX + HeadWidth/2f + eyeInset + eyeSpace - eyeWidth, headY + eyeSpace, eyeWidth, eyeWidth, Color, thickness); + + var mouthCenterX = HeadWidth / 2f + MouthShift * HeadWidth / 2f; + var halfMouth = (HeadWidth * MouthWidth) / 2f; + batcher.DrawLine( + Math.Max(headX + mouthCenterX - halfMouth, headX), + headY + HeadHeight * 0.8f , + Math.Min(headX + mouthCenterX + halfMouth, headX + HeadWidth), + headY + HeadHeight * 0.8f , + Color + ); + } + } +} \ No newline at end of file diff --git a/Inky/Inky/Lanky2.cs b/Inky/Inky/Lanky2.cs new file mode 100644 index 0000000..ec64726 --- /dev/null +++ b/Inky/Inky/Lanky2.cs @@ -0,0 +1,89 @@ +using System; +using Inky.Skeletron; +using Microsoft.Xna.Framework; +using Nez; +using Edge = Inky.Skeletron.Edge; +using Random = Nez.Random; + +namespace Inky +{ + public class Lanky2 : RenderableComponent + { + public override float Height => 100; + public override float Width => 100; + + public readonly int LegLength; + public readonly int ArmLength; + public readonly int BodyWidth; + public readonly int BodyHeight; + public readonly int HeadWidth; + public readonly int HeadHeight; + + public Alignment LegAlignment; + public Alignment ArmAlignment; + + private float _headShift; + public float HeadShift + { + get => _headShift; + set => _headShift = Math.Clamp(value, -1, 1); + } + + private float _eyeShift; + public float EyeShift + { + get => _eyeShift; + set => _eyeShift = Math.Clamp(value, -1, 1); + } + + private float _mouthShift; + public float MouthShift + { + get => _mouthShift; + set => _mouthShift = Math.Clamp(value, -1, 1); + } + + private float _mouthWidth; + public float MouthWidth + { + get => _mouthWidth; + set => _mouthWidth = Math.Clamp(value, 0, 1); + } + + private float _legWobble; + + public float LegWobble + { + get => _legWobble; + set => _legWobble = Math.Clamp(value, -1, 1); + } + + public Skeletron.Rectangle _body; + + public Lanky2() + { + BodyHeight = (int) ((Random.NextFloat(0.5f) + 0.5f) * 100); + _body = new Skeletron.Rectangle(100, BodyHeight); + var _head = new Skeletron.Rectangle(40, 40); + _body.Attach(Edge.Top, _head); + + // readonly + LegLength = (int)((Random.NextFloat(0.6f) + 0.6f) * BodyHeight); + _body.Attach(Edge.Bottom, new Line(LegLength, true), 0.4f); + _body.Attach(Edge.Bottom, new Line(LegLength, true), -0.4f); + + ArmLength = (int)((Random.NextFloat(0.3f) + 0.3f) * BodyHeight); + _body.Attach(Edge.Left, new Line(ArmLength, true), 0.4f); + _body.Attach(Edge.Right, new Line(ArmLength, true), -0.4f); + + LegAlignment = Alignment.Center; + ArmAlignment = Alignment.Center; + MouthWidth = 0.5f; + } + + public override void Render(Batcher batcher, Camera camera) + { + _body.Draw(batcher, Entity.Position.X, Entity.Position.Y, Color.Black); + } + } +} \ No newline at end of file diff --git a/Inky/Inky/Program.cs b/Inky/Inky/Program.cs new file mode 100644 index 0000000..2ba94f4 --- /dev/null +++ b/Inky/Inky/Program.cs @@ -0,0 +1,14 @@ +using System; + +namespace Inky +{ + public static class Program + { + [STAThread] + static void Main() + { + using (var game = new Game1()) + game.Run(); + } + } +} \ No newline at end of file diff --git a/Inky/Inky/Skeletron/Head.cs b/Inky/Inky/Skeletron/Head.cs new file mode 100644 index 0000000..2dead67 --- /dev/null +++ b/Inky/Inky/Skeletron/Head.cs @@ -0,0 +1,7 @@ +namespace Inky.Skeletron +{ + public class Head + { + + } +} \ No newline at end of file diff --git a/Inky/Inky/Skeletron/Line.cs b/Inky/Inky/Skeletron/Line.cs new file mode 100644 index 0000000..b5a1eb9 --- /dev/null +++ b/Inky/Inky/Skeletron/Line.cs @@ -0,0 +1,29 @@ +using Microsoft.Xna.Framework; +using Nez; + +namespace Inky.Skeletron +{ + public class Line : Shape + { + public override int HalfHeight => IsVertical ? Length / 2 : 1; + public override int HalfWidth => IsVertical ? 1 : Length / 2; + + public int Length; + public bool IsVertical; + + public Line(int length, bool isVertical) + { + Length = length; + IsVertical = true; + } + + public override void Draw(Batcher batcher, float x, float y, Color color) + { + if(IsVertical) + batcher.DrawLine(x, y-Length/2f, x, y+Length/2f, color); + else + batcher.DrawLine(x-Length/2f, y, x+Length/2f, y, color); + DrawChildren(batcher, x, y, Color.Black); + } + } +} \ No newline at end of file diff --git a/Inky/Inky/Skeletron/Rectangle.cs b/Inky/Inky/Skeletron/Rectangle.cs new file mode 100644 index 0000000..14c5e1b --- /dev/null +++ b/Inky/Inky/Skeletron/Rectangle.cs @@ -0,0 +1,28 @@ +using Microsoft.Xna.Framework; +using Nez; + +namespace Inky.Skeletron +{ + + public class Rectangle : Shape + { + public override int HalfHeight => Height/2; + public override int HalfWidth => Width/2; + + public int Width; + public int Height; + + public Rectangle(int width, int height) + { + Width = width; + Height = height; + } + + public override void Draw(Batcher batcher, float x, float y, Color color) + { + batcher.DrawHollowRect(x - Width/2f,y - Height/2f, Width, Height, color, 2f); + DrawChildren(batcher, x, y, Color.Black); + } + + } +} \ No newline at end of file diff --git a/Inky/Inky/Skeletron/Shape.cs b/Inky/Inky/Skeletron/Shape.cs new file mode 100644 index 0000000..0455105 --- /dev/null +++ b/Inky/Inky/Skeletron/Shape.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Nez; + +namespace Inky.Skeletron +{ + public enum Edge + { + Top, + Bottom, + Left, + Right + } + + public class EdgeAttachment + { + public Edge OnEdge; + public float XOffset; + public float YOffset; + public Shape Shape; + } + + public abstract class Shape + { + public abstract int HalfHeight { get; } + public abstract int HalfWidth { get; } + + private List _attachments; + + protected Shape() + { + _attachments = new List(); + } + + public abstract void Draw(Batcher batcher, float x, float y, Color color); + + public EdgeAttachment Attach(Edge on, Shape shape, float xOffset = 0, float yOffset = 0) + { + var ea = new EdgeAttachment() { OnEdge = on, Shape = shape, XOffset = xOffset, YOffset = yOffset}; + _attachments.Add(ea); + return ea; + } + + protected void DrawChildren(Batcher batcher, float x, float y, Color color) + { + foreach (var att in _attachments) + { + var childX = x; + var childY = y; + + switch (att.OnEdge) + { + case Edge.Top: + childY -= att.Shape.HalfHeight + HalfHeight; + break; + case Edge.Bottom: + childY += att.Shape.HalfHeight + HalfHeight; + break; + case Edge.Left: + childX -= att.Shape.HalfWidth + HalfWidth; + break; + case Edge.Right: + childX += att.Shape.HalfWidth + HalfWidth; + break; + } + att.Shape.Draw(batcher, childX + att.XOffset * HalfWidth, childY + att.YOffset * HalfHeight, color); + } + } + } +} \ No newline at end of file diff --git a/Inky/Inky/Skeletron/Skeletron.cs b/Inky/Inky/Skeletron/Skeletron.cs new file mode 100644 index 0000000..8e7916b --- /dev/null +++ b/Inky/Inky/Skeletron/Skeletron.cs @@ -0,0 +1,37 @@ +using Microsoft.Xna.Framework; +using Nez; + +namespace Inky.Skeletron +{ + public class Skeletron : RenderableComponent, IUpdatable + { + public override float Height => 100; + public override float Width => 100; + + public Shape _body; + private EdgeAttachment _neckJoint; + + public Skeletron() + { + _body = new Rectangle(80, 200); + (_body as Rectangle).Attach(Edge.Left, new Rectangle(30, 10)); + (_body as Rectangle).Attach(Edge.Right, new Rectangle(60, 10)); + (_body as Rectangle).Attach(Edge.Right, new Rectangle(60, 10), 0, 0.3f); + var head = new Rectangle(80, 80); + head.Attach(Edge.Top, new Rectangle(100, 5), 0, 0); + _neckJoint = (_body as Rectangle).Attach(Edge.Top, head, -1, 0); + (_body as Rectangle).Attach(Edge.Bottom, new Line(40, true), -0.5f, 0); + (_body as Rectangle).Attach(Edge.Bottom, new Line(40, true), 0.5f, 0); + } + + public void Update() + { + _neckJoint.XOffset += Time.DeltaTime; + } + + public override void Render(Batcher batcher, Camera camera) + { + _body.Draw(batcher, Entity.Position.X, Entity.Position.Y, Color.Black); + } + } +} \ No newline at end of file diff --git a/Inky/Inky/app.manifest b/Inky/Inky/app.manifest new file mode 100644 index 0000000..4c75233 --- /dev/null +++ b/Inky/Inky/app.manifest @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + + diff --git a/Inky/newproj.txt b/Inky/newproj.txt new file mode 100644 index 0000000..819616f --- /dev/null +++ b/Inky/newproj.txt @@ -0,0 +1,20 @@ +- new monogame cross platform +- add Nez as submodule + git submodule add git@github.com:prime31/Nez.git Nez +- SLN -> add existing projeectt -> Nez.Portable.MG38 +- Proj -> add reference -> Nez +- change Game1.cs to + +using Nez; + +namespace Dakota +{ + public class Game1 : Core + { + protected override void Initialize() + { + base.Initialize(); + Scene = new SomeScene(); + } + } +} diff --git a/UPWG/.gitignore b/UPWG/.gitignore new file mode 100755 index 0000000..add57be --- /dev/null +++ b/UPWG/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ \ No newline at end of file diff --git a/UPWG/Content/jason.jpg b/UPWG/Content/jason.jpg new file mode 100755 index 0000000..0904f9e Binary files /dev/null and b/UPWG/Content/jason.jpg differ diff --git a/UPWG/Data/moves.csv b/UPWG/Data/moves.csv new file mode 100755 index 0000000..08ff511 --- /dev/null +++ b/UPWG/Data/moves.csv @@ -0,0 +1,39 @@ +Name,Type,Rank,PartsUtilized,PartsRestrained,BaseChance,BaseOffense,BasePopularity +Chop 1,Strike,1,Arms,0,0.8,3,1 +Chop 2,Strike,2,Arms,0,0.85,6,2 +Chop 3,Strike,3,Arms,0,0.9,9,3 +Kick 1,Strike,1,Legs,0,0.7,4,1 +Kick 2,Strike,2,Legs,0,0.8,8,3 +Kick 3,Strike,3,Legs,0,0.85,12,5 +Headbutt 1,Strike,1,Head,0,0.8,5,1 +Headbutt 2,Strike,2,Head,0,0.9,9,1 +Headbutt 3,Strike,3,Head,0,1.0,12,3 +Bodystrike 1,Strike,1,All,0,0.75,6,1 +Bodystrike 2,Strike,2,All,0,0.85,8,2 +Bodystrike 3,Strike,3,All,0,0.9,10,3 +Arm Hold 1,Hold,1,All,Arms,0.4,5,1 +Arm Hold 2,Hold,2,All,Arms,0.5,8,2 +Arm Hold 3,Hold,3,All,Arms,0.6,10,3 +Leg Hold 1,Hold,1,All,Legs,0.4,5,1 +Leg Hold 2,Hold,2,All,Legs,0.5,8,2 +Leg Hold 3,Hold,3,All,Legs,0.6,10,3 +Head Hold 1,Hold,1,All,Head,0.3,5,1 +Head Hold 2,Hold,2,All,Head,0.5,8,2 +Head Hold 3,Hold,3,All,Head,0.6,10,3 +Body Hold 1,Hold,1,All,Body,0.5,5,1 +Body Hold 2,Hold,2,All,Body,0.6,8,2 +Body Hold 3,Hold,3,All,Body,0.7,10,3 +Aerial Arm 1,Aerial,1,All,0,0.5,8,3 +Aerial Arm 2,Aerial,2,All,0,0.6,12,4 +Aerial Arm 3,Aerial,3,All,0,0.7,14,5 +Aerial Leg 1,Aerial,1,All,0,0.4,8,3 +Aerial Leg 2,Aerial,2,All,0,0.5,12,4 +Aerial Leg 3,Aerial,3,All,0,0.6,14,5 +Aerial Body 1,Aerial,1,All,0,0.4,9,4 +Aerial Body 2,Aerial,2,All,0,0.5,14,5 +Aerial Body 3,Aerial,3,All,0,0.7,17,6 +Pin,Pin,1,All,0,0.3,0,1 +Arms Pin,Pin,2,All,Arms,0.4,0,3 +Legs Pin,Pin,2,All,Legs,0.4,0,3 +Body Pin,Pin,2,All,Body,0.4,0,3 +Super Pin,Pin,3,All,All,0.5,0,5 \ No newline at end of file diff --git a/UPWG/Elite/Elite.csproj b/UPWG/Elite/Elite.csproj new file mode 100755 index 0000000..f0a8082 --- /dev/null +++ b/UPWG/Elite/Elite.csproj @@ -0,0 +1,11 @@ + + + + net5.0 + + + + + + + diff --git a/UPWG/Elite/Engine.cs b/UPWG/Elite/Engine.cs new file mode 100755 index 0000000..eadb57a --- /dev/null +++ b/UPWG/Elite/Engine.cs @@ -0,0 +1,81 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; + +namespace Elite +{ + public class Engine : Game + { + public static Engine Instance; + + private GraphicsDeviceManager _graphicsDeviceManager; + private int _width; + private int _height; + private Color _clearColor; + + private Scene _currentScene; + private Scene _nextScene; + + private float _elapsed; + + public static Scene Scene + { + get => Instance._currentScene; + + set + { + if (Instance._currentScene != null) + Instance._nextScene = value; + else + Instance._currentScene = value; + } + } + + public static float Elapsed => Instance._elapsed; + + public Engine(int width, int height, Color clearColor) + { + Instance = this; + + _graphicsDeviceManager = new GraphicsDeviceManager(this); + Content.RootDirectory = "Content"; + IsMouseVisible = true; + IsFixedTimeStep = false; + _clearColor = clearColor; + _width = width; + _height = height; + } + + protected override void Initialize() + { + base.Initialize(); + _graphicsDeviceManager.PreferredBackBufferWidth = _width; + _graphicsDeviceManager.PreferredBackBufferHeight = _height; + _graphicsDeviceManager.ApplyChanges(); + } + + protected override void Update(GameTime gameTime) + { + base.Update(gameTime); + _elapsed = (float) gameTime.ElapsedGameTime.TotalSeconds; + if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || + Keyboard.GetState().IsKeyDown(Keys.Escape)) + Exit(); + + _currentScene?.Update(_elapsed); + + if (_nextScene != null) + { + _currentScene = _nextScene; + _nextScene = null; + } + } + + protected override void Draw(GameTime gameTime) + { + GraphicsDevice.Clear(_clearColor); + + _currentScene.Draw(_elapsed); + } + } +} \ No newline at end of file diff --git a/UPWG/Elite/Entity.cs b/UPWG/Elite/Entity.cs new file mode 100755 index 0000000..d86a100 --- /dev/null +++ b/UPWG/Elite/Entity.cs @@ -0,0 +1,23 @@ +namespace Elite +{ + public class Component + { + public Entity Entity; + } + + public interface IUpdateable + { + void Update(); + } + + public interface + + public class Entity + { + private static uint _nextId; + + public string Name; + public readonly int Id; + private readonly FastList + + WinExe + net5.0 + false + false + + + app.manifest + Icon.ico + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UPWG/Manhattan/Program.cs b/UPWG/Manhattan/Program.cs new file mode 100755 index 0000000..9ca5c37 --- /dev/null +++ b/UPWG/Manhattan/Program.cs @@ -0,0 +1,16 @@ +using System; +using Elite; +using Microsoft.Xna.Framework; + +namespace Manhattan +{ + public static class Program + { + [STAThread] + static void Main() + { + using (var game = new ManhattanGame(1280, 720, Color.Bisque)) + game.Run(); + } + } +} \ No newline at end of file diff --git a/UPWG/Manhattan/app.manifest b/UPWG/Manhattan/app.manifest new file mode 100755 index 0000000..3924952 --- /dev/null +++ b/UPWG/Manhattan/app.manifest @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + + diff --git a/UPWG/UPWG.Core/Battle.cs b/UPWG/UPWG.Core/Battle.cs new file mode 100755 index 0000000..522084e --- /dev/null +++ b/UPWG/UPWG.Core/Battle.cs @@ -0,0 +1,367 @@ +using System; +using System.Collections.Generic; + +namespace UPWG.Core +{ + public enum WrestlerPosition + { + InRing, + OnRopes, + OutsideRing, + } + + public enum MomentumLevel + { + None = 0, + Low = 1, + Medium = 2, + High = 3, + } + + + public enum HitResult + { + Miss, + Hit, + Critical, + } + + public class MoveResult + { + public HitResult Result; + + // what did this do to the recipient? + public uint RecipientDamage; + public int RecipientMomentumDelta; + public BodyParts RecipientRestrained; + + // and what did this do to us? + public uint SelfDamage; + public int SelfMomentumDelta; + public int SelfPopularityDelta; + + public MoveResult( + HitResult result, + uint recipientDamage, + int recipientMomentumDelta = 0, + BodyParts restrained = 0, + uint selfDamage = 0, + int selfMomentumDelta = 0, + int selfPopularityDelta = 0 + ) + { + Result = result; + RecipientDamage = recipientDamage; + RecipientMomentumDelta = recipientMomentumDelta; + RecipientRestrained = restrained; + SelfDamage = selfDamage; + SelfMomentumDelta = selfMomentumDelta; + SelfPopularityDelta = selfPopularityDelta; + } + } + + interface IGoal + { + public void ProcessMove(Move move, MoveResult result); + public bool IsComplete(); + } + + class MaxMomentumGoal : IGoal + { + public int GoalMomentum { get; } + private int _maxMomentum; + private int _curMomentum; + + MaxMomentumGoal(int goalMomentum) + { + GoalMomentum = goalMomentum; + } + + public void ProcessMove(Move move, MoveResult result) + { + _curMomentum += result.SelfMomentumDelta; + if (_curMomentum > _maxMomentum) + _maxMomentum = _curMomentum; + } + + public bool IsComplete() + { + return _maxMomentum >= GoalMomentum; + } + } + + public enum MatchStatus + { + InProgress, + Pinfall, + Submission, + CountOut, + Disqualification, + } + + public class MoveOptions + { + // this class represents the available moves + public string Strike { get; set; } + public string Grapple { get; set; } + public string Other { get; set; } + public string Special { get; set; } + } + + public class Battle + { + private static Random _random = new Random(); + + public enum ParticipantRole + { + LeftSingle, + RightSingle, + LeftTag1, + LeftTag2, + RightTag1, + RightTag2, + } + + public struct Participant + { + public Wrestler Wrestler; + public WrestlerPosition Position; + public BodyParts Restrained; + public BodyParts Injured; + public MomentumLevel Momentum; + public uint MaxFatigue; + public uint CurFatigue; + + public Participant(Wrestler wrestler) + { + Wrestler = wrestler; + Position = WrestlerPosition.InRing; + Restrained = 0; + Injured = 0; + Momentum = MomentumLevel.None; + CurFatigue = MaxFatigue = wrestler.Fortitude * 10; + } + } + + private uint _turnClock; + private ParticipantRole _playerRole; + + public Dictionary Participants { get; } + public MatchStatus Status { get; private set; } + public ParticipantRole[] UpcomingTurns { get; set; } + public MoveOptions CurrentMoveOptions { get; private set; } + + private void InitializeState() + { + _turnClock = 1; + Status = MatchStatus.InProgress; + UpcomingTurns = new ParticipantRole[5]; + CalcUpcomingTurns(); + CurrentMoveOptions = new MoveOptions(); + CalcPlayerMoveOptions(); + } + + public Battle(Wrestler leftWrestler, Wrestler rightWrestler) + { + Participants = new Dictionary + { + [ParticipantRole.LeftSingle] = new Participant(leftWrestler), + [ParticipantRole.RightSingle] = new Participant(rightWrestler) + }; + _playerRole = ParticipantRole.LeftSingle; + InitializeState(); + } + + public Battle(Wrestler leftOne, Wrestler leftTwo, Wrestler rightOne, Wrestler rightTwo) + { + Participants = new Dictionary + { + [ParticipantRole.LeftTag1] = new Participant(leftOne), + [ParticipantRole.LeftTag2] = new Participant(leftTwo), + [ParticipantRole.RightTag1] = new Participant(rightOne), + [ParticipantRole.RightTag2] = new Participant(rightTwo) + }; + _playerRole = ParticipantRole.LeftTag1; + InitializeState(); + } + + // Player Interaction + + public void CalcPlayerMoveOptions() + { + // TODO: revisit for other match types + var player = Participants[_playerRole]; + var target = Participants[ParticipantRole.RightSingle]; + // TODO: revisit for other move types + CurrentMoveOptions.Strike = GetBestAvailableMove(player, MoveType.Strike, target); + CurrentMoveOptions.Grapple = GetBestAvailableMove(player, MoveType.Aerial, target); + CurrentMoveOptions.Special = "Pin"; + CurrentMoveOptions.Other = "Enzugiri **"; + } + + public MoveResult ApplyMove(string moveName) + { + var move = MoveDatabase.Lookup(moveName); + return ApplyMove(_playerRole, move, ParticipantRole.RightSingle); + } + + public bool IsPlayerTurn() + { + return UpcomingTurns[0] == _playerRole; + } + + public void EndTurn() + { + _turnClock += 1; + } + + // CPU Control + + public void GetCpuMove(out string move, out ParticipantRole target) + { + move = "Chop 1"; + target = ParticipantRole.LeftSingle; + } + + // Internals + + private MoveResult ApplyMove(ParticipantRole performerPos, Move move, ParticipantRole recipientPos) + { + var performer = Participants[performerPos]; + var recipient = Participants[recipientPos]; + + // can this move be done? + if (!MoveIsValid(performer, move, recipient)) + throw new InvalidMoveException(); + + var hitResult = CheckForHit(performer, move, recipient); + + // if move misses, no damage + if (hitResult == HitResult.Miss) + return new MoveResult(hitResult, 0); + + uint damage = 0; + uint selfDamage = 0; + switch (move.Type) + { + case MoveType.Strike: + damage = (uint) (move.BaseOffense * performer.Wrestler.DamageMult); + break; + case MoveType.Aerial: + damage = (uint) (move.BaseOffense * performer.Wrestler.DamageMult); + selfDamage = 5; + break; + case MoveType.Hold: + break; + case MoveType.Throw: + damage = (uint) (move.BaseOffense * performer.Wrestler.DamageMult); + break; + case MoveType.Pin: + break; + } + + // TODO: how does momentum/popularity work? + + return new MoveResult(hitResult, damage, 0, 0, selfDamage, 0, 0); + } + + private void CalcUpcomingTurns() + { + for (var i = 0; i < UpcomingTurns.Length; i++) + { + // TODO: use speed to determine turn order + if ((_turnClock + i) % 2 == 1) + { + UpcomingTurns[i] = ParticipantRole.LeftSingle; + } + else + { + UpcomingTurns[i] = ParticipantRole.RightSingle; + } + } + } + + private static readonly double[] HitMod = {0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4}; + private static readonly double[] EvadeMod = {-0.1, -0.1, -0.05, -0.05, 0, 0.05, 0.05, 0.1, 0.1, 0.15}; + + private static HitResult CheckForHit(Participant performer, Move move, Participant recipient) + { + var critChance = 0.05; + uint attackStat = 0; + + switch (move.Type) + { + case MoveType.Strike: + attackStat = performer.Wrestler.Strength; + break; + case MoveType.Aerial: + attackStat = performer.Wrestler.Agility; + break; + case MoveType.Pin: + attackStat = performer.Wrestler.Strength > performer.Wrestler.Agility + ? performer.Wrestler.Strength + : performer.Wrestler.Agility; + break; + case MoveType.Hold: + attackStat = performer.Wrestler.Grappling; + break; + case MoveType.Throw: + attackStat = performer.Wrestler.Grappling; + break; + } + + var hitChance = move.BaseChance * HitMod[attackStat] - EvadeMod[recipient.Wrestler.Agility]; + + var hitRoll = _random.NextDouble(); + if (hitRoll < critChance) + return HitResult.Critical; + else if (hitRoll <= hitChance) + return HitResult.Hit; + else + return HitResult.Miss; + } + + private static bool MoveIsValid(Participant performer, Move move, Participant recipient) + { + // TODO: check position of performer and recipient + + // is there an issue affecting the required body part + var requiredPartsRestrained = performer.Restrained & move.PartsUtilized; + if (requiredPartsRestrained != 0) + return false; + + // does wrestler have momentum? + if (performer.Momentum < (MomentumLevel) (move.Rank - 1)) + return false; + + return true; + } + + private static string GetBestAvailableMove(Participant participant, MoveType type, Participant recipient) + { + List best = new(); + var bestRank = -1; + foreach (var (moveName, expertise) in participant.Wrestler.MoveSet) + { + var move = MoveDatabase.Lookup(moveName); + // accumulate list of best valid moves of the type + if (move.Type == type && MoveIsValid(participant, move, recipient)) + { + // for now just use rank, eventually this may be dynamic + if (move.Rank > bestRank) + { + bestRank = (int) move.Rank; + best.Clear(); + best.Add(move); + } + else if (move.Rank == bestRank) + { + best.Add(move); + } + } + } + + return best[_random.Next(best.Count)].Name; + } + } +} \ No newline at end of file diff --git a/UPWG/UPWG.Core/Moves.cs b/UPWG/UPWG.Core/Moves.cs new file mode 100755 index 0000000..0395ddd --- /dev/null +++ b/UPWG/UPWG.Core/Moves.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using CsvHelper; + +namespace UPWG.Core +{ + public class InvalidMoveException : Exception + { + public InvalidMoveException() + { + } + + public InvalidMoveException(string message) : base(message) + { + } + } + + public enum MoveType + { + Strike, + Aerial, + Hold, + Throw, + Pin, + } + + [Flags] + public enum BodyParts + { + Arms = 1, + Legs = 2, + Head = 4, + Body = 8, + All = Arms | Legs | Head | Body, + } + + public class Move + { + public string Name { get; set; } + public MoveType Type { get; set; } + + public uint Rank { get; set; } + + // can it be done? + public double BaseChance { get; set; } + + //public MomentumLevel RequiredMomentum { get; set; } + public BodyParts PartsRestrained { get; set; } + public BodyParts PartsUtilized { get; set; } + + // what does it do? + public int BasePopularity { get; set; } + public int BaseOffense { get; set; } + } + + public static class MoveDatabase + { + public static readonly Dictionary Moves = new(); + + public static void Initialize() + { + using var reader = new StreamReader("Data/moves.csv"); + using var csv = new CsvReader(reader, CultureInfo.InvariantCulture); + var records = csv.GetRecords(); + foreach (var record in records) + { + Moves[record.Name] = record; + } + } + + public static Move Lookup(string name) + { + return Moves[name]; + } + } +} \ No newline at end of file diff --git a/UPWG/UPWG.Core/StateMachine.cs b/UPWG/UPWG.Core/StateMachine.cs new file mode 100755 index 0000000..43205f8 --- /dev/null +++ b/UPWG/UPWG.Core/StateMachine.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; + +namespace UPWG.Core +{ + public class StateMachine + where T : Enum + { + public T CurrentState { get; private set; } + public double TimeInState { get; private set; } + private Dictionary _transitions; + + public StateMachine(T initial) + { + CurrentState = initial; + TimeInState = 0; + _transitions = new Dictionary(); + } + + public void AddTransition(T from, T to, double after = 0) + { + _transitions[from] = (after, to); + } + + public void SetState(T newState) + { + CurrentState = newState; + TimeInState = 0; + } + + public void Update(double elapsed) + { + TimeInState += elapsed; + + if (!_transitions.ContainsKey(CurrentState)) + return; + + var (maxTime, nextState) = _transitions[CurrentState]; + if (TimeInState > maxTime) + { + SetState(nextState); + } + } + } +} \ No newline at end of file diff --git a/UPWG/UPWG.Core/UPWG.Core.csproj b/UPWG/UPWG.Core/UPWG.Core.csproj new file mode 100755 index 0000000..ed99cc1 --- /dev/null +++ b/UPWG/UPWG.Core/UPWG.Core.csproj @@ -0,0 +1,11 @@ + + + + net5.0 + + + + + + + diff --git a/UPWG/UPWG.Core/Wrestler.cs b/UPWG/UPWG.Core/Wrestler.cs new file mode 100755 index 0000000..989147d --- /dev/null +++ b/UPWG/UPWG.Core/Wrestler.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; + +namespace UPWG.Core +{ + public enum WeightClass + { + Light, + Normal, + Heavy, + } + + public class Wrestler + { + public string Name { get; } + public uint Strength { get; } // used + public uint Grappling { get; } + public uint Agility { get; } + public uint Fortitude { get; } + public uint Charisma { get; } + public WeightClass Weight { get; } + public Dictionary MoveSet; + + public Wrestler(string name, uint strength, uint grappling, uint agility, uint fortitude, uint charisma, + WeightClass weight + ) + { + Name = name; + Strength = strength; + Grappling = grappling; + Agility = agility; + Fortitude = fortitude; + Charisma = charisma; + Weight = weight; + MoveSet = new Dictionary(); + foreach (var (move, _) in MoveDatabase.Moves) + { + MoveSet[move] = 0; + } + } + + public double DamageMult + { + get + { + switch (Strength) + { + case <= 2: return 0.5; + case <= 4: return 1.0; + case <= 6: return 1.2; + case <= 8: return 1.4; + default: return 1.7; + } + } + } + + public uint GetExpertise(string moveName) + { + return MoveSet.ContainsKey(moveName) ? MoveSet[moveName] : 0; + } + } +} \ No newline at end of file diff --git a/UPWG/UPWG.Main/Animation.cs b/UPWG/UPWG.Main/Animation.cs new file mode 100755 index 0000000..59989e8 --- /dev/null +++ b/UPWG/UPWG.Main/Animation.cs @@ -0,0 +1,160 @@ +using System; +using System.IO; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Elite; + +namespace UPWG.Main +{ + internal enum MovementType + { + Run, + Jump, + Flip, + } + + internal enum RingPosition + { + // these use cardinal directions (picture a compass rose laid over the ring) + // they will be flipped when animations are reversed + SouthWest, + West, + NorthWest, + North, + NorthEast, + East, + SouthEast, + South, + Center, + } + + abstract class BattleAnimation + { + protected double ElapsedSeconds; + protected readonly double RunSeconds; + + protected BattleAnimation(double runSeconds) + { + ElapsedSeconds = 0; + RunSeconds = runSeconds; + } + + public bool Done() + { + return ElapsedSeconds > RunSeconds; + } + + public abstract void Update(double elapsed, ref Vector2 pos, ref float rotation); + } + + class Run : BattleAnimation + { + private Vector2 _start; + private Vector2 _end; + + public Run(double runSeconds, RingPosition from, RingPosition to) : base(runSeconds) + { + _start = RingPositionToVector2(from); + _end = RingPositionToVector2(to); + } + + public override void Update(double elapsed, ref Vector2 pos, ref float rotation) + { + ElapsedSeconds += elapsed; + pos = Vector2.Lerp(_start, _end, (float) (ElapsedSeconds / RunSeconds)); + } + + private const float EastRopesX = 200; + private const float WestRopesX = 500; + private const float CenterX = (EastRopesX + WestRopesX) / 2; + private const float NorthRopesY = 100; + private const float SouthRopesY = 300; + private const float CenterY = (NorthRopesY + SouthRopesY) / 2; + + public static Vector2 RingPositionToVector2(RingPosition position) + { + return position switch + { + RingPosition.North => new Vector2(CenterX, NorthRopesY), + RingPosition.NorthEast => new Vector2(EastRopesX, NorthRopesY), + RingPosition.East => new Vector2(EastRopesX, CenterY), + RingPosition.SouthEast => new Vector2(EastRopesX, SouthRopesY), + RingPosition.South => new Vector2(CenterX, SouthRopesY), + RingPosition.SouthWest => new Vector2(WestRopesX, SouthRopesY), + RingPosition.West => new Vector2(WestRopesX, CenterY), + RingPosition.NorthWest => new Vector2(WestRopesX, NorthRopesY), + RingPosition.Center => new Vector2(CenterX, CenterY), + _ => throw new ArgumentOutOfRangeException(nameof(position)) + }; + } + } + + class Spin : BattleAnimation + { + private float _angleFrom; + private float _angleTo; + + public Spin(double runSeconds, float angleFrom, float angleTo) : base(runSeconds) + { + _angleFrom = angleFrom; + _angleTo = angleTo; + } + + public override void Update(double elapsed, ref Vector2 pos, ref float rotation) + { + ElapsedSeconds += elapsed; + rotation = MathHelper.Lerp(_angleFrom, _angleTo, (float) (ElapsedSeconds / RunSeconds)); + } + } + + public class BattleSprite : IBatchDrawable + { + private const int Width = 128; + private const int Height = 128; + + private Texture2D _texture; + private Vector2 _origin; + private Vector2 _pos; + private float _rotation; + private BattleAnimation[] _animations; + private int _animationNum; + + public BattleSprite(GraphicsDevice graphicsDevice, string filename) + { + var fileStream = new FileStream("Content/jason.jpg", FileMode.Open); + _texture = Texture2D.FromStream(graphicsDevice, fileStream); + fileStream.Dispose(); + + _pos = new Vector2(100, 200); + _origin = new Vector2(_texture.Width / 2, _texture.Height / 2); + + _animations = new BattleAnimation[] + { + new Run(1, RingPosition.West, RingPosition.East), + new Run(1, RingPosition.East, RingPosition.West), + //new Run(1, RingPosition.West, RingPosition.East), + //new Run(1, RingPosition.East, RingPosition.Center), + //new Run(0.2, RingPosition.Center, RingPosition.Center), + new Spin(4, 0, 3.14f), + }; + _animationNum = 0; + } + + public void Update(double elapsedSeconds) + { + if (_animationNum < _animations.Length) + { + _animations[_animationNum].Update(elapsedSeconds, ref _pos, ref _rotation); + if (_animations[_animationNum].Done()) + _animationNum += 1; + } + } + + + public void Draw(SpriteBatch batch) + { + batch.Draw(_texture, new Rectangle((int) _pos.X, (int) _pos.Y, Width, Height), null, Color.White, + _rotation, _origin, SpriteEffects.None, 0); + } + } +} \ No newline at end of file diff --git a/UPWG/UPWG.Main/BattleTest.cs b/UPWG/UPWG.Main/BattleTest.cs new file mode 100755 index 0000000..dd564a1 --- /dev/null +++ b/UPWG/UPWG.Main/BattleTest.cs @@ -0,0 +1,197 @@ +using System.Collections.Generic; +using System.IO; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using UPWG.Core; +using Elite; + +namespace UPWG.Main +{ + public enum TurnState + { + WaitingForInput, + PlayerMoveAnimation, + PlayerMoveResult, + OpponentMoveAnimation, + OpponentMoveResult, + } + + public class BattleTest : Game + { + private GraphicsDeviceManager _graphicsDeviceManager; + private SpriteFont _spriteFont; + private Wrestler _p1; + private Wrestler _p2; + private Battle _match; + private StateMachine _turnState; + private BattleSprite _sprite1; + private BattleSprite _sprite2; + private ScaledRenderTarget _target; + private Sprite _ring; + + public BattleTest() + { + _graphicsDeviceManager = new GraphicsDeviceManager(this); + Content.RootDirectory = "Content"; + IsMouseVisible = true; + MoveDatabase.Initialize(); + + _turnState = new StateMachine(TurnState.WaitingForInput); + _turnState.AddTransition(TurnState.PlayerMoveAnimation, TurnState.PlayerMoveResult, 1); + _turnState.AddTransition(TurnState.PlayerMoveResult, TurnState.OpponentMoveAnimation, 1); + _turnState.AddTransition(TurnState.OpponentMoveAnimation, TurnState.OpponentMoveResult, 1); + _turnState.AddTransition(TurnState.OpponentMoveResult, TurnState.WaitingForInput, 1); + + _p1 = new Wrestler("CM Punk", 7, 9, 5, 8, 10, WeightClass.Normal); + _p2 = new Wrestler("John Cena", 8, 8, 5, 8, 8, WeightClass.Normal); + _match = new Battle(_p1, _p2); + } + + protected override void Initialize() + { + _graphicsDeviceManager.PreferredBackBufferWidth = 1281; + _graphicsDeviceManager.PreferredBackBufferHeight = 720; + _graphicsDeviceManager.ApplyChanges(); + base.Initialize(); + } + + protected override void LoadContent() + { + _spriteFont = Content.Load("Arial"); + _target = new ScaledRenderTarget(GraphicsDevice, 640, 360, 2.0f, _spriteFont); + _sprite1 = new BattleSprite(GraphicsDevice, ""); + _sprite2 = new BattleSprite(GraphicsDevice, ""); + _ring = new Sprite(GraphicsDevice, "Content/oga/ring_7.png"); + + _target.DrawList.Add(_ring); + _target.DrawList.Add(_sprite1); + _target.DrawList.Add(_sprite2); + } + + protected override void UnloadContent() + { + } + + protected override void Update(GameTime gameTime) + { + var keyboardState = Keyboard.GetState(); + MoveResult moveResult; + + _turnState.Update(gameTime.ElapsedGameTime.TotalSeconds); + _sprite1.Update(gameTime.ElapsedGameTime.TotalSeconds); + _sprite2.Update(gameTime.ElapsedGameTime.TotalSeconds); + + if (keyboardState.IsKeyDown(Keys.Escape)) + Exit(); + + if (_match.IsPlayerTurn() && _turnState.CurrentState == TurnState.WaitingForInput) + { + if (keyboardState.IsKeyDown(Keys.A)) + { + moveResult = _match.ApplyMove(_match.CurrentMoveOptions.Strike); + _turnState.SetState(TurnState.PlayerMoveAnimation); + } + else if (keyboardState.IsKeyDown(Keys.S)) + { + moveResult = _match.ApplyMove(_match.CurrentMoveOptions.Grapple); + _turnState.SetState(TurnState.PlayerMoveAnimation); + } + else if (keyboardState.IsKeyDown(Keys.D)) + { + moveResult = _match.ApplyMove(_match.CurrentMoveOptions.Other); + _turnState.SetState(TurnState.PlayerMoveAnimation); + } + else if (keyboardState.IsKeyDown(Keys.F)) + { + moveResult = _match.ApplyMove(_match.CurrentMoveOptions.Special); + _turnState.SetState(TurnState.PlayerMoveAnimation); + } + } + else + { + _match.GetCpuMove(out var move, out var target); + moveResult = _match.ApplyMove(move); + } + + base.Update(gameTime); + } + + private void DrawWrestler(Battle.ParticipantRole role, Battle.Participant participant) + { + int x, y; + + switch (role) + { + case Battle.ParticipantRole.LeftSingle: + x = 100; + y = 100; + break; + default: + x = 1280 - 300; + y = 100; + break; + } + + _target.DrawString( + _spriteFont, $"{participant.Wrestler.Name}", + new Vector2(x, y), Color.Pink + ); + _target.DrawString( + _spriteFont, $"Momentum: {participant.Momentum}", + new Vector2(x, y + 20), Color.Pink + ); + _target.DrawString( + _spriteFont, $"Restrained: {participant.Restrained} Injured: {participant.Injured}", + new Vector2(x, y + 40), Color.Pink + ); + _target.DrawString( + _spriteFont, $"{participant.CurFatigue} / {participant.MaxFatigue}", + new Vector2(x, y + 60), Color.Pink + ); + } + + private void DrawMoveOptions(MoveOptions moveOptions) + { + _target.DrawString(_spriteFont, $"A: {moveOptions.Strike}", + new Vector2(200, 200), Color.Yellow); + _target.DrawString(_spriteFont, $"S: {moveOptions.Grapple}", + new Vector2(200, 210), Color.Yellow); + _target.DrawString(_spriteFont, $"D: {moveOptions.Special}", + new Vector2(200, 220), Color.Yellow); + _target.DrawString(_spriteFont, $"F: {moveOptions.Other}", + new Vector2(200, 230), Color.Yellow); + } + + protected override void Draw(GameTime gameTime) + { + GraphicsDevice.Clear(Color.Red); + + const int x = 300; + var y = 300; + var n = 1; + + _target.Draw(gameTime); + _target.SpriteBatch.Begin(); + foreach (var (key, value) in _match.Participants) + { + DrawWrestler(key, value); + } + + _target.DrawString(_spriteFont, $"{_turnState.CurrentState}", new Vector2(_target.Width / 2, 0), + Color.White); + foreach (var turn in _match.UpcomingTurns) + { + _target.DrawString(_spriteFont, $"{n}: {turn}", new Vector2(x, y), Color.Green); + n += 1; + y += 12; + } + + if (_match.IsPlayerTurn()) + DrawMoveOptions(_match.CurrentMoveOptions); + _target.SpriteBatch.End(); + + base.Draw(gameTime); + } + } +} \ No newline at end of file diff --git a/UPWG/UPWG.Main/Content/Arial.spritefont b/UPWG/UPWG.Main/Content/Arial.spritefont new file mode 100755 index 0000000..72b9cbb --- /dev/null +++ b/UPWG/UPWG.Main/Content/Arial.spritefont @@ -0,0 +1,60 @@ + + + + + + + Arial + + + 12 + + + 0 + + + true + + + + + + + + + + + + ~ + + + + diff --git a/UPWG/UPWG.Main/Content/Content.mgcb b/UPWG/UPWG.Main/Content/Content.mgcb new file mode 100755 index 0000000..1f21e35 --- /dev/null +++ b/UPWG/UPWG.Main/Content/Content.mgcb @@ -0,0 +1,20 @@ +#----------------------------- Global Properties ----------------------------# + +/outputDir:bin/$(Platform) +/intermediateDir:obj/$(Platform) +/platform:Windows +/config: +/profile:Reach +/compress:False + +#-------------------------------- References --------------------------------# + + +#---------------------------------- Content ---------------------------------# + +#begin Arial.spritefont +/importer:FontDescriptionImporter +/processor:FontDescriptionProcessor +/processorParam:PremultiplyAlpha=True +/processorParam:TextureFormat=Compressed +/build:Arial.spritefont diff --git a/UPWG/UPWG.Main/Icon.bmp b/UPWG/UPWG.Main/Icon.bmp new file mode 100755 index 0000000..2b48165 Binary files /dev/null and b/UPWG/UPWG.Main/Icon.bmp differ diff --git a/UPWG/UPWG.Main/Icon.ico b/UPWG/UPWG.Main/Icon.ico new file mode 100755 index 0000000..7d9dec1 Binary files /dev/null and b/UPWG/UPWG.Main/Icon.ico differ diff --git a/UPWG/UPWG.Main/Program.cs b/UPWG/UPWG.Main/Program.cs new file mode 100755 index 0000000..662f87d --- /dev/null +++ b/UPWG/UPWG.Main/Program.cs @@ -0,0 +1,14 @@ +using System; + +namespace UPWG.Main +{ + public static class Program + { + [STAThread] + static void Main() + { + using (var game = new BattleTest()) + game.Run(); + } + } +} \ No newline at end of file diff --git a/UPWG/UPWG.Main/UPWG.Main.csproj b/UPWG/UPWG.Main/UPWG.Main.csproj new file mode 100755 index 0000000..c02d941 --- /dev/null +++ b/UPWG/UPWG.Main/UPWG.Main.csproj @@ -0,0 +1,36 @@ + + + WinExe + net5.0 + false + false + UPWG.Main + + + app.manifest + Icon.ico + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UPWG/UPWG.Main/app.manifest b/UPWG/UPWG.Main/app.manifest new file mode 100755 index 0000000..edbd3d8 --- /dev/null +++ b/UPWG/UPWG.Main/app.manifest @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + + diff --git a/UPWG/UPWG.sln b/UPWG/UPWG.sln new file mode 100755 index 0000000..f0ecaf5 --- /dev/null +++ b/UPWG/UPWG.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UPWG.Core", "UPWG.Core\UPWG.Core.csproj", "{C8358627-AEAF-473B-B7B9-FD670A366095}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UPWG.Main", "UPWG.Main\UPWG.Main.csproj", "{4D7C34FD-5B50-454C-93ED-E805F261F48B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elite", "Elite\Elite.csproj", "{090DD346-EFA7-4907-8ACB-757D63F9F4B5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Manhattan", "Manhattan\Manhattan.csproj", "{9FD7026A-D69B-40D8-AC7B-B77023CF5444}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C8358627-AEAF-473B-B7B9-FD670A366095}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C8358627-AEAF-473B-B7B9-FD670A366095}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8358627-AEAF-473B-B7B9-FD670A366095}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C8358627-AEAF-473B-B7B9-FD670A366095}.Release|Any CPU.Build.0 = Release|Any CPU + {4D7C34FD-5B50-454C-93ED-E805F261F48B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D7C34FD-5B50-454C-93ED-E805F261F48B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D7C34FD-5B50-454C-93ED-E805F261F48B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D7C34FD-5B50-454C-93ED-E805F261F48B}.Release|Any CPU.Build.0 = Release|Any CPU + {090DD346-EFA7-4907-8ACB-757D63F9F4B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {090DD346-EFA7-4907-8ACB-757D63F9F4B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {090DD346-EFA7-4907-8ACB-757D63F9F4B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {090DD346-EFA7-4907-8ACB-757D63F9F4B5}.Release|Any CPU.Build.0 = Release|Any CPU + {9FD7026A-D69B-40D8-AC7B-B77023CF5444}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FD7026A-D69B-40D8-AC7B-B77023CF5444}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FD7026A-D69B-40D8-AC7B-B77023CF5444}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FD7026A-D69B-40D8-AC7B-B77023CF5444}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal