From 9237a9efe2c48584e1aff0effe08c20d33f7bab3 Mon Sep 17 00:00:00 2001 From: Regalis Date: Fri, 26 Jun 2015 23:05:51 +0300 Subject: [PATCH] Functional level generation + moving the submarine --- Subsurface/Camera.cs | 2 + Subsurface/Characters/AI/EnemyAIController.cs | 10 +- Subsurface/Characters/AI/SteeringManager.cs | 10 +- Subsurface/Characters/Character.cs | 4 +- Subsurface/Characters/Limb.cs | 2 +- Subsurface/Characters/Ragdoll.cs | 2 +- .../Content/Items/Diving/divinggear.xml | 2 +- Subsurface/DebugConsole.cs | 11 +- Subsurface/Events/MonsterEvent.cs | 4 +- Subsurface/GUI/GUI.cs | 24 +- Subsurface/Game1.cs | 23 +- Subsurface/GameSession/GameSession.cs | 49 +- Subsurface/Items/Components/Door.cs | 2 +- Subsurface/Items/Components/MiniMap.cs | 10 +- Subsurface/Items/Components/RepairTool.cs | 4 +- .../Components/Signal/ConnectionPanel.cs | 21 +- Subsurface/Items/Components/Signal/Wire.cs | 9 + Subsurface/Items/Item.cs | 7 +- Subsurface/Items/ItemPrefab.cs | 2 +- Subsurface/Map/Explosion.cs | 2 +- Subsurface/Map/Gap.cs | 18 +- Subsurface/Map/Hull.cs | 12 +- Subsurface/Map/Level.cs | 539 ++++++++++++-- Subsurface/Map/Location.cs | 37 + Subsurface/Map/Map.cs | 696 +++++------------- Subsurface/Map/MapEntity.cs | 16 +- Subsurface/Map/MapEntityPrefab.cs | 12 +- Subsurface/Map/{MapHash.cs => Md5Hash.cs} | 11 +- Subsurface/Map/Structure.cs | 8 +- Subsurface/Map/StructurePrefab.cs | 6 +- Subsurface/Map/Submarine.cs | 564 ++++++++++++++ Subsurface/Map/Voronoi.cs | 12 + Subsurface/Map/VoronoiElements.cs | 23 + Subsurface/Map/WayPoint.cs | 4 +- Subsurface/Networking/GameClient.cs | 4 +- Subsurface/Networking/GameServer.cs | 6 +- Subsurface/Particles/Particle.cs | 4 +- Subsurface/Particles/ParticleManager.cs | 2 +- Subsurface/SaveUtil.cs | 4 +- Subsurface/Screens/EditCharacterScreen.cs | 2 +- Subsurface/Screens/EditMapScreen.cs | 2 +- Subsurface/Screens/GameScreen.cs | 38 +- Subsurface/Screens/LobbyScreen.cs | 105 +-- Subsurface/Screens/MainMenu.cs | 6 +- Subsurface/Screens/NetLobbyScreen.cs | 30 +- Subsurface/Subsurface.csproj | 6 +- Subsurface/ToolBox.cs | 12 +- Subsurface_Solution.v12.suo | Bin 299008 -> 308224 bytes 48 files changed, 1629 insertions(+), 750 deletions(-) create mode 100644 Subsurface/Map/Location.cs rename Subsurface/Map/{MapHash.cs => Md5Hash.cs} (86%) create mode 100644 Subsurface/Map/Submarine.cs diff --git a/Subsurface/Camera.cs b/Subsurface/Camera.cs index 30f5842d8..662fdb27b 100644 --- a/Subsurface/Camera.cs +++ b/Subsurface/Camera.cs @@ -162,6 +162,8 @@ namespace Subsurface if (Keyboard.GetState().IsKeyDown(Keys.Right)) moveCam.X += moveSpeed; if (Keyboard.GetState().IsKeyDown(Keys.Down)) moveCam.Y -= moveSpeed; if (Keyboard.GetState().IsKeyDown(Keys.Up)) moveCam.Y += moveSpeed; + + Zoom = MathHelper.Clamp(Zoom + PlayerInput.ScrollWheelSpeed / 1000.0f, 0.1f, 2.0f); } else { diff --git a/Subsurface/Characters/AI/EnemyAIController.cs b/Subsurface/Characters/AI/EnemyAIController.cs index 8044270f4..9b3aafbf7 100644 --- a/Subsurface/Characters/AI/EnemyAIController.cs +++ b/Subsurface/Characters/AI/EnemyAIController.cs @@ -219,9 +219,9 @@ namespace Subsurface //check if there's a wall between the target and the character Vector2 rayStart = character.animController.limbs[0].SimPosition; Vector2 rayEnd = selectedTarget.Position; - Body closestBody = Map.CheckVisibility(rayStart, rayEnd); + Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd); - if (Map.LastPickedFraction == 1.0f || closestBody == null) + if (Submarine.LastPickedFraction == 1.0f || closestBody == null) { wallAttackPos = Vector2.Zero; return; @@ -230,11 +230,11 @@ namespace Subsurface Structure wall = closestBody.UserData as Structure; if (wall == null) { - wallAttackPos = Map.LastPickedPosition; + wallAttackPos = Submarine.LastPickedPosition; } else { - int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Map.LastPickedPosition)); + int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition)); float sectionDamage = wall.SectionDamage(sectionIndex); for (int i = sectionIndex - 2; i <= sectionIndex + 2; i++) @@ -377,7 +377,7 @@ namespace Subsurface Vector2 rayStart = character.animController.limbs[0].SimPosition; Vector2 rayEnd = target.Position; - Body closestBody = Map.CheckVisibility(rayStart, rayEnd); + Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd); Structure closestStructure = (closestBody == null) ? null : closestBody.UserData as Structure; //if (targetCharacter != null) diff --git a/Subsurface/Characters/AI/SteeringManager.cs b/Subsurface/Characters/AI/SteeringManager.cs index 0b6337300..7a371dcc2 100644 --- a/Subsurface/Characters/AI/SteeringManager.cs +++ b/Subsurface/Characters/AI/SteeringManager.cs @@ -32,7 +32,7 @@ namespace Subsurface { this.host = host; - wanderAngle = ToolBox.RandomFloat(0.0f, MathHelper.TwoPi); + wanderAngle = ToolBox.RandomFloatLocal(0.0f, MathHelper.TwoPi); } public void SteeringSeek(Vector2 target, float speed = 1.0f) @@ -90,7 +90,7 @@ namespace Subsurface float angleChange = 1.5f; - wanderAngle += ToolBox.RandomFloat(0.0f, 1.0f) * angleChange - angleChange * 0.5f; + wanderAngle += ToolBox.RandomFloatLocal(0.0f, 1.0f) * angleChange - angleChange * 0.5f; Vector2 newSteering = circleCenter + displacement; float steeringSpeed = (newSteering + host.Steering).Length(); @@ -113,7 +113,7 @@ namespace Subsurface if (rayCastTimer <= 0.0f) { rayCastTimer = RayCastInterval; - Body closestBody = Map.CheckVisibility(host.Position, ahead); + Body closestBody = Submarine.CheckVisibility(host.Position, ahead); if (closestBody == null) { avoidSteering = Vector2.Zero; @@ -124,7 +124,7 @@ namespace Subsurface Structure closestStructure = closestBody.UserData as Structure; if (closestStructure!=null) { - Vector2 obstaclePosition = Map.LastPickedPosition; + Vector2 obstaclePosition = Submarine.LastPickedPosition; if (closestStructure.IsHorizontal) { obstaclePosition.Y = closestStructure.SimPosition.Y; @@ -134,7 +134,7 @@ namespace Subsurface obstaclePosition.X = closestStructure.SimPosition.X; } - avoidSteering = Vector2.Normalize(Map.LastPickedPosition - obstaclePosition); + avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - obstaclePosition); } } diff --git a/Subsurface/Characters/Character.cs b/Subsurface/Characters/Character.cs index b84404b8a..a8470820f 100644 --- a/Subsurface/Characters/Character.cs +++ b/Subsurface/Characters/Character.cs @@ -769,13 +769,13 @@ namespace Subsurface for (int i = 0; i < 10; i++) { Particle p = Game1.particleManager.CreateParticle("waterblood", - torso.SimPosition + new Vector2(ToolBox.RandomFloat(-0.5f, 0.5f), ToolBox.RandomFloat(-0.5f, 0.5f)), + torso.SimPosition + new Vector2(ToolBox.RandomFloatLocal(-0.5f, 0.5f), ToolBox.RandomFloatLocal(-0.5f, 0.5f)), Vector2.Zero); if (p!=null) p.Size *= 2.0f; Game1.particleManager.CreateParticle("bubbles", torso.SimPosition, - new Vector2(ToolBox.RandomFloat(-0.5f, 0.5f), ToolBox.RandomFloat(-1.0f,0.5f))); + new Vector2(ToolBox.RandomFloatLocal(-0.5f, 0.5f), ToolBox.RandomFloatLocal(-1.0f,0.5f))); } foreach (var joint in animController.limbJoints) diff --git a/Subsurface/Characters/Limb.cs b/Subsurface/Characters/Limb.cs index 39e57402f..d62f04b3f 100644 --- a/Subsurface/Characters/Limb.cs +++ b/Subsurface/Characters/Limb.cs @@ -314,7 +314,7 @@ namespace Subsurface Game1.particleManager.CreateParticle("blood", SimPosition, - particleVel * ToolBox.RandomFloat(1.0f, 3.0f)); + particleVel * ToolBox.RandomFloatLocal(1.0f, 3.0f)); } for (int i = 0; i < bloodAmount / 2; i++) diff --git a/Subsurface/Characters/Ragdoll.cs b/Subsurface/Characters/Ragdoll.cs index 48809eb49..932776d19 100644 --- a/Subsurface/Characters/Ragdoll.cs +++ b/Subsurface/Characters/Ragdoll.cs @@ -462,7 +462,7 @@ namespace Subsurface //limb isn't in any room -> it's in the water limb.inWater = true; } - else if (limbHull.Volume>0.0f && Map.RectContains(limbHull.Rect, limbPosition)) + else if (limbHull.Volume>0.0f && Submarine.RectContains(limbHull.Rect, limbPosition)) { if (limbPosition.Y < limbHull.Surface) diff --git a/Subsurface/Content/Items/Diving/divinggear.xml b/Subsurface/Content/Items/Diving/divinggear.xml index 0c5b41dc6..d3923ec3e 100644 --- a/Subsurface/Content/Items/Diving/divinggear.xml +++ b/Subsurface/Content/Items/Diving/divinggear.xml @@ -40,7 +40,7 @@ - + diff --git a/Subsurface/DebugConsole.cs b/Subsurface/DebugConsole.cs index 660b7f103..e1b5043e5 100644 --- a/Subsurface/DebugConsole.cs +++ b/Subsurface/DebugConsole.cs @@ -197,6 +197,10 @@ namespace Subsurface case "editchar": Game1.EditCharacterScreen.Select(); break; + case "freecamera": + Character.Controlled = null; + Game1.GameScreen.Cam.TargetPos = Vector2.Zero; + break; case "editwater": case "water": if (Game1.Client== null) @@ -205,7 +209,8 @@ namespace Subsurface } break; case "generatelevel": - Game1.Level = new Level(Game1.localRandom.Next(), 20, 500, 500); + Game1.Level = new Level(100, 500,500, 50); + Game1.Level.Generate(100.0f); break; case "fowenabled": case "fow": @@ -218,12 +223,12 @@ namespace Subsurface break; case "savemap": if (commands.Length < 2) break; - Map.SaveCurrent("Content/SavedMaps/" + commands[1]); + Submarine.SaveCurrent("Content/SavedMaps/" + commands[1]); NewMessage("map saved", Color.Green); break; case "loadmap": if (commands.Length < 2) break; - Map.Load("Content/SavedMaps/" + commands[1]); + Submarine.Load("Content/SavedMaps/" + commands[1]); break; case "savegame": SaveUtil.SaveGame(SaveUtil.SaveFolder+"save"); diff --git a/Subsurface/Events/MonsterEvent.cs b/Subsurface/Events/MonsterEvent.cs index 6702dc2f2..7544fddc2 100644 --- a/Subsurface/Events/MonsterEvent.cs +++ b/Subsurface/Events/MonsterEvent.cs @@ -32,8 +32,8 @@ namespace Subsurface for (int i = 0; i < amount; i++) { Vector2 position = (randomWayPoint == null) ? Vector2.Zero : randomWayPoint.SimPosition; - position.X += ToolBox.RandomFloat(-0.5f,0.5f); - position.Y += ToolBox.RandomFloat(-0.5f,0.5f); + position.X += ToolBox.RandomFloatLocal(-0.5f,0.5f); + position.Y += ToolBox.RandomFloatLocal(-0.5f,0.5f); monsters[i] = new Character(characterFile, position); } } diff --git a/Subsurface/GUI/GUI.cs b/Subsurface/GUI/GUI.cs index f53eedb3c..9408588f0 100644 --- a/Subsurface/GUI/GUI.cs +++ b/Subsurface/GUI/GUI.cs @@ -7,7 +7,13 @@ using Microsoft.Xna.Framework.Input; namespace Subsurface { [Flags] - public enum Alignment { CenterX = 1, Left = 2, Right = 4, CenterY = 8, Top = 16, Bottom = 32 } + public enum Alignment + { + CenterX = 1, Left = 2, Right = 4, CenterY = 8, Top = 16, Bottom = 32 , + TopRight = (Top | Right), TopLeft = (Top | Left), TopCenter = (CenterX | Top), + Center = (CenterX | CenterY), + BottomRight = (Bottom | Right), BottomLeft = (Bottom | Left), BottomCenter = (CenterX | Bottom) + } class GUIMessage @@ -258,15 +264,15 @@ namespace Subsurface public static void Draw(float deltaTime, SpriteBatch spriteBatch, Camera cam) { - //spriteBatch.DrawString(font, - // "FPS: " + (int)Game1.frameCounter.AverageFramesPerSecond - // + " - render: "+Game1.renderTimeElapsed, - // new Vector2(10, 10), Color.White); + spriteBatch.DrawString(font, + "FPS: " + (int)Game1.frameCounter.AverageFramesPerSecond + + " - render: " + Game1.renderTimeElapsed, + new Vector2(10, 10), Color.White); - //spriteBatch.DrawString(font, - // "Physics: " + Game1.world.UpdateTime - // + " - bodies: " + Game1.world.BodyList.Count, - // new Vector2(10, 30), Color.White); + spriteBatch.DrawString(font, + "Physics: " + Game1.world.UpdateTime + + " - bodies: " + Game1.world.BodyList.Count, + new Vector2(10, 30), Color.White); if (Character.Controlled != null && cam!=null) Character.Controlled.DrawHud(spriteBatch, cam); diff --git a/Subsurface/Game1.cs b/Subsurface/Game1.cs index 0dc69062c..847a4062d 100644 --- a/Subsurface/Game1.cs +++ b/Subsurface/Game1.cs @@ -15,10 +15,12 @@ namespace Subsurface /// class Game1 : Game { - GraphicsDeviceManager graphics; + public static GraphicsDeviceManager graphics; static int graphicsWidth, graphicsHeight; static SpriteBatch spriteBatch; + public static GraphicsDevice CurrGraphicsDevice; + public static FrameCounter frameCounter; public static readonly Version version = Assembly.GetEntryAssembly().GetName().Version; @@ -46,7 +48,7 @@ namespace Subsurface public static Random localRandom; public static Random random; - private Stopwatch renderTimer; + //private Stopwatch renderTimer; public static int renderTimeElapsed; @@ -55,6 +57,7 @@ namespace Subsurface get { return GameScreen.Cam; } } + public static int GraphicsWidth { get { return graphicsWidth; } @@ -68,7 +71,7 @@ namespace Subsurface public Game1() { graphics = new GraphicsDeviceManager(this); - + graphicsWidth = 1280; graphicsHeight = 720; @@ -76,13 +79,13 @@ namespace Subsurface graphics.PreferredBackBufferWidth = graphicsWidth; graphics.PreferredBackBufferHeight = graphicsHeight; Content.RootDirectory = "Content"; - + //graphics.SynchronizeWithVerticalRetrace = false; //graphics.ApplyChanges(); frameCounter = new FrameCounter(); - renderTimer = new Stopwatch(); + //renderTimer = new Stopwatch(); IsMouseVisible = true; @@ -107,6 +110,8 @@ namespace Subsurface { base.Initialize(); + CurrGraphicsDevice = GraphicsDevice; + particleManager = new ParticleManager("Content/Particles/prefabs.xml", Cam); GameMode.Init(); @@ -145,7 +150,7 @@ namespace Subsurface AmbientSoundManager.Init("Content/Sounds/Sounds.xml"); - Map.PreloadMaps("Content/SavedMaps"); + Submarine.Preload("Content/SavedMaps"); GameScreen = new GameScreen(graphics.GraphicsDevice); MainMenuScreen = new MainMenuScreen(this); LobbyScreen = new LobbyScreen(); @@ -209,7 +214,7 @@ namespace Subsurface /// protected override void Draw(GameTime gameTime) { - renderTimer.Restart(); + //renderTimer.Restart(); double deltaTime = gameTime.ElapsedGameTime.TotalSeconds; @@ -217,8 +222,8 @@ namespace Subsurface Screen.Selected.Draw(deltaTime, GraphicsDevice, spriteBatch); - renderTimeElapsed = (int)renderTimer.Elapsed.Ticks; - renderTimer.Stop(); + //renderTimeElapsed = (int)renderTimer.Elapsed.Ticks; + //renderTimer.Stop(); } protected override void OnExiting(object sender, EventArgs args) diff --git a/Subsurface/GameSession/GameSession.cs b/Subsurface/GameSession/GameSession.cs index 6762a6f45..e2dab348a 100644 --- a/Subsurface/GameSession/GameSession.cs +++ b/Subsurface/GameSession/GameSession.cs @@ -27,15 +27,29 @@ namespace Subsurface private string savePath; - private Map selectedMap; + private Submarine submarine; - public GameSession(Map selectedMap, GameModePreset gameModePreset) - :this(selectedMap, gameModePreset.Instantiate()) + private Level level; + + public Map map; + + public Level Level + { + get { return level; } + } + + public Submarine Submarine + { + get { return submarine; } + } + + public GameSession(Submarine submarine, GameModePreset gameModePreset) + :this(submarine, gameModePreset.Instantiate()) { } - public GameSession(Map selectedMap, GameMode gameMode = null) + public GameSession(Submarine selectedMap, GameMode gameMode = null) { taskManager = new TaskManager(this); @@ -43,6 +57,8 @@ namespace Subsurface guiRoot = new GUIFrame(new Rectangle(0,0,Game1.GraphicsWidth,Game1.GraphicsWidth), Color.Transparent); + map = new Map(Game1.random.Next(), 500); + int width = 350, height = 100; if (Game1.Client!=null || Game1.Server!=null) { @@ -67,7 +83,7 @@ namespace Subsurface //startTime = DateTime.Now; //endTime = startTime + gameDuration; - this.selectedMap = selectedMap; + this.submarine = selectedMap; //if (!save) return; @@ -75,7 +91,7 @@ namespace Subsurface } - public GameSession(Map selectedMap, string savePath, string filePath) + public GameSession(Submarine selectedMap, string savePath, string filePath) : this(selectedMap) { XDocument doc = ToolBox.TryLoadXml(filePath); @@ -95,14 +111,31 @@ namespace Subsurface this.savePath = savePath; } + public void StartShift(TimeSpan duration, Level level) + { + + if (Submarine.Loaded != submarine) submarine.Load(); + + level.Generate(submarine==null ? 100.0f : Math.Max(Submarine.Borders.Width, Submarine.Borders.Height)); + + this.level = level; + + StartShift(duration, 1); + } + public void StartShift(TimeSpan duration, int scriptedEventCount = 1) { //if (crewManager.characterInfos.Count == 0) return; - if (Map.Loaded!=selectedMap) selectedMap.Load(); + if (Submarine.Loaded!=submarine) submarine.Load(); if (gameMode!=null) gameMode.Start(duration); + if (level!=null) + { + submarine.Move(level.StartPosition - submarine.Center, 1.0f); + } + //crewManager.StartShift(); taskManager.StartShift(scriptedEventCount); } @@ -122,7 +155,7 @@ namespace Subsurface SaveUtil.SaveGame(savePath); } - + taskManager.EndShift(); //gameMode.End(); diff --git a/Subsurface/Items/Components/Door.cs b/Subsurface/Items/Components/Door.cs index aa6bd1cbd..459cba8cd 100644 --- a/Subsurface/Items/Components/Door.cs +++ b/Subsurface/Items/Components/Door.cs @@ -215,7 +215,7 @@ namespace Subsurface.Items.Components { base.Move(amount); - LinkedGap.Move(amount); + //LinkedGap.Move(amount); body.SetTransform(body.Position + ConvertUnits.ToSimUnits(amount), 0.0f); diff --git a/Subsurface/Items/Components/MiniMap.cs b/Subsurface/Items/Components/MiniMap.cs index aa41886c1..1f501484d 100644 --- a/Subsurface/Items/Components/MiniMap.cs +++ b/Subsurface/Items/Components/MiniMap.cs @@ -41,12 +41,12 @@ namespace Subsurface.Items.Components Rectangle miniMap = new Rectangle(x + 20, y + 40, width - 40, height - 60); - float size = Math.Min((float)miniMap.Width / (float)Map.Borders.Width, (float)miniMap.Height / (float)Map.Borders.Height); + float size = Math.Min((float)miniMap.Width / (float)Submarine.Borders.Width, (float)miniMap.Height / (float)Submarine.Borders.Height); foreach (Hull hull in Hull.hullList) { Rectangle hullRect = new Rectangle( - miniMap.X + (int)((hull.Rect.X - Map.Borders.X) * size), - miniMap.Y - (int)((hull.Rect.Y - Map.Borders.Y) * size), + miniMap.X + (int)((hull.Rect.X - Submarine.Borders.X) * size), + miniMap.Y - (int)((hull.Rect.Y - Submarine.Borders.Y) * size), (int)(hull.Rect.Width * size), (int)(hull.Rect.Height * size)); @@ -71,8 +71,8 @@ namespace Subsurface.Items.Components if (c.animController.CurrentHull!=null) continue; Rectangle characterRect = new Rectangle( - miniMap.X + (int)((c.Position.X - Map.Borders.X) * size), - miniMap.Y - (int)((c.Position.Y - Map.Borders.Y) * size), + miniMap.X + (int)((c.Position.X - Submarine.Borders.X) * size), + miniMap.Y - (int)((c.Position.Y - Submarine.Borders.Y) * size), 5, 5); GUI.DrawRectangle(spriteBatch, characterRect, Color.White, true); diff --git a/Subsurface/Items/Components/RepairTool.cs b/Subsurface/Items/Components/RepairTool.cs index 9571498ba..f781bbf33 100644 --- a/Subsurface/Items/Components/RepairTool.cs +++ b/Subsurface/Items/Components/RepairTool.cs @@ -109,8 +109,8 @@ namespace Subsurface.Items.Components ignoredBodies.Add(limb.body.FarseerBody); } - Body targetBody = Map.PickBody(TransformedBarrelPos, targetPosition, ignoredBodies); - pickedPosition = Map.LastPickedPosition; + Body targetBody = Submarine.PickBody(TransformedBarrelPos, targetPosition, ignoredBodies); + pickedPosition = Submarine.LastPickedPosition; if (targetBody==null || targetBody.UserData==null) return true; diff --git a/Subsurface/Items/Components/Signal/ConnectionPanel.cs b/Subsurface/Items/Components/Signal/ConnectionPanel.cs index 884c82285..a331234a5 100644 --- a/Subsurface/Items/Components/Signal/ConnectionPanel.cs +++ b/Subsurface/Items/Components/Signal/ConnectionPanel.cs @@ -6,8 +6,7 @@ using System.Xml.Linq; namespace Subsurface.Items.Components { class ConnectionPanel : ItemComponent - { - + { public List connections; Character user; @@ -31,10 +30,20 @@ namespace Subsurface.Items.Components } } - public override void Move(Vector2 amount) - { - base.Move(amount); - } + //public override void Move(Vector2 amount) + //{ + // base.Move(amount); + + // foreach (Connection c in connections) + // { + // foreach (Wire w in c.wires) + // { + // if (w == null) continue; + + // w.Move + // } + // } + //} public override void DrawHUD(SpriteBatch spriteBatch, Character character) { diff --git a/Subsurface/Items/Components/Signal/Wire.cs b/Subsurface/Items/Components/Signal/Wire.cs index b2ce301cf..f00bc8503 100644 --- a/Subsurface/Items/Components/Signal/Wire.cs +++ b/Subsurface/Items/Components/Signal/Wire.cs @@ -35,6 +35,15 @@ namespace Subsurface.Items.Components connections = new Connection[2]; } + public override void Move(Vector2 amount) + { + amount = FarseerPhysics.ConvertUnits.ToDisplayUnits(amount); + for (int i = 0; i h.Rect.X && rect.X + rect.Width < h.Rect.X+h.Rect.Width && @@ -250,20 +250,20 @@ namespace Subsurface pos.Y = ConvertUnits.ToSimUnits(MathHelper.Clamp(lowerSurface, rect.Y-rect.Height, rect.Y)); Game1.particleManager.CreateParticle("watersplash", - new Vector2(pos.X, pos.Y - ToolBox.RandomFloat(0.0f, 0.1f)), - new Vector2(flowForce.X * ToolBox.RandomFloat(0.005f, 0.007f), flowForce.Y * ToolBox.RandomFloat(0.005f, 0.007f))); + new Vector2(pos.X, pos.Y - ToolBox.RandomFloatLocal(0.0f, 0.1f)), + new Vector2(flowForce.X * ToolBox.RandomFloatLocal(0.005f, 0.007f), flowForce.Y * ToolBox.RandomFloatLocal(0.005f, 0.007f))); - pos.Y = ConvertUnits.ToSimUnits(ToolBox.RandomFloat(lowerSurface, rect.Y - rect.Height)); + pos.Y = ConvertUnits.ToSimUnits(ToolBox.RandomFloatLocal(lowerSurface, rect.Y - rect.Height)); Game1.particleManager.CreateParticle("bubbles", pos, flowForce / 200.0f); } else { pos.Y += Math.Sign(flowForce.Y) * ConvertUnits.ToSimUnits(rect.Height / 2.0f); - for (int i = 0; i < rect.Width; i += (int)ToolBox.RandomFloat(80, 100)) + for (int i = 0; i < rect.Width; i += (int)ToolBox.RandomFloatLocal(80, 100)) { - pos.X = ConvertUnits.ToSimUnits(ToolBox.RandomFloat(rect.X, rect.X+rect.Width)); + pos.X = ConvertUnits.ToSimUnits(ToolBox.RandomFloatLocal(rect.X, rect.X+rect.Width)); Subsurface.Particles.Particle splash = Game1.particleManager.CreateParticle("watersplash", pos, - new Vector2(flowForce.X * ToolBox.RandomFloat(0.005f, 0.008f), flowForce.Y * ToolBox.RandomFloat(0.005f, 0.008f))); + new Vector2(flowForce.X * ToolBox.RandomFloatLocal(0.005f, 0.008f), flowForce.Y * ToolBox.RandomFloatLocal(0.005f, 0.008f))); if (splash!=null) splash.Size = splash.Size * MathHelper.Clamp(rect.Width / 50.0f, 0.8f, 4.0f); diff --git a/Subsurface/Map/Hull.cs b/Subsurface/Map/Hull.cs index 5cc7c7af6..c35bb3cf0 100644 --- a/Subsurface/Map/Hull.cs +++ b/Subsurface/Map/Hull.cs @@ -150,8 +150,8 @@ namespace Subsurface public override bool Contains(Vector2 position) { - return (Map.RectContains(rect, position) && - !Map.RectContains(new Rectangle(rect.X + 8, rect.Y - 8, rect.Width - 16, rect.Height - 16), position)); + return (Submarine.RectContains(rect, position) && + !Submarine.RectContains(new Rectangle(rect.X + 8, rect.Y - 8, rect.Width - 16, rect.Height - 16), position)); } public int GetWaveIndex(Vector2 position) @@ -189,7 +189,7 @@ namespace Subsurface if (EditWater) { Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); - if (Map.RectContains(rect, position)) + if (Submarine.RectContains(rect, position)) { if (PlayerInput.LeftButtonDown()) { @@ -210,7 +210,7 @@ namespace Subsurface for (int i = 0; i < waveY.Length; i++) { float maxDelta = Math.Max(Math.Abs(rightDelta[i]), Math.Abs(leftDelta[i])); - if (maxDelta > ToolBox.RandomFloat(0.2f,10.0f)) + if (maxDelta > ToolBox.RandomFloatLocal(0.2f,10.0f)) { Game1.particleManager.CreateParticle("mist", ConvertUnits.ToSimUnits(new Vector2(rect.X + WaveWidth * i,surface + waveY[i])), @@ -390,12 +390,12 @@ namespace Subsurface { if (guess != null && hullList.Contains(guess)) { - if (Map.RectContains(guess.rect, position)) return guess; + if (Submarine.RectContains(guess.rect, position)) return guess; } foreach (Hull w in hullList) { - if (Map.RectContains(w.rect, position)) return w; + if (Submarine.RectContains(w.rect, position)) return w; } return null; diff --git a/Subsurface/Map/Level.cs b/Subsurface/Map/Level.cs index 0c6715bfe..9851ce1f0 100644 --- a/Subsurface/Map/Level.cs +++ b/Subsurface/Map/Level.cs @@ -6,109 +6,548 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Text; +using System.Diagnostics; using Voronoi2; namespace Subsurface { class Level { + public static Level Loaded + { + get { return loaded; } + } + + static Level loaded; + private int seed; - List bodies; + private int siteInterval; + + const int gridCellWidth = 1000; + List[,] cellGrid; + + //List bodies; List cells; - public Level(int seed, int siteCount, int width, int height) + BasicEffect basicEffect; + + private VertexPositionColor[] vertices; + private VertexBuffer vertexBuffer; + + private Vector2 startPosition; + private Vector2 endPosition; + + Rectangle borders; + + public Vector2 StartPosition + { + get { return startPosition; } + } + + public Level(int seed, int width, int height, int siteInterval) { this.seed = seed; - Voronoi voronoi = new Voronoi(1.0); + this.siteInterval = siteInterval; - List sites = new List(); - Random rand = new Random(seed); + borders = new Rectangle(0, 0, width, height); + } - for (int i = 0; i < siteCount; i++) + public static Level CreateRandom() + { + return new Level(100, 100000, 40000, 2000); + } + + public void Generate(float minWidth) + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + + Game1.random = new Random(seed); + + if (loaded != this && loaded != null) { - sites.Add(new PointF((float)(rand.NextDouble() * width), (float)(rand.NextDouble() * width))); + loaded.Unload(); } - List graphEdges = MakeVoronoiGraph(sites, voronoi, width, height); + loaded = this; + Voronoi voronoi = new Voronoi(1.0); + + List sites = new List(); + Random rand = new Random(seed); + + float siteVariance = siteInterval * 0.8f; + for (int x = siteInterval/2; x < borders.Width; x += siteInterval) + { + for (int y = siteInterval / 2; y < borders.Height; y += siteInterval) + { + sites.Add(new Vector2( + x + (float)(Game1.random.NextDouble() - 0.5) * siteVariance, + y + (float)(Game1.random.NextDouble() - 0.5) * siteVariance)); + } + } + + Stopwatch sw2 = new Stopwatch(); + sw2.Start(); + + List graphEdges = voronoi.MakeVoronoiGraph(sites, borders.Width, borders.Height); + + + Debug.WriteLine("MakeVoronoiGraph: " + sw2.ElapsedMilliseconds + " ms"); + sw2.Restart(); + + cellGrid = new List[borders.Width / gridCellWidth, borders.Height / gridCellWidth]; + for (int x = 0; x < borders.Width / gridCellWidth; x++) + { + for (int y = 0; y < borders.Height / gridCellWidth; y++) + { + cellGrid[x, y] = new List(); + } + } + + //construct voronoi cells based on the graph edges cells = new List(); foreach (GraphEdge ge in graphEdges) { - for (int i = 0; i<2; i++) + for (int i = 0; i < 2; i++) { - Site site = (i==0) ? ge.site1 : ge.site2; - VoronoiCell cell = cells.Find(c => c.site == site); + Site site = (i == 0) ? ge.site1 : ge.site2; + + VoronoiCell cell = cellGrid[ + (int)Math.Floor(site.coord.x / gridCellWidth), + (int)Math.Floor(site.coord.y / gridCellWidth)].Find(c => c.site == site); + if (cell == null) { cell = new VoronoiCell(site); + cellGrid[(int)Math.Floor(cell.Center.X / gridCellWidth), (int)Math.Floor(cell.Center.Y / gridCellWidth)].Add(cell); cells.Add(cell); } - if (!cell.edges.Contains(ge)) cell.edges.Add(ge); + + if (ge.cell1 == null) + { + ge.cell1 = cell; + } + else + { + ge.cell2 = cell; + } + cell.edges.Add(ge); + } + } + + Debug.WriteLine("find cells: " + sw2.ElapsedMilliseconds + " ms"); + sw2.Restart(); + + //generate a path from the left edge of the map to right edge + Rectangle pathBorders = new Rectangle( + borders.X + (int)minWidth, borders.Y + (int)minWidth, + borders.Right - (int)minWidth, borders.Y + borders.Height - (int)minWidth); + + List pathCells = GeneratePath( + new Vector2((int)minWidth, Game1.random.Next((int)minWidth, borders.Height - (int)minWidth)), + new Vector2(borders.Width - (int)minWidth, Game1.random.Next((int)minWidth, borders.Height - (int)minWidth)), + cells, pathBorders, minWidth); + + + //generate a couple of random paths + for (int i = 0; i < Game1.random.Next() % 3; i++ ) + { + pathBorders = new Rectangle( + borders.X + siteInterval * 2, borders.Y - siteInterval * 2, + borders.Right - siteInterval * 2, borders.Y + borders.Height - siteInterval * 2); + + Vector2 start = pathCells[Game1.random.Next(1,pathCells.Count-2)].Center; + Vector2 end = new Vector2(ToolBox.RandomFloat(pathBorders.X, pathBorders.Right), ToolBox.RandomFloat(pathBorders.Y, pathBorders.Bottom)); + + pathCells.AddRange + ( + GeneratePath( start,end, cells, pathBorders, 0.0f) + ); + } + + Debug.WriteLine("path: " + sw2.ElapsedMilliseconds + " ms"); + sw2.Restart(); + + startPosition = pathCells[0].Center; + endPosition = pathCells[pathCells.Count - 1].Center; + + foreach (VoronoiCell cell in pathCells) + { + cells.Remove(cell); + } + + GenerateLevel(cells); + + Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms"); + sw2.Restart(); + + vertexBuffer = new VertexBuffer(Game1.CurrGraphicsDevice, VertexPositionColor.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly); + + vertexBuffer.SetData(vertices); + + basicEffect = new BasicEffect(Game1.CurrGraphicsDevice); + basicEffect.VertexColorEnabled = true; + + Debug.WriteLine("Generated a map with "+sites.Count+" sites in "+sw.ElapsedMilliseconds+" ms"); + } + + private List GeneratePath(Vector2 start, Vector2 end, List cells, Microsoft.Xna.Framework.Rectangle limits, float minWidth, float wanderAmount = 0.3f) + { + + Stopwatch sw2 = new Stopwatch(); + sw2.Start(); + + //how heavily the path "steers" towards the endpoint + //lower values will cause the path to "wander" more, higher will make it head straight to the end + wanderAmount = MathHelper.Clamp(wanderAmount, 0.0f, 1.0f); + + List pathCells = new List(); + + VoronoiCell currentCell = cells[FindCellIndex(start)]; + pathCells.Add(currentCell); + + VoronoiCell endCell = cells[FindCellIndex(end)]; + + do + { + int edgeIndex = 0; + + //steer towards target + if (Game1.random.NextDouble()>wanderAmount) + { + for (int i = 0; i < currentCell.edges.Count; i++) + { + if (!IsIntersecting(currentCell.Center, end, currentCell.edges[i].point1, currentCell.edges[i].point2)) continue; + edgeIndex = i; + break; + } + } + //choose random edge (ignoring ones where the adjacent cell is outside limits) + else + { + List allowedEdges = new List(); + + foreach(GraphEdge edge in currentCell.edges) + { + if (!limits.Contains(edge.AdjacentCell(currentCell).Center)) continue; + + allowedEdges.Add(edge); + } + edgeIndex = (allowedEdges.Count==0) ? + 0 : currentCell.edges.IndexOf(allowedEdges[Game1.random.Next() % allowedEdges.Count]); + } + + currentCell = currentCell.edges[edgeIndex].AdjacentCell(currentCell); + + + pathCells.Add(currentCell); + + } while (currentCell!=endCell); + + Debug.WriteLine("genpath: " + sw2.ElapsedMilliseconds + " ms"); + sw2.Restart(); + + List removedCells = GetTooCloseCells(pathCells, minWidth); + foreach (VoronoiCell removedCell in removedCells) + { + if (pathCells.Contains(removedCell)) continue; + pathCells.Add(removedCell); + } + + Debug.WriteLine("gettooclose: " + sw2.ElapsedMilliseconds + " ms"); + sw2.Restart(); + + return pathCells; + } + + private List GetTooCloseCells(List emptyCells, float minDistance) + { + List tooCloseCells = new List(); + + Vector2 position = emptyCells[0].Center; + + if (minDistance == 0.0f) return tooCloseCells; + + float step = 100.0f; + + int targetCellIndex = 1; + + minDistance *= 0.5f; + do + { + for (int x = -1; x<=1; x++) + { + for (int y = -1; y <= 1; y++) + { + if (x == 0 && y == 0) continue; + Vector2 cornerPos = position + new Vector2(x*minDistance, y*minDistance); + + int cellIndex = FindCellIndex(cornerPos); + if (cellIndex == -1) continue; + if (!tooCloseCells.Contains(cells[cellIndex])) + { + tooCloseCells.Add(cells[cellIndex]); + } + } + } + + position += Vector2.Normalize(emptyCells[targetCellIndex].Center - position) * step; + + if (Vector2.Distance(emptyCells[targetCellIndex].Center, position) < step * 2.0f) targetCellIndex++; + + } while (Vector2.Distance(position, emptyCells[emptyCells.Count - 1].Center) > step*2.0f); + + return tooCloseCells; + } + + /// + /// check whether line from a to b is intersecting with line from c to b + /// + bool IsIntersecting(Vector2 a, Vector2 b, Vector2 c, Vector2 d) + { + float denominator = ((b.X - a.X) * (d.Y - c.Y)) - ((b.Y - a.Y) * (d.X - c.X)); + float numerator1 = ((a.Y - c.Y) * (d.X - c.X)) - ((a.X - c.X) * (d.Y - c.Y)); + float numerator2 = ((a.Y - c.Y) * (b.X - a.X)) - ((a.X - c.X) * (b.Y - a.Y)); + + if (denominator == 0) return numerator1 == 0 && numerator2 == 0; + + float r = numerator1 / denominator; + float s = numerator2 / denominator; + + return (r >= 0 && r <= 1) && (s >= 0 && s <= 1); + } + + /// + /// find the index of the cell which the point is inside + /// (actually finds the cell whose center is closest, but it's always the correct cell assuming the point is inside the borders of the diagram) + /// + private int FindCellIndex(Vector2 position) + { + float closestDist = 0.0f; + VoronoiCell closestCell = null; + + int gridPosX = (int)Math.Floor(position.X / gridCellWidth); + int gridPosY = (int)Math.Floor(position.Y / gridCellWidth); + + int searchOffset = 1; + + for (int x = Math.Max(gridPosX-searchOffset,0); x<=Math.Min(gridPosX+searchOffset, cellGrid.GetLength(0)-1); x++) + { + for (int y = Math.Max(gridPosY-searchOffset,0); y<=Math.Min(gridPosY+searchOffset, cellGrid.GetLength(1)-1); y++) + { + for (int i = 0; i < cellGrid[x,y].Count; i++) + { + float dist = Vector2.Distance(cellGrid[x, y][i].Center, position); + if (closestDist != 0.0f && dist > closestDist) continue; + + closestDist = dist; + closestCell = cellGrid[x, y][i]; + } } } - bodies = new List(); + + return cells.IndexOf(closestCell); + } + + private void GenerateLevel(List cells) + { + List verticeList = new List(); + //bodies = new List(); + + + List tempVertices = new List(); + + int n = 0; foreach (VoronoiCell cell in cells) { - //List of vectors defining my custom poly - List vlist = new List(); + n = (n + 30) % 255; + + tempVertices.Clear(); foreach (GraphEdge ge in cell.edges) { - if (!vlist.Contains(ge.point1)) vlist.Add(ge.point1); - if (!vlist.Contains(ge.point2)) vlist.Add(ge.point2); + if (ge.point1 == ge.point2) continue; + if (!tempVertices.Contains(ge.point1)) tempVertices.Add(ge.point1); + if (!tempVertices.Contains(ge.point2)) tempVertices.Add(ge.point2); } - if (vlist.Count < 3) continue; + if (tempVertices.Count < 3) continue; + + int triangleCount = tempVertices.Count - 2; - for (int i = 0; i < vlist.Count; i++ ) + tempVertices.Sort(new CompareCCW(cell.Center)); + + int lastIndex = 1; + for (int i = 0; i < triangleCount; i++ ) { - vlist[i] = ConvertUnits.ToSimUnits(vlist[i]); + List triangleVertices = new List(); + + triangleVertices.Add(tempVertices[0]); + for (int j = lastIndex; j<=lastIndex+1; j++) + { + triangleVertices.Add(tempVertices[j]); + } + lastIndex += 1; + + foreach (Vector2 vertex in triangleVertices) + { + verticeList.Add(new VertexPositionColor(new Vector3(vertex, 0.0f), Color.LightGray*0.8f));//new Color(n,(n*2)%255,(n*3)%255)*0.5f)); + } + + bool isSame = false; + if (triangleVertices[0].Y == triangleVertices[1].Y && triangleVertices[1].Y == triangleVertices[2].Y) isSame = true; + if (triangleVertices[0].X == triangleVertices[1].X && triangleVertices[1].X == triangleVertices[2].X) isSame = true; + + if (isSame) continue; + + CreateBody(cell, triangleVertices); } - //get farseer 'vertices' from vectors - Vertices _shapevertices = new Vertices(vlist); - _shapevertices.Sort(new CompareCCW(cell.Center)); - - //feed vertices array to BodyFactory.CreatePolygon to get a new farseer polygonal body - Body _newBody = BodyFactory.CreatePolygon(Game1.world, _shapevertices, 15); - _newBody.BodyType = BodyType.Static; - _newBody.CollisionCategories = Physics.CollisionWall; - - bodies.Add(_newBody); } + + vertices = verticeList.ToArray(); + + //return bodies; } - List MakeVoronoiGraph(List sites, Voronoi voronoi, int width, int height) + private void CreateBody(VoronoiCell cell, List bodyVertices) { - double[] xVal = new double[sites.Count]; - double[] yVal = new double[sites.Count]; - for (int i = 0; i < sites.Count; i++) + for (int i = 0; i < bodyVertices.Count; i++) { - xVal[i] = sites[i].X; - yVal[i] = sites[i].Y; + bodyVertices[i] = ConvertUnits.ToSimUnits(bodyVertices[i]); } - return voronoi.generateVoronoi(xVal, yVal, 0, width, 0, height); + //get farseer 'vertices' from vectors + Vertices _shapevertices = new Vertices(bodyVertices); + //_shapevertices.Sort(new CompareCCW(cell.Center)); + + //feed vertices array to BodyFactory.CreatePolygon to get a new farseer polygonal body + Body _newBody = BodyFactory.CreatePolygon(Game1.world, _shapevertices, 15); + _newBody.BodyType = BodyType.Static; + _newBody.CollisionCategories = Physics.CollisionWall; + + cell.bodies.Add(_newBody); } - public void Render(SpriteBatch spriteBatch) + + Vector2 position; + public void Move(Vector2 amount, float deltaTime) { + amount = amount * deltaTime; + position += amount; + + amount = ConvertUnits.ToSimUnits(amount); + foreach (VoronoiCell cell in cells) + { + foreach (Body b in cell.bodies) + { + b.SetTransform(b.Position+amount, b.Rotation); + } + } + + } + + + + public void SetObserverPosition(Vector2 position) + { + position = position - this.position; + int gridPosX = (int)Math.Floor(position.X / gridCellWidth); + int gridPosY = (int)Math.Floor(position.Y / gridCellWidth); + int searchOffset = 1; + + for (int x = 0; x < cellGrid.GetLength(0); x++) + { + for (int y = 0; y (PrimitiveType.TriangleList, vertices, 0, (int)Math.Floor(vertices.Length / 3.0f)); + } + + private void Unload() + { + foreach (VoronoiCell cell in cells) + { + foreach (Body b in cell.bodies) + { + Game1.world.RemoveBody(b); + } + } + + + //bodies = null; + + vertices = null; + + cells = null; + + vertexBuffer.Dispose(); + vertexBuffer = null; + } + } + class CompareCCW : IComparer { private Vector2 center; @@ -119,24 +558,24 @@ namespace Subsurface } public int Compare(Vector2 a, Vector2 b) { - if (a.X - center.X >= 0 && b.X - center.X < 0) return 1; - if (a.X - center.X < 0 && b.X - center.X >= 0) return -1; + if (a.X - center.X >= 0 && b.X - center.X < 0) return -1; + if (a.X - center.X < 0 && b.X - center.X >= 0) return 1; if (a.X - center.X == 0 && b.X - center.X == 0) { - if (a.Y - center.Y >= 0 || b.Y - center.Y >= 0) return Math.Sign(a.Y - b.Y); - return Math.Sign(b.Y - a.Y); + if (a.Y - center.Y >= 0 || b.Y - center.Y >= 0) return Math.Sign(b.Y-a.Y); + return Math.Sign(a.Y-b.Y); } // compute the cross product of vectors (center -> a) x (center -> b) float det = (a.X - center.X) * (b.Y - center.Y) - (b.X - center.X) * (a.Y - center.Y); - if (det < 0) return 1; - if (det > 0) return -1; + if (det < 0) return -1; + if (det > 0) return 1; // points a and b are on the same line from the center // check which point is closer to the center float d1 = (a.X - center.X) * (a.X - center.X) + (a.Y - center.Y) * (a.Y - center.Y); float d2 = (b.X - center.X) * (b.X - center.X) + (b.Y - center.Y) * (b.Y - center.Y); - return Math.Sign(d1 - d2); + return Math.Sign(d2-d1); } } diff --git a/Subsurface/Map/Location.cs b/Subsurface/Map/Location.cs new file mode 100644 index 000000000..f71f91d16 --- /dev/null +++ b/Subsurface/Map/Location.cs @@ -0,0 +1,37 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Subsurface +{ + class Location + { + string name; + + Vector2 mapPosition; + + public string Name + { + get { return name; } + } + + public Vector2 MapPosition + { + get { return mapPosition; } + } + + public Location(string name, Vector2 mapPosition) + { + this.name = name; + + this.mapPosition = mapPosition; + } + + public static Location CreateRandom(Vector2 position) + { + return new Location("Location " + (Game1.random.Next() % 10000), position); + } + } +} diff --git a/Subsurface/Map/Map.cs b/Subsurface/Map/Map.cs index 0ac4f41f1..91216ace7 100644 --- a/Subsurface/Map/Map.cs +++ b/Subsurface/Map/Map.cs @@ -1,539 +1,239 @@ -using FarseerPhysics; -using FarseerPhysics.Collision; -using FarseerPhysics.Dynamics; -using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Reflection; -using System.Xml.Linq; +using System.Text; +using Voronoi2; namespace Subsurface { - public enum Direction : byte - { - None = 0, Left = 1, Right = 2 - } - class Map { - static string MapFolder; - MapHash mapHash; + private List levels; - public static List SavedMaps = new List(); + private List locations; - private static Map loaded; - - //public static Map Loaded - //{ - // get { return loaded; } - // set { loaded = value; } - //} - - - public static readonly Vector2 gridSize = new Vector2(16.0f, 16.0f); - - private static Vector2 lastPickedPosition; - private static float lastPickedFraction; - - private Rectangle borders; - - private string filePath; - private string name; - - public string Name - { - get { return name; } - } - - public static Vector2 LastPickedPosition - { - get { return lastPickedPosition; } - } - - public static float LastPickedFraction - { - get { return lastPickedFraction; } - } - - public MapHash MapHash - { - get - { - if (mapHash != null) return mapHash; - - XDocument doc = OpenDoc(filePath); - mapHash = new MapHash(doc); - - return mapHash; - } - } - - public static Map Loaded - { - get { return loaded; } - } - - public static Rectangle Borders - { - get - { - return (loaded==null) ? Rectangle.Empty : loaded.borders; - } - } - - public string FilePath - { - get { return filePath; } - } - - public static void Draw(SpriteBatch spriteBatch, bool editing = false) - { - for (int i = 0; i < MapEntity.mapEntityList.Count(); i++ ) - { - MapEntity.mapEntityList[i].Draw(spriteBatch, editing); - } - } - - public static void DrawFront(SpriteBatch spriteBatch, bool editing = false) - { - for (int i = 0; i < MapEntity.mapEntityList.Count(); i++) - { - if (MapEntity.mapEntityList[i].sprite == null || MapEntity.mapEntityList[i].sprite.Depth < 0.5f) - MapEntity.mapEntityList[i].Draw(spriteBatch, editing); - } - } - - public static void DrawBack(SpriteBatch spriteBatch, bool editing = false) - { - - for (int i = 0; i < MapEntity.mapEntityList.Count(); i++) - { - if (MapEntity.mapEntityList[i].sprite == null || MapEntity.mapEntityList[i].sprite.Depth >= 0.5f) - MapEntity.mapEntityList[i].Draw(spriteBatch, editing); - } - } - - public static Vector2 MouseToWorldGrid(Camera cam) - { - Vector2 position = new Vector2(PlayerInput.GetMouseState.X, PlayerInput.GetMouseState.Y); - position = cam.ScreenToWorld(position); - - return VectorToWorldGrid(position); - } - - public static Vector2 VectorToWorldGrid(Vector2 position) - { - position.X = (float)Math.Floor(Convert.ToDouble(position.X / gridSize.X)) * gridSize.X; - position.Y = (float)Math.Ceiling(Convert.ToDouble(position.Y / gridSize.Y)) * gridSize.Y; - - return position; - } - - - public static Rectangle AbsRect(Vector2 pos, Vector2 size) - { - if (size.X < 0.0f) - { - pos.X += size.X; - size.X = -size.X; - } - if (size.Y < 0.0f) - { - pos.Y -= size.Y; - size.Y = -size.Y; - } - - return new Rectangle((int)pos.X, (int)pos.Y, (int)size.X, (int)size.Y); - } - - public static bool RectContains(Rectangle rect, Vector2 pos) - { - return (pos.X > rect.X && pos.X < rect.X + rect.Width - && pos.Y < rect.Y && pos.Y > rect.Y - rect.Height); - } - - public static bool RectsOverlap(Rectangle rect1, Rectangle rect2, bool inclusive=true) - { - if (inclusive) - { - return !(rect1.X > rect2.X + rect2.Width || rect1.X + rect1.Width < rect2.X || - rect1.Y < rect2.Y - rect2.Height || rect1.Y - rect1.Height > rect2.Y); - } - else - { - return !(rect1.X >= rect2.X + rect2.Width || rect1.X + rect1.Width <= rect2.X || - rect1.Y <= rect2.Y - rect2.Height || rect1.Y - rect1.Height >= rect2.Y); - } - - } + private List connections; - public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, List ignoredBodies = null) + private int seed; + private int size; + + private Location currentLocation; + private Location selectedLocation; + + public Map(int seed, int size) { - float closestFraction = 1.0f; - Body closestBody = null; - Game1.world.RayCast((fixture, point, normal, fraction) => - { - if (fixture == null || fixture.CollisionCategories == Category.None) return -1; - if (ignoredBodies != null && ignoredBodies.Contains(fixture.Body)) return -1; + this.seed = seed; - Structure structure = fixture.Body.UserData as Structure; - if (structure != null && (structure.IsPlatform || !structure.HasBody)) return -1; + this.size = size; - if (fraction < closestFraction) - { - closestFraction = fraction; - if (fixture.Body!=null) closestBody = fixture.Body; - } - return fraction; - } - , rayStart, rayEnd); + levels = new List(); - lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction; - lastPickedFraction = closestFraction; - return closestBody; - } + locations = new List(); + connections = new List(); - public static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd) - { - Body closestBody = null; - float closestFraction = 1.0f; + GenerateLocations(); - if (Vector2.Distance(rayStart,rayEnd)<0.01f) - { - closestFraction = 0.01f; - return null; - } - - Game1.world.RayCast((fixture, point, normal, fraction) => - { - if (fixture == null || fixture.CollisionCategories != Physics.CollisionWall) return -1; - - Structure structure = fixture.Body.UserData as Structure; - if (structure != null) - { - if (structure.IsPlatform || structure.StairDirection != Direction.None) return -1; - int sectionIndex = structure.FindSectionIndex(ConvertUnits.ToDisplayUnits(point)); - if (sectionIndex > -1 && structure.SectionHasHole(sectionIndex)) return -1; - } - - if (fraction < closestFraction) - { - closestBody = fixture.Body; - closestFraction = fraction; - } - return closestFraction; - } - , rayStart, rayEnd); - - - lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction; - lastPickedFraction = closestFraction; - return closestBody; - } - - public static Body PickBody(Vector2 point) - { - Body foundBody = null; - AABB aabb = new AABB(point, point); - - Game1.world.QueryAABB(p => - { - foundBody = p.Body; - - return true; - - }, ref aabb); - - return foundBody; - } - - public static bool InsideWall(Vector2 point) - { - Body foundBody = PickBody(point); - if (foundBody==null) return false; - - Structure wall = foundBody.UserData as Structure; - if (wall == null || wall.IsPlatform) return false; - - return true; - } - - - public void Save() - { - SaveAs(filePath); - } - - public void SaveAs(string filePath) - { - //if (filePath=="") + //for (int i = 0; i<10; i++) //{ - // DebugConsole.ThrowError("No save file selected"); - // return; + // Vector2 pos = new Vector2((float)Game1.random.NextDouble() * size, (float)Game1.random.NextDouble() * size); + + // Location location = + // locations.Add(location); //} - XDocument doc = new XDocument(new XElement((XName)name)); - foreach (MapEntity e in MapEntity.mapEntityList) - { - e.Save(doc); - } + //for (int i = 0; i < 10; i++) + //{ - mapHash = new MapHash(doc); - doc.Root.Add(new XAttribute("md5hash", mapHash.MD5Hash)); + // int closestIndex = 0; + // float closestDistance = 0.0f; + // for (int j = 0; j<10; j++) + // { + // if (j == i) continue; - try - { - SaveUtil.CompressStringToFile(filePath, doc.ToString()); - } - catch (Exception e) - { - DebugConsole.ThrowError("Saving map ''" + filePath + "'' failed!", e); - } + // //ignore if already connected + // bool alreadyConnected = false; + // foreach (LocationConnection connection in connections) + // { + // if (connection.Locations.Contains(locations[i]) && connection.Locations.Contains(locations[j])) + // { + // alreadyConnected = true; + // break; + // } + // } + // if (alreadyConnected) continue; - //doc.Save(filePath); + // float dist = Vector2.Distance(locations[i].MapPosition, locations[j].MapPosition); + // if (closestDistance > 0.0f && dist > closestDistance) continue; + + // closestDistance = dist; + // closestIndex = j; + // } + + + // connections.Add(new LocationConnection(locations[i], locations[closestIndex], level)); + //} + + currentLocation = locations[0]; } - public static void SaveCurrent(string savePath) + private void GenerateLocations() { - if (loaded==null) + Voronoi voronoi = new Voronoi(0.5f); + + List sites = new List(); + for (int i = 0; i < 50; i++) { - loaded = new Map(savePath); - return; + sites.Add(new Vector2((float)Game1.random.NextDouble() * size, (float)Game1.random.NextDouble() * size)); } - - loaded.SaveAs(savePath); - } - - public static void PreloadMaps(string mapFolder) - { - MapFolder = mapFolder; - - //string[] mapFilePaths; - Unload(); - SavedMaps.Clear(); - - if (!Directory.Exists(MapFolder)) - { - try - { - Directory.CreateDirectory(MapFolder); - } - catch - { - - DebugConsole.ThrowError("Directory ''Content/SavedMaps'' not found and creating the directory failed."); - return; - } - } - - string[] mapFilePaths; - - try - { - mapFilePaths = Directory.GetFiles(MapFolder); - } - catch (Exception e) - { - DebugConsole.ThrowError("Couldn't open directory ''Content/SavedMaps''!", e); - return; - } - - foreach (string mapPath in mapFilePaths) - { - //Map savedMap = new Map(mapPath); - SavedMaps.Add(new Map(mapPath)); - } - } - - public Map(string filePath, string mapHash="") - { - this.filePath = filePath; - try - { - name = Path.GetFileNameWithoutExtension(filePath); - } - catch (Exception e) - { - DebugConsole.ThrowError("Error loading map " + filePath + "!", e); - } - - - if (mapHash != "") - { - this.mapHash = new MapHash(mapHash); - } - else - { - //XDocument doc = OpenDoc(filePath); - - //string md5Hash = ToolBox.GetAttributeString(doc.Root, "md5hash", ""); - //if (md5Hash == "" || md5Hash.Length < 16) - //{ - // DebugConsole.ThrowError("Couldn't find a valid MD5 hash in the map file"); - //} - - //this.mapHash = new MapHash(md5Hash); - } - - } - - private XDocument OpenDoc(string file) - { - XDocument doc = null; - string extension = ""; - - try - { - extension = Path.GetExtension(file); - } - catch - { - DebugConsole.ThrowError("Couldn't load map ''" + file + "! (Unrecognized file extension)"); - return null; - } - - if (extension == ".gz") - { - Stream stream = SaveUtil.DecompressFiletoStream(file); - if (stream == null) - { - DebugConsole.ThrowError("Loading map ''" + file + "'' failed!"); - return null; - } - - try - { - stream.Position = 0; - doc = XDocument.Load(stream); //ToolBox.TryLoadXml(file); - stream.Close(); - stream.Dispose(); - } - - catch - { - DebugConsole.ThrowError("Loading map ''" + file + "'' failed!"); - return null; - } - } - else if (extension == ".xml") - { - try - { - doc = XDocument.Load(file); - } - - catch - { - DebugConsole.ThrowError("Loading map ''" + file + "'' failed!"); - return null; - } - } - else - { - DebugConsole.ThrowError("Couldn't load map ''" + file + "! (Unrecognized file extension)"); - return null; - } - - return doc; - } - - public void Load() - { - //string file = filePath; - - XDocument doc = OpenDoc(filePath); - if (doc == null) return; - - foreach (XElement element in doc.Root.Elements()) - { - string typeName = element.Name.ToString(); - - Type t; - try - { - // Get the type of a specified class. - t = Type.GetType("Subsurface." + typeName + ", Subsurface", true, true); - if (t == null) - { - DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type ''" + typeName + "''."); - continue; - } - } - catch (Exception e) - { - DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type ''" + typeName + "''.", e); - continue; - } - - try - { - MethodInfo loadMethod = t.GetMethod("Load"); - loadMethod.Invoke(t, new object[] { element }); - } - catch (Exception e) - { - DebugConsole.ThrowError("Could not find the method ''Load'' in " + t + ".", e); - } - - } - - borders = new Rectangle(0, 0, 1, 1); - foreach (Hull hull in Hull.hullList) - { - if (hull.Rect.X < borders.X || borders.X == 0) borders.X = hull.Rect.X; - if (hull.Rect.Y > borders.Y || borders.Y == 0) borders.Y = hull.Rect.Y; - - if (hull.Rect.X + hull.Rect.Width > borders.X + borders.Width) borders.Width = hull.Rect.X + hull.Rect.Width - borders.X; - if (hull.Rect.Y - hull.Rect.Height < borders.Y - borders.Height) borders.Height = borders.Y - (hull.Rect.Y - hull.Rect.Height); - } - - MapEntity.LinkAll(); - foreach (Item item in Item.itemList) - { - foreach (ItemComponent ic in item.components) - { - ic.OnMapLoaded(); - } - } - - loaded = this; - } - - public static Map Load(string file) - { - Unload(); - - Map map = new Map(file); - map.Load(); - - return map; + List edges = voronoi.MakeVoronoiGraph(sites, size, size); + sites.Clear(); + foreach (GraphEdge edge in edges) + { + if (edge.point1 == edge.point2) continue; + + Location[] newLocations = new Location[2]; + newLocations[0] = locations.Find(l => l.MapPosition == edge.point1 || l.MapPosition == edge.point2); + newLocations[1] = locations.Find(l => l != newLocations[0] && (l.MapPosition == edge.point1 || l.MapPosition == edge.point2)); + + for (int i = 0; i < 2; i++) + { + if (newLocations[i] != null) continue; + + Vector2[] points = new Vector2[] { edge.point1, edge.point2 }; + + int positionIndex = Game1.random.Next(0, 1); + + Vector2 position = points[positionIndex]; + if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) position = points[1 - positionIndex]; + + newLocations[i] = Location.CreateRandom(position); + locations.Add(newLocations[i]); + } + + connections.Add(new LocationConnection(newLocations[0], newLocations[1], Level.CreateRandom())); + } + + float minDistance = 50.0f; + for (int i = connections.Count - 1; i >= 0; i--) + { + LocationConnection connection = connections[i]; + + if (Vector2.Distance(connection.Locations[0].MapPosition, connection.Locations[1].MapPosition) > minDistance) continue; + + locations.Remove(connection.Locations[0]); + connections.Remove(connection); + + foreach (LocationConnection connection2 in connections) + { + if (connection2.Locations[0] == connection.Locations[0]) connection2.Locations[0] = connection.Locations[1]; + if (connection2.Locations[1] == connection.Locations[0]) connection2.Locations[1] = connection.Locations[1]; + } + } + } - public static void Unload() + public void Draw(SpriteBatch spriteBatch, Rectangle rect) { - if (loaded == null) return; - loaded.Clear(); - loaded = null; - } + GUI.DrawRectangle(spriteBatch, rect, Color.DarkBlue, true); - private void Clear() + Vector2 scale = new Vector2((float)rect.Width/ size, (float)rect.Height/size); + + float maxDist = 20.0f; + float closestDist = 0.0f; + Location highlightedLocation = null; + foreach (Location location in locations) + { + Vector2 pos = location.MapPosition * scale; + GUI.DrawRectangle(spriteBatch, new Rectangle(rect.X + (int)pos.X, rect.Y + (int)pos.Y, 5, 5), Color.White, true); + + if (currentLocation == location) + { + GUI.DrawRectangle(spriteBatch, new Rectangle(rect.X + (int)pos.X - 4, rect.Y + (int)pos.Y - 4, 5+8, 5+8), Color.Red, false); + } + + float dist = Vector2.Distance(PlayerInput.MousePosition, new Vector2(rect.X + pos.X, rect.Y + pos.Y)); + if (dist < maxDist && (highlightedLocation == null || dist < closestDist)) + { + closestDist = dist; + highlightedLocation = location; + } + } + + if (highlightedLocation!=null) + { + Vector2 pos = highlightedLocation.MapPosition * scale; + spriteBatch.DrawString(GUI.font, highlightedLocation.Name, pos + new Vector2(rect.X - 50, rect.Y), Color.White); + GUI.DrawRectangle(spriteBatch, new Rectangle(rect.X + (int)pos.X - 4, rect.Y + (int)pos.Y - 4, 5 + 8, 5 + 8), Color.White, false); + } + + if (selectedLocation != null) + { + Vector2 pos = selectedLocation.MapPosition * scale; + spriteBatch.DrawString(GUI.font, selectedLocation.Name, pos + new Vector2(rect.X - 50, rect.Y), Color.White); + GUI.DrawRectangle(spriteBatch, new Rectangle(rect.X + (int)pos.X - 4, rect.Y + (int)pos.Y - 4, 5 + 8, 5 + 8), Color.White, false); + } + + Vector2 rectCorner = new Vector2(rect.X, rect.Y); + foreach (LocationConnection connection in connections) + { + GUI.DrawLine(spriteBatch, + connection.Locations[0].MapPosition * scale + rectCorner, + connection.Locations[1].MapPosition * scale + rectCorner, Color.LightGray); + + if (highlightedLocation!=currentLocation && + connection.Locations.Contains(highlightedLocation) && connection.Locations.Contains(currentLocation)) + { + GUI.DrawLine(spriteBatch, + connection.Locations[0].MapPosition * scale + rectCorner +Vector2.One, + connection.Locations[1].MapPosition * scale + rectCorner + Vector2.One, Color.White); + + if (PlayerInput.LeftButtonClicked()) + if(selectedLocation!=highlightedLocation && highlightedLocation!=null) + { + //currentLocation = highlightedLocation; + Game1.LobbyScreen.SelectLocation(highlightedLocation, connection); + selectedLocation = highlightedLocation; + } + } + + if (selectedLocation != currentLocation && + (connection.Locations.Contains(selectedLocation) && connection.Locations.Contains(currentLocation))) + { + GUI.DrawLine(spriteBatch, + connection.Locations[0].MapPosition * scale + rectCorner + Vector2.One, + connection.Locations[1].MapPosition * scale + rectCorner + Vector2.One, Color.White); + + } + } + + } + } + + + class LocationConnection + { + Location[] locations; + Level level; + + public Location[] Locations { - if (Game1.GameScreen.Cam != null) Game1.GameScreen.Cam.TargetPos = Vector2.Zero; - - Entity.RemoveAll(); - - PhysicsBody.list.Clear(); - - Ragdoll.list.Clear(); - - Game1.world.Clear(); + get { return locations; } } + public Level Level + { + get { return level; } + } + + public LocationConnection(Location location1, Location location2, Level level) + { + locations = new Location[] { location1, location2 }; + this.level = level; + } } } diff --git a/Subsurface/Map/MapEntity.cs b/Subsurface/Map/MapEntity.cs index 34cfdbf1a..69837ac9b 100644 --- a/Subsurface/Map/MapEntity.cs +++ b/Subsurface/Map/MapEntity.cs @@ -117,7 +117,7 @@ namespace Subsurface public virtual bool Contains(Vector2 position) { - return (Map.RectContains(rect, position)); + return (Submarine.RectContains(rect, position)); } public virtual void Draw(SpriteBatch spriteBatch, bool editing) {} @@ -216,7 +216,7 @@ namespace Subsurface //mouse released -> move the entities to the new position of the mouse Vector2 moveAmount = position - startMovingPos; - moveAmount = Map.VectorToWorldGrid(moveAmount); + moveAmount = Submarine.VectorToWorldGrid(moveAmount); if (moveAmount != Vector2.Zero) { @@ -235,7 +235,7 @@ namespace Subsurface selectionSize.Y = selectionPos.Y - position.Y; List newSelection = new List();// FindSelectedEntities(selectionPos, selectionSize); - if (Math.Abs(selectionSize.X) > Map.gridSize.X || Math.Abs(selectionSize.Y) > Map.gridSize.Y) + if (Math.Abs(selectionSize.X) > Submarine.gridSize.X || Math.Abs(selectionSize.Y) > Submarine.gridSize.Y) { newSelection = FindSelectedEntities(selectionPos, selectionSize); } @@ -315,7 +315,7 @@ namespace Subsurface if (startMovingPos != Vector2.Zero) { Vector2 moveAmount = position - startMovingPos; - moveAmount = Map.VectorToWorldGrid(moveAmount); + moveAmount = Submarine.VectorToWorldGrid(moveAmount); moveAmount.Y = -moveAmount.Y; //started moving the selected entities if (moveAmount != Vector2.Zero) @@ -358,7 +358,7 @@ namespace Subsurface List foundEntities = new List(); foreach (MapEntity e in mapEntityList) { - if (Map.RectContains(e.rect, pos)) foundEntities.Add(e); + if (Submarine.RectContains(e.rect, pos)) foundEntities.Add(e); } return foundEntities; } @@ -367,7 +367,7 @@ namespace Subsurface { foreach (MapEntity e in mapEntityList) { - if (Map.RectContains(e.rect, pos)) return e; + if (Submarine.RectContains(e.rect, pos)) return e; } return null; } @@ -379,11 +379,11 @@ namespace Subsurface { List foundEntities = new List(); - Rectangle selectionRect = Map.AbsRect(pos, size); + Rectangle selectionRect = Submarine.AbsRect(pos, size); foreach (MapEntity e in mapEntityList) { - if (Map.RectsOverlap(selectionRect, e.rect)) + if (Submarine.RectsOverlap(selectionRect, e.rect)) foundEntities.Add(e); } diff --git a/Subsurface/Map/MapEntityPrefab.cs b/Subsurface/Map/MapEntityPrefab.cs index e3cf2210f..0f596b3b5 100644 --- a/Subsurface/Map/MapEntityPrefab.cs +++ b/Subsurface/Map/MapEntityPrefab.cs @@ -73,23 +73,23 @@ namespace Subsurface public virtual void UpdatePlacing(SpriteBatch spriteBatch, Camera cam) { - Vector2 placeSize = Map.gridSize; + Vector2 placeSize = Submarine.gridSize; if (placePosition == Vector2.Zero) { if (PlayerInput.GetMouseState.LeftButton == ButtonState.Pressed) - placePosition = Map.MouseToWorldGrid(cam); + placePosition = Submarine.MouseToWorldGrid(cam); } else { - Vector2 position = Map.MouseToWorldGrid(cam); + Vector2 position = Submarine.MouseToWorldGrid(cam); if (resizeHorizontal) placeSize.X = position.X - placePosition.X; if (resizeVertical) placeSize.Y = placePosition.Y - position.Y; - Rectangle newRect = Map.AbsRect(placePosition, placeSize); - newRect.Width = (int)Math.Max(newRect.Width, Map.gridSize.X); - newRect.Height = (int)Math.Max(newRect.Height, Map.gridSize.Y); + Rectangle newRect = Submarine.AbsRect(placePosition, placeSize); + newRect.Width = (int)Math.Max(newRect.Width, Submarine.gridSize.X); + newRect.Height = (int)Math.Max(newRect.Height, Submarine.gridSize.Y); if (PlayerInput.GetMouseState.LeftButton == ButtonState.Released) { diff --git a/Subsurface/Map/MapHash.cs b/Subsurface/Map/Md5Hash.cs similarity index 86% rename from Subsurface/Map/MapHash.cs rename to Subsurface/Map/Md5Hash.cs index e82fa2a03..5e3d71dca 100644 --- a/Subsurface/Map/MapHash.cs +++ b/Subsurface/Map/Md5Hash.cs @@ -1,14 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Xml.Linq; namespace Subsurface { - class MapHash + class Md5Hash { private string md5Hash; private string shortHash; @@ -29,14 +26,14 @@ namespace Subsurface } } - public MapHash(string md5Hash) + public Md5Hash(string md5Hash) { this.md5Hash = md5Hash; shortHash = GetShortHash(md5Hash); } - public MapHash(XDocument doc) + public Md5Hash(XDocument doc) { string docString = Regex.Replace(doc.ToString(), @"\s+", ""); // step 1, calculate MD5 hash from input diff --git a/Subsurface/Map/Structure.cs b/Subsurface/Map/Structure.cs index 0bb628ef3..f3ce6dc14 100644 --- a/Subsurface/Map/Structure.cs +++ b/Subsurface/Map/Structure.cs @@ -111,7 +111,7 @@ namespace Subsurface Vector2 simAmount = ConvertUnits.ToSimUnits(amount); foreach (Body b in bodies) { - b.SetTransform(b.Position + simAmount, 0.0f); + b.SetTransform(b.Position + simAmount, b.Rotation); } } @@ -203,15 +203,15 @@ namespace Subsurface bodies = new List(); Body newBody = BodyFactory.CreateRectangle(Game1.world, - ConvertUnits.ToSimUnits(rect.Width * Math.Sqrt(2.0) - Map.gridSize.X), + ConvertUnits.ToSimUnits(rect.Width * Math.Sqrt(2.0) - Submarine.gridSize.X), ConvertUnits.ToSimUnits(10), 1.5f); newBody.BodyType = BodyType.Static; Vector2 stairPos = new Vector2(Position.X, rect.Y - rect.Height + rect.Width / 2.0f); stairPos += new Vector2( - (StairDirection == Direction.Right) ? -Map.gridSize.X*1.5f : Map.gridSize.X*1.5f, - - Map.gridSize.Y*2.0f); + (StairDirection == Direction.Right) ? -Submarine.gridSize.X*1.5f : Submarine.gridSize.X*1.5f, + - Submarine.gridSize.Y*2.0f); newBody.Position = ConvertUnits.ToSimUnits(stairPos); diff --git a/Subsurface/Map/StructurePrefab.cs b/Subsurface/Map/StructurePrefab.cs index 4da137408..b353cccf8 100644 --- a/Subsurface/Map/StructurePrefab.cs +++ b/Subsurface/Map/StructurePrefab.cs @@ -109,7 +109,7 @@ namespace Subsurface public override void UpdatePlacing(SpriteBatch spriteBatch, Camera cam) { - Vector2 position = Map.MouseToWorldGrid(cam); + Vector2 position = Submarine.MouseToWorldGrid(cam); //Vector2 placeSize = size; Rectangle newRect = new Rectangle((int)position.X, (int)position.Y, (int)size.X, (int)size.Y); @@ -118,7 +118,7 @@ namespace Subsurface if (placePosition == Vector2.Zero) { if (PlayerInput.GetMouseState.LeftButton == ButtonState.Pressed) - placePosition = Map.MouseToWorldGrid(cam); + placePosition = Submarine.MouseToWorldGrid(cam); newRect.X = (int)position.X; newRect.Y = (int)position.Y; @@ -131,7 +131,7 @@ namespace Subsurface if (resizeHorizontal) placeSize.X = position.X - placePosition.X; if (resizeVertical) placeSize.Y = placePosition.Y - position.Y; - newRect = Map.AbsRect(placePosition, placeSize); + newRect = Submarine.AbsRect(placePosition, placeSize); //newRect.Width = (int)Math.Max(newRect.Width, Map.gridSize.X); //newRect.Height = (int)Math.Max(newRect.Height, Map.gridSize.Y); diff --git a/Subsurface/Map/Submarine.cs b/Subsurface/Map/Submarine.cs new file mode 100644 index 000000000..5b6722236 --- /dev/null +++ b/Subsurface/Map/Submarine.cs @@ -0,0 +1,564 @@ +using FarseerPhysics; +using FarseerPhysics.Collision; +using FarseerPhysics.Dynamics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; + +namespace Subsurface +{ + public enum Direction : byte + { + None = 0, Left = 1, Right = 2 + } + + class Submarine + { + static string SaveFolder; + Md5Hash hash; + + public static List SavedSubmarines = new List(); + + private static Submarine loaded; + + //public static Map Loaded + //{ + // get { return loaded; } + // set { loaded = value; } + //} + + + public static readonly Vector2 gridSize = new Vector2(16.0f, 16.0f); + + private static Vector2 lastPickedPosition; + private static float lastPickedFraction; + + private Rectangle borders; + + private string filePath; + private string name; + + public string Name + { + get { return name; } + } + + public static Vector2 LastPickedPosition + { + get { return lastPickedPosition; } + } + + public static float LastPickedFraction + { + get { return lastPickedFraction; } + } + + public Md5Hash Hash + { + get + { + if (hash != null) return hash; + + XDocument doc = OpenDoc(filePath); + hash = new Md5Hash(doc); + + return hash; + } + } + + public static Submarine Loaded + { + get { return loaded; } + } + + public static Rectangle Borders + { + get + { + return (loaded==null) ? Rectangle.Empty : loaded.borders; + } + } + + public Vector2 Center + { + get { return new Vector2(borders.X+borders.Width/2, borders.Y - borders.Height/2); } + } + + public string FilePath + { + get { return filePath; } + } + + public static void Draw(SpriteBatch spriteBatch, bool editing = false) + { + for (int i = 0; i < MapEntity.mapEntityList.Count(); i++ ) + { + MapEntity.mapEntityList[i].Draw(spriteBatch, editing); + } + } + + public static void DrawFront(SpriteBatch spriteBatch, bool editing = false) + { + for (int i = 0; i < MapEntity.mapEntityList.Count(); i++) + { + if (MapEntity.mapEntityList[i].sprite == null || MapEntity.mapEntityList[i].sprite.Depth < 0.5f) + MapEntity.mapEntityList[i].Draw(spriteBatch, editing); + } + } + + public static void DrawBack(SpriteBatch spriteBatch, bool editing = false) + { + for (int i = 0; i < MapEntity.mapEntityList.Count(); i++) + { + if (MapEntity.mapEntityList[i].sprite == null || MapEntity.mapEntityList[i].sprite.Depth >= 0.5f) + MapEntity.mapEntityList[i].Draw(spriteBatch, editing); + } + } + + public static Vector2 MouseToWorldGrid(Camera cam) + { + Vector2 position = new Vector2(PlayerInput.GetMouseState.X, PlayerInput.GetMouseState.Y); + position = cam.ScreenToWorld(position); + + return VectorToWorldGrid(position); + } + + public static Vector2 VectorToWorldGrid(Vector2 position) + { + position.X = (float)Math.Floor(Convert.ToDouble(position.X / gridSize.X)) * gridSize.X; + position.Y = (float)Math.Ceiling(Convert.ToDouble(position.Y / gridSize.Y)) * gridSize.Y; + + return position; + } + + + public static Rectangle AbsRect(Vector2 pos, Vector2 size) + { + if (size.X < 0.0f) + { + pos.X += size.X; + size.X = -size.X; + } + if (size.Y < 0.0f) + { + pos.Y -= size.Y; + size.Y = -size.Y; + } + + return new Rectangle((int)pos.X, (int)pos.Y, (int)size.X, (int)size.Y); + } + + public static bool RectContains(Rectangle rect, Vector2 pos) + { + return (pos.X > rect.X && pos.X < rect.X + rect.Width + && pos.Y < rect.Y && pos.Y > rect.Y - rect.Height); + } + + public static bool RectsOverlap(Rectangle rect1, Rectangle rect2, bool inclusive=true) + { + if (inclusive) + { + return !(rect1.X > rect2.X + rect2.Width || rect1.X + rect1.Width < rect2.X || + rect1.Y < rect2.Y - rect2.Height || rect1.Y - rect1.Height > rect2.Y); + } + else + { + return !(rect1.X >= rect2.X + rect2.Width || rect1.X + rect1.Width <= rect2.X || + rect1.Y <= rect2.Y - rect2.Height || rect1.Y - rect1.Height >= rect2.Y); + } + } + + public void Move(Vector2 amount, float deltaTime) + { + if (amount == Vector2.Zero) return; + + Level.Loaded.Move(-amount, deltaTime); + + //foreach (MapEntity e in Structure.mapEntityList) + //{ + // e.Move(amount); + //} + + //amount = ConvertUnits.ToSimUnits(amount*deltaTime); + //foreach (Character c in Character.characterList) + //{ + // if (c.animController.CurrentHull != null) continue; + // foreach (Limb l in c.animController.limbs) + // { + // l.body.SetTransform(l.body.Position - amount, l.body.Rotation); + // } + //} + } + + public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, List ignoredBodies = null) + { + float closestFraction = 1.0f; + Body closestBody = null; + Game1.world.RayCast((fixture, point, normal, fraction) => + { + if (fixture == null || fixture.CollisionCategories == Category.None) return -1; + if (ignoredBodies != null && ignoredBodies.Contains(fixture.Body)) return -1; + + Structure structure = fixture.Body.UserData as Structure; + if (structure != null && (structure.IsPlatform || !structure.HasBody)) return -1; + + if (fraction < closestFraction) + { + closestFraction = fraction; + if (fixture.Body!=null) closestBody = fixture.Body; + } + return fraction; + } + , rayStart, rayEnd); + + lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction; + lastPickedFraction = closestFraction; + return closestBody; + } + + + public static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd) + { + Body closestBody = null; + float closestFraction = 1.0f; + + if (Vector2.Distance(rayStart,rayEnd)<0.01f) + { + closestFraction = 0.01f; + return null; + } + + Game1.world.RayCast((fixture, point, normal, fraction) => + { + if (fixture == null || fixture.CollisionCategories != Physics.CollisionWall) return -1; + + Structure structure = fixture.Body.UserData as Structure; + if (structure != null) + { + if (structure.IsPlatform || structure.StairDirection != Direction.None) return -1; + int sectionIndex = structure.FindSectionIndex(ConvertUnits.ToDisplayUnits(point)); + if (sectionIndex > -1 && structure.SectionHasHole(sectionIndex)) return -1; + } + + if (fraction < closestFraction) + { + closestBody = fixture.Body; + closestFraction = fraction; + } + return closestFraction; + } + , rayStart, rayEnd); + + + lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction; + lastPickedFraction = closestFraction; + return closestBody; + } + + public static Body PickBody(Vector2 point) + { + Body foundBody = null; + AABB aabb = new AABB(point, point); + + Game1.world.QueryAABB(p => + { + foundBody = p.Body; + + return true; + + }, ref aabb); + + return foundBody; + } + + public static bool InsideWall(Vector2 point) + { + Body foundBody = PickBody(point); + if (foundBody==null) return false; + + Structure wall = foundBody.UserData as Structure; + if (wall == null || wall.IsPlatform) return false; + + return true; + } + + + public void Save() + { + SaveAs(filePath); + } + + public void SaveAs(string filePath) + { + //if (filePath=="") + //{ + // DebugConsole.ThrowError("No save file selected"); + // return; + //} + XDocument doc = new XDocument(new XElement((XName)name)); + + foreach (MapEntity e in MapEntity.mapEntityList) + { + e.Save(doc); + } + + hash = new Md5Hash(doc); + doc.Root.Add(new XAttribute("md5hash", hash.MD5Hash)); + + try + { + SaveUtil.CompressStringToFile(filePath, doc.ToString()); + } + catch (Exception e) + { + DebugConsole.ThrowError("Saving submarine ''" + filePath + "'' failed!", e); + } + + + //doc.Save(filePath); + } + + public static void SaveCurrent(string savePath) + { + if (loaded==null) + { + loaded = new Submarine(savePath); + return; + } + + loaded.SaveAs(savePath); + } + + public static void Preload(string folder) + { + SaveFolder = folder; + + //string[] mapFilePaths; + Unload(); + SavedSubmarines.Clear(); + + if (!Directory.Exists(SaveFolder)) + { + try + { + Directory.CreateDirectory(SaveFolder); + } + catch + { + + DebugConsole.ThrowError("Directory ''"+SaveFolder+"'' not found and creating the directory failed."); + return; + } + } + + string[] filePaths; + + try + { + filePaths = Directory.GetFiles(SaveFolder); + } + catch (Exception e) + { + DebugConsole.ThrowError("Couldn't open directory ''" + SaveFolder + "''!", e); + return; + } + + foreach (string path in filePaths) + { + //Map savedMap = new Map(mapPath); + SavedSubmarines.Add(new Submarine(path)); + } + } + + public Submarine(string filePath, string hash="") + { + this.filePath = filePath; + try + { + name = Path.GetFileNameWithoutExtension(filePath); + } + catch (Exception e) + { + DebugConsole.ThrowError("Error loading map " + filePath + "!", e); + } + + + if (hash != "") + { + this.hash = new Md5Hash(hash); + } + else + { + //XDocument doc = OpenDoc(filePath); + + //string md5Hash = ToolBox.GetAttributeString(doc.Root, "md5hash", ""); + //if (md5Hash == "" || md5Hash.Length < 16) + //{ + // DebugConsole.ThrowError("Couldn't find a valid MD5 hash in the map file"); + //} + + //this.mapHash = new MapHash(md5Hash); + } + + } + + private XDocument OpenDoc(string file) + { + XDocument doc = null; + string extension = ""; + + try + { + extension = Path.GetExtension(file); + } + catch + { + DebugConsole.ThrowError("Couldn't load submarine ''" + file + "! (Unrecognized file extension)"); + return null; + } + + if (extension == ".gz") + { + Stream stream = SaveUtil.DecompressFiletoStream(file); + if (stream == null) + { + DebugConsole.ThrowError("Loading submarine ''" + file + "'' failed!"); + return null; + } + + try + { + stream.Position = 0; + doc = XDocument.Load(stream); //ToolBox.TryLoadXml(file); + stream.Close(); + stream.Dispose(); + } + + catch + { + DebugConsole.ThrowError("Loading submarine ''" + file + "'' failed!"); + return null; + } + } + else if (extension == ".xml") + { + try + { + doc = XDocument.Load(file); + } + + catch + { + DebugConsole.ThrowError("Loading submarine ''" + file + "'' failed!"); + return null; + } + } + else + { + DebugConsole.ThrowError("Couldn't load submarine ''" + file + "! (Unrecognized file extension)"); + return null; + } + + return doc; + } + + public void Load() + { + //string file = filePath; + + XDocument doc = OpenDoc(filePath); + if (doc == null) return; + + foreach (XElement element in doc.Root.Elements()) + { + string typeName = element.Name.ToString(); + + Type t; + try + { + // Get the type of a specified class. + t = Type.GetType("Subsurface." + typeName + ", Subsurface", true, true); + if (t == null) + { + DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type ''" + typeName + "''."); + continue; + } + } + catch (Exception e) + { + DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type ''" + typeName + "''.", e); + continue; + } + + try + { + MethodInfo loadMethod = t.GetMethod("Load"); + loadMethod.Invoke(t, new object[] { element }); + } + catch (Exception e) + { + DebugConsole.ThrowError("Could not find the method ''Load'' in " + t + ".", e); + } + + } + + borders = new Rectangle(0, 0, 1, 1); + foreach (Hull hull in Hull.hullList) + { + if (hull.Rect.X < borders.X || borders.X == 0) borders.X = hull.Rect.X; + if (hull.Rect.Y > borders.Y || borders.Y == 0) borders.Y = hull.Rect.Y; + + if (hull.Rect.X + hull.Rect.Width > borders.X + borders.Width) borders.Width = hull.Rect.X + hull.Rect.Width - borders.X; + if (hull.Rect.Y - hull.Rect.Height < borders.Y - borders.Height) borders.Height = borders.Y - (hull.Rect.Y - hull.Rect.Height); + } + + MapEntity.LinkAll(); + foreach (Item item in Item.itemList) + { + foreach (ItemComponent ic in item.components) + { + ic.OnMapLoaded(); + } + } + + loaded = this; + } + + public static Submarine Load(string file) + { + Unload(); + + Submarine sub = new Submarine(file); + sub.Load(); + + return sub; + + } + + public static void Unload() + { + if (loaded == null) return; + loaded.Clear(); + loaded = null; + } + + private void Clear() + { + if (Game1.GameScreen.Cam != null) Game1.GameScreen.Cam.TargetPos = Vector2.Zero; + + Entity.RemoveAll(); + + PhysicsBody.list.Clear(); + + Ragdoll.list.Clear(); + + Game1.world.Clear(); + } + + } +} diff --git a/Subsurface/Map/Voronoi.cs b/Subsurface/Map/Voronoi.cs index 8b8e0ed16..3b456efbb 100644 --- a/Subsurface/Map/Voronoi.cs +++ b/Subsurface/Map/Voronoi.cs @@ -972,5 +972,17 @@ namespace Voronoi2 return true; } + public List MakeVoronoiGraph(List sites, int width, int height) + { + double[] xVal = new double[sites.Count]; + double[] yVal = new double[sites.Count]; + for (int i = 0; i < sites.Count; i++) + { + xVal[i] = sites[i].X; + yVal[i] = sites[i].Y; + } + return generateVoronoi(xVal, yVal, 0, width, 0, height); + } + } // Voronoi Class End } // namespace Voronoi2 End \ No newline at end of file diff --git a/Subsurface/Map/VoronoiElements.cs b/Subsurface/Map/VoronoiElements.cs index 3728524d9..7b5aa49a6 100644 --- a/Subsurface/Map/VoronoiElements.cs +++ b/Subsurface/Map/VoronoiElements.cs @@ -50,7 +50,9 @@ */ +using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; +using Subsurface; using System; using System.Collections.Generic; @@ -119,6 +121,8 @@ namespace Voronoi2 public List edges; public Site site; + public List bodies; + public Vector2 Center { get { return new Vector2((float)site.coord.x, (float)site.coord.y); } @@ -127,6 +131,8 @@ namespace Voronoi2 public VoronoiCell(Site site) { edges = new List(); + + bodies = new List(); this.site = site; } } @@ -135,6 +141,23 @@ namespace Voronoi2 { public Vector2 point1, point2; public Site site1, site2; + public VoronoiCell cell1, cell2; + + public VoronoiCell AdjacentCell(VoronoiCell cell) + { + if (cell1==cell) + { + return cell2; + } + else if (cell2==cell) + { + return cell1; + } + else + { + return null; + } + } } // للترتيب diff --git a/Subsurface/Map/WayPoint.cs b/Subsurface/Map/WayPoint.cs index cb8b17421..5abea6130 100644 --- a/Subsurface/Map/WayPoint.cs +++ b/Subsurface/Map/WayPoint.cs @@ -68,7 +68,7 @@ namespace Subsurface if (e.GetType()!=typeof(WayPoint)) continue; if (e == this) continue; - if (!Map.RectContains(e.Rect, position)) continue; + if (!Submarine.RectContains(e.Rect, position)) continue; linkedTo.Add(e); e.linkedTo.Add(this); @@ -123,7 +123,7 @@ namespace Subsurface Rectangle rect = new Rectangle( int.Parse(element.Attribute("x").Value), int.Parse(element.Attribute("y").Value), - (int)Map.gridSize.X, (int)Map.gridSize.Y); + (int)Submarine.gridSize.X, (int)Submarine.gridSize.Y); WayPoint w = new WayPoint(rect); diff --git a/Subsurface/Networking/GameClient.cs b/Subsurface/Networking/GameClient.cs index e3108b808..4ec335a25 100644 --- a/Subsurface/Networking/GameClient.cs +++ b/Subsurface/Networking/GameClient.cs @@ -240,7 +240,7 @@ namespace Subsurface.Networking TimeSpan duration = new TimeSpan(0,(int)durationMinutes,0); //int gameModeIndex = inc.ReadInt32(); - Game1.GameSession = new GameSession(Map.Loaded); + Game1.GameSession = new GameSession(Submarine.Loaded); Game1.GameSession.StartShift(duration, 1); myCharacter = ReadCharacterData(inc); @@ -323,7 +323,7 @@ namespace Subsurface.Networking public void EndGame(string endMessage) { - Map.Unload(); + Submarine.Unload(); Game1.NetLobbyScreen.Select(); diff --git a/Subsurface/Networking/GameServer.cs b/Subsurface/Networking/GameServer.cs index d57040920..27097b277 100644 --- a/Subsurface/Networking/GameServer.cs +++ b/Subsurface/Networking/GameServer.cs @@ -270,7 +270,7 @@ namespace Subsurface.Networking int seed = DateTime.Now.Millisecond; Game1.random = new Random(seed); - Map selectedMap = Game1.NetLobbyScreen.SelectedMap as Map; + Submarine selectedMap = Game1.NetLobbyScreen.SelectedMap as Submarine; //selectedMap.Load(); @@ -307,7 +307,7 @@ namespace Subsurface.Networking msg.Write(seed); msg.Write(Game1.NetLobbyScreen.SelectedMap.Name); - msg.Write(Game1.NetLobbyScreen.SelectedMap.MapHash.MD5Hash); + msg.Write(Game1.NetLobbyScreen.SelectedMap.Hash.MD5Hash); msg.Write(Game1.NetLobbyScreen.GameDuration.TotalMinutes); @@ -339,7 +339,7 @@ namespace Subsurface.Networking public void EndGame(string endMessage) { - Map.Unload(); + Submarine.Unload(); gameStarted = false; diff --git a/Subsurface/Particles/Particle.cs b/Subsurface/Particles/Particle.cs index cc17f32e7..17fea5eb3 100644 --- a/Subsurface/Particles/Particle.cs +++ b/Subsurface/Particles/Particle.cs @@ -66,7 +66,7 @@ namespace Subsurface.Particles velocity = speed; - this.rotation = rotation + ToolBox.RandomFloat(prefab.startRotationMin, prefab.startRotationMax); + this.rotation = rotation + ToolBox.RandomFloatLocal(prefab.startRotationMin, prefab.startRotationMax); prevRotation = rotation; float rand = (float)Game1.localRandom.NextDouble(); @@ -132,7 +132,7 @@ namespace Subsurface.Particles } else { - if (Map.InsideWall(new Vector2(drawPosition.X, -drawPosition.Y))) + if (Submarine.InsideWall(new Vector2(drawPosition.X, -drawPosition.Y))) { return false; } diff --git a/Subsurface/Particles/ParticleManager.cs b/Subsurface/Particles/ParticleManager.cs index ad65a31c0..967b18409 100644 --- a/Subsurface/Particles/ParticleManager.cs +++ b/Subsurface/Particles/ParticleManager.cs @@ -61,7 +61,7 @@ namespace Subsurface.Particles public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation=0.0f) { - if (!Map.RectContains(cam.WorldView, ConvertUnits.ToDisplayUnits(position))) return null; + if (!Submarine.RectContains(cam.WorldView, ConvertUnits.ToDisplayUnits(position))) return null; if (particleCount >= MaxParticles) return null; if (particles[particleCount] == null) particles[particleCount] = new Particle(); diff --git a/Subsurface/SaveUtil.cs b/Subsurface/SaveUtil.cs index beef366c4..a4651ee82 100644 --- a/Subsurface/SaveUtil.cs +++ b/Subsurface/SaveUtil.cs @@ -23,7 +23,7 @@ namespace Subsurface //Directory.CreateDirectory(Path.GetDirectoryName(filePath) + "\\temp"); - Map.Loaded.SaveAs(tempPath + "\\map.gz"); + Submarine.Loaded.SaveAs(tempPath + "\\map.gz"); Game1.GameSession.Save(tempPath + "\\gamesession.xml"); //Game1.GameSession.crewManager.Save(directory+"\\crew.xml"); @@ -39,7 +39,7 @@ namespace Subsurface DecompressToDirectory(filePath, tempPath, null); - Map selectedMap = Map.Load(tempPath +"\\map.gz"); + Submarine selectedMap = Submarine.Load(tempPath +"\\map.gz"); Game1.GameSession = new GameSession(selectedMap, filePath, tempPath + "\\gamesession.xml"); Directory.Delete(tempPath, true); diff --git a/Subsurface/Screens/EditCharacterScreen.cs b/Subsurface/Screens/EditCharacterScreen.cs index d5e05605e..4b115ddd3 100644 --- a/Subsurface/Screens/EditCharacterScreen.cs +++ b/Subsurface/Screens/EditCharacterScreen.cs @@ -140,7 +140,7 @@ namespace Subsurface null, null, null, null, cam.Transform); - Map.Draw(spriteBatch, true); + Submarine.Draw(spriteBatch, true); spriteBatch.End(); diff --git a/Subsurface/Screens/EditMapScreen.cs b/Subsurface/Screens/EditMapScreen.cs index bce806af2..2b66d4d05 100644 --- a/Subsurface/Screens/EditMapScreen.cs +++ b/Subsurface/Screens/EditMapScreen.cs @@ -229,7 +229,7 @@ namespace Subsurface graphics.Clear(new Color(0.051f, 0.149f, 0.271f, 1.0f)); - Map.Draw(spriteBatch, true); + Submarine.Draw(spriteBatch, true); if (!characterMode) { diff --git a/Subsurface/Screens/GameScreen.cs b/Subsurface/Screens/GameScreen.cs index 6d7a8ee63..51b09b751 100644 --- a/Subsurface/Screens/GameScreen.cs +++ b/Subsurface/Screens/GameScreen.cs @@ -57,16 +57,16 @@ namespace Subsurface AmbientSoundManager.Update(); - //Vector2 targetMovement = Vector2.Zero; - //if (PlayerInput.KeyDown(Keys.I)) targetMovement.Y += 1.0f; - //if (PlayerInput.KeyDown(Keys.K)) targetMovement.Y -= 1.0f; - //if (PlayerInput.KeyDown(Keys.J)) targetMovement.X -= 1.0f; - //if (PlayerInput.KeyDown(Keys.L)) targetMovement.X += 1.0f; + if (Game1.GameSession.Level!=null) + { + Vector2 targetMovement = Vector2.Zero; + if (PlayerInput.KeyDown(Keys.I)) targetMovement.Y += 1.0f; + if (PlayerInput.KeyDown(Keys.K)) targetMovement.Y -= 1.0f; + if (PlayerInput.KeyDown(Keys.J)) targetMovement.X -= 1.0f; + if (PlayerInput.KeyDown(Keys.L)) targetMovement.X += 1.0f; - //foreach (MapEntity e in Structure.mapEntityList) - //{ - // e.Move(targetMovement); - //} + Game1.GameSession.Submarine.Move(targetMovement*1000.0f, (float)deltaTime); + } if (Game1.GameSession!=null) Game1.GameSession.Update((float)deltaTime); //EventManager.Update(gameTime); @@ -170,8 +170,8 @@ namespace Subsurface if (y<0) { - backgroundTop.SourceRect = new Rectangle(x, y, 1024, 1024); - backgroundTop.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(Game1.GraphicsWidth, Math.Min(1024 - y, Game1.GraphicsHeight)), + backgroundTop.SourceRect = new Rectangle(x, y, 1024, Math.Min(-y,1024)); + backgroundTop.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(Game1.GraphicsWidth, Math.Min(-y, Game1.GraphicsHeight)), Vector2.Zero, Color.White); } } @@ -183,10 +183,8 @@ namespace Subsurface BlendState.AlphaBlend, null, null, null, null, cam.Transform); - - if (Game1.Level!=null) Game1.Level.Render(spriteBatch); - Map.DrawBack(spriteBatch); + Submarine.DrawBack(spriteBatch); foreach (Character c in Character.characterList) c.Draw(spriteBatch); @@ -257,10 +255,18 @@ namespace Subsurface null, null, null, null, cam.Transform); - Map.DrawFront(spriteBatch); - + Submarine.DrawFront(spriteBatch); + spriteBatch.End(); + if (Game1.GameSession != null && Game1.GameSession.Level != null) + { + Game1.GameSession.Level.Render(graphics, cam); + Game1.GameSession.Level.SetObserverPosition(cam.WorldViewCenter); + } + + if (Game1.Level != null) Game1.Level.Render(graphics, cam); + LightManager.DrawFow(graphics,cam); } } diff --git a/Subsurface/Screens/LobbyScreen.cs b/Subsurface/Screens/LobbyScreen.cs index 508569e85..42a25bee0 100644 --- a/Subsurface/Screens/LobbyScreen.cs +++ b/Subsurface/Screens/LobbyScreen.cs @@ -9,10 +9,12 @@ namespace Subsurface { class LobbyScreen : Screen { + enum PanelTab { Crew = 0, Map = 1, Hire = 2 } + GUIFrame leftPanel; GUIFrame[] rightPanel; - GUIFrame shiftPanel; + GUIButton startButton; int selectedRightPanel; @@ -26,6 +28,8 @@ namespace Subsurface Character previewCharacter; + Level selectedLevel; + public LobbyScreen() { Rectangle panelRect = new Rectangle( @@ -44,62 +48,55 @@ namespace Subsurface "", Color.Transparent, Color.White, Alignment.Left, leftPanel); moneyText.TextGetter = GetMoney; - GUIButton button = new GUIButton(new Rectangle(0, 60, 100, 30), "Crew", GUI.style, Alignment.CenterX, leftPanel); - button.UserData = 0; + + + GUIButton button = new GUIButton(new Rectangle(0, 60, 100, 30), "Map", GUI.style, Alignment.Left, leftPanel); + button.UserData = PanelTab.Map; button.OnClicked = SelectRightPanel; - button = new GUIButton(new Rectangle(0, 100, 100, 30), "Hire", GUI.style, Alignment.CenterX, leftPanel); - button.UserData = 1; + button = new GUIButton(new Rectangle(0, 100, 100, 30), "Crew", GUI.style, Alignment.Left, leftPanel); + button.UserData = PanelTab.Crew; button.OnClicked = SelectRightPanel; - //-------------------------------------- + button = new GUIButton(new Rectangle(0, 140, 100, 30), "Hire", GUI.style, Alignment.Left, leftPanel); + button.UserData = PanelTab.Hire; + button.OnClicked = SelectRightPanel; + + //--------------------------------------------------------------- + //--------------------------------------------------------------- panelRect = new Rectangle( panelRect.X + panelRect.Width + (int)(GUI.style.largePadding.X), - panelRect.Y, + (int)GUI.style.largePadding.Y, Game1.GraphicsWidth - panelRect.Width - (int)(GUI.style.largePadding.X * 3.0f), - (int)(Game1.GraphicsHeight * 0.3f) - (int)(GUI.style.largePadding.Y * 1.5f)); + Game1.GraphicsHeight - (int)(GUI.style.largePadding.Y * 2)); - shiftPanel = new GUIFrame(panelRect, GUI.style.backGroundColor); - shiftPanel.Padding = GUI.style.smallPadding; + rightPanel = new GUIFrame[3]; - GUITextBlock dayText = new GUITextBlock(new Rectangle(0, 0, 200, 25), - "", Color.Transparent, Color.White, Alignment.Left, shiftPanel); - dayText.TextGetter = GetDay; + rightPanel[(int)PanelTab.Crew] = new GUIFrame(panelRect, GUI.style.backGroundColor); + rightPanel[(int)PanelTab.Crew].Padding = GUI.style.smallPadding; - GUIProgressBar progressBar = new GUIProgressBar(new Rectangle(0, 30, 200, 20), Color.Green, 0.0f, shiftPanel); - progressBar.ProgressGetter = GetWeekProgress; + new GUITextBlock(new Rectangle(0, 0, 200, 25), "Crew:", Color.Transparent, Color.White, Alignment.Left, rightPanel[(int)PanelTab.Crew]); - - button = new GUIButton(new Rectangle(0,0,100,30), "Start", GUI.style, - (Alignment.Right | Alignment.Bottom), shiftPanel); - button.OnClicked = StartShift; - - //--------------------------------------------------------------- - //--------------------------------------------------------------- - - rightPanel = new GUIFrame[2]; - - panelRect = new Rectangle( - panelRect.X, - panelRect.Y + panelRect.Height + (int)(GUI.style.largePadding.Y), - panelRect.Width, - (int)(Game1.GraphicsHeight * 0.7f) - (int)(GUI.style.largePadding.Y * 1.5f)); - - rightPanel[0] = new GUIFrame(panelRect, GUI.style.backGroundColor); - rightPanel[0].Padding = GUI.style.smallPadding; - - new GUITextBlock(new Rectangle(0, 0, 200, 25), "Crew:", Color.Transparent, Color.White, Alignment.Left, rightPanel[0]); - - characterList = new GUIListBox(new Rectangle(0, 30, 300, 0), Color.White, rightPanel[0]); + characterList = new GUIListBox(new Rectangle(0, 30, 300, 0), Color.White, rightPanel[(int)PanelTab.Crew]); characterList.OnSelected = SelectCharacter; //--------------------------------------- - rightPanel[1] = new GUIFrame(panelRect, GUI.style.backGroundColor); - rightPanel[1].Padding = GUI.style.smallPadding; + rightPanel[(int)PanelTab.Map] = new GUIFrame(panelRect, GUI.style.backGroundColor); + rightPanel[(int)PanelTab.Map].Padding = GUI.style.smallPadding; - hireList = new GUIListBox(new Rectangle(0, 30, 300, 0), Color.White, Alignment.Left, rightPanel[1]); + startButton = new GUIButton(new Rectangle(0, 0, 100, 30), "Start", GUI.style, + Alignment.BottomRight, rightPanel[(int)PanelTab.Map]); + startButton.OnClicked = StartShift; + startButton.Enabled = false; + + //--------------------------------------- + + rightPanel[(int)PanelTab.Hire] = new GUIFrame(panelRect, GUI.style.backGroundColor); + rightPanel[(int)PanelTab.Hire].Padding = GUI.style.smallPadding; + + hireList = new GUIListBox(new Rectangle(0, 30, 300, 0), Color.White, Alignment.Left, rightPanel[(int)PanelTab.Hire]); hireList.OnSelected = HireCharacter; } @@ -168,7 +165,22 @@ namespace Subsurface previewCharacter.animController.UpdateAnim((float)Physics.step); Game1.world.Step((float)Physics.step); } + } + public void SelectLocation(Location location, LocationConnection connection) + { + GUIComponent locationPanel = rightPanel[(int)PanelTab.Map].GetChild("selectedlocation"); + + if (locationPanel != null) rightPanel[(int)PanelTab.Map].RemoveChild(locationPanel); + + locationPanel = new GUIFrame(new Rectangle(0, 0, rightPanel[(int)PanelTab.Map].Rect.Width / 2 - 40, 190), Color.Transparent, rightPanel[(int)PanelTab.Map]); + locationPanel.UserData = "selectedlocation"; + + new GUITextBlock(new Rectangle(0,0,100,20), location.Name, Color.Transparent, Color.White, Alignment.TopLeft, locationPanel); + + startButton.Enabled = true; + + selectedLevel = connection.Level; } private void UpdateCharacterLists() @@ -220,7 +232,7 @@ namespace Subsurface leftPanel.Update((float)deltaTime); rightPanel[selectedRightPanel].Update((float)deltaTime); - shiftPanel.Update((float)deltaTime); + //shiftPanel.Update((float)deltaTime); } public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) @@ -239,10 +251,17 @@ namespace Subsurface spriteBatch.Begin(); leftPanel.Draw(spriteBatch); - shiftPanel.Draw(spriteBatch); rightPanel[selectedRightPanel].Draw(spriteBatch); + if (selectedRightPanel == (int)PanelTab.Map) + { + Game1.GameSession.map.Draw(spriteBatch, new Rectangle( + rightPanel[selectedRightPanel].Rect.Right - 20 - 400, + rightPanel[selectedRightPanel].Rect.Y + 20, + 400, 400)); + } + GUI.Draw((float)deltaTime, spriteBatch, null); spriteBatch.End(); @@ -352,7 +371,7 @@ namespace Subsurface private bool StartShift(GUIButton button, object selection) { - Game1.GameSession.StartShift(TimeSpan.Zero); + Game1.GameSession.StartShift(TimeSpan.Zero, selectedLevel); Game1.GameScreen.Select(); return true; diff --git a/Subsurface/Screens/MainMenu.cs b/Subsurface/Screens/MainMenu.cs index 5e442a034..5a8361e8f 100644 --- a/Subsurface/Screens/MainMenu.cs +++ b/Subsurface/Screens/MainMenu.cs @@ -64,7 +64,7 @@ namespace Subsurface new GUITextBlock(new Rectangle(0, 30, 0, 30), "Selected map:", Color.Transparent, Color.Black, Alignment.Left, menuTabs[(int)Tabs.NewGame]); mapList = new GUIListBox(new Rectangle(0, 60, 200, 400), Color.White, menuTabs[(int)Tabs.NewGame]); - foreach (Map map in Map.SavedMaps) + foreach (Submarine map in Submarine.SavedSubmarines) { GUITextBlock textBlock = new GUITextBlock( new Rectangle(0, 0, 0, 25), @@ -76,7 +76,7 @@ namespace Subsurface textBlock.Padding = new Vector4(10.0f, 0.0f, 0.0f, 0.0f); textBlock.UserData = map; } - if (Map.SavedMaps.Count > 0) mapList.Select(Map.SavedMaps[0]); + if (Submarine.SavedSubmarines.Count > 0) mapList.Select(Submarine.SavedSubmarines[0]); button = new GUIButton(new Rectangle(0, 0, 100, 30), "Start", GUI.style, Alignment.Right | Alignment.Bottom, menuTabs[(int)Tabs.NewGame]); @@ -173,7 +173,7 @@ namespace Subsurface private bool StartGame(GUIButton button, object obj) { - Map selectedMap = mapList.SelectedData as Map; + Submarine selectedMap = mapList.SelectedData as Submarine; if (selectedMap == null) return false; Game1.GameSession = new GameSession(selectedMap, GameModePreset.list.Find(gm => gm.Name == "Single Player")); diff --git a/Subsurface/Screens/NetLobbyScreen.cs b/Subsurface/Screens/NetLobbyScreen.cs index d2e304ce3..3336027b9 100644 --- a/Subsurface/Screens/NetLobbyScreen.cs +++ b/Subsurface/Screens/NetLobbyScreen.cs @@ -30,9 +30,9 @@ namespace Subsurface public bool isServer; - public Map SelectedMap + public Submarine SelectedMap { - get { return mapList.SelectedData as Map; } + get { return mapList.SelectedData as Submarine; } } @@ -135,9 +135,9 @@ namespace Subsurface mapList.OnSelected = SelectMap; mapList.Enabled = (Game1.Server!=null); - if (Map.SavedMaps.Count>0) + if (Submarine.SavedSubmarines.Count>0) { - foreach (Map map in Map.SavedMaps) + foreach (Submarine map in Submarine.SavedSubmarines) { GUITextBlock textBlock = new GUITextBlock( new Rectangle(0, 0, 0, 25), @@ -245,10 +245,10 @@ namespace Subsurface { if (Game1.Server != null) Game1.Server.UpdateNetLobby(obj); - Map map = (Map)obj; + Submarine map = (Submarine)obj; //map already loaded - if (Map.Loaded!=null && map.FilePath == Map.Loaded.FilePath) return true; + if (Submarine.Loaded!=null && map.FilePath == Submarine.Loaded.FilePath) return true; map.Load(); @@ -281,13 +281,13 @@ namespace Subsurface Game1.GameScreen.Cam.MoveCamera((float)deltaTime); Vector2 pos = new Vector2( - Map.Borders.X + Map.Borders.Width / 2, - Map.Borders.Y - Map.Borders.Height / 2); + Submarine.Borders.X + Submarine.Borders.Width / 2, + Submarine.Borders.Y - Submarine.Borders.Height / 2); camAngle += (float)deltaTime / 10.0f; Vector2 offset = (new Vector2( - (float)Math.Cos(camAngle) * (Map.Borders.Width / 2.0f), - (float)Math.Sin(camAngle) * (Map.Borders.Height / 2.0f))); + (float)Math.Cos(camAngle) * (Submarine.Borders.Width / 2.0f), + (float)Math.Sin(camAngle) * (Submarine.Borders.Height / 2.0f))); pos += offset * 0.8f; @@ -466,7 +466,7 @@ namespace Subsurface public void WriteData(NetOutgoingMessage msg) { - Map selectedMap = mapList.SelectedData as Map; + Submarine selectedMap = mapList.SelectedData as Submarine; if (selectedMap==null) { @@ -476,7 +476,7 @@ namespace Subsurface else { msg.Write(Path.GetFileName(selectedMap.Name)); - msg.Write(selectedMap.MapHash.MD5Hash); + msg.Write(selectedMap.Hash.MD5Hash); } msg.Write(modeList.SelectedIndex); @@ -486,7 +486,7 @@ namespace Subsurface public bool TrySelectMap(string mapName, string md5Hash) { - Map map = Map.SavedMaps.Find(m => m.Name == mapName); + Submarine map = Submarine.SavedSubmarines.Find(m => m.Name == mapName); if (map==null) { DebugConsole.ThrowError("The map ''" + mapName + "'' has been selected by the server."); @@ -495,10 +495,10 @@ namespace Subsurface } else { - if (map.MapHash.MD5Hash!=md5Hash) + if (map.Hash.MD5Hash!=md5Hash) { DebugConsole.ThrowError("Your version of the map file ''"+map.Name+"'' doesn't match the server's version!"); - DebugConsole.ThrowError("Your file: "+map.Name+"(MD5 hash : "+map.MapHash.MD5Hash+")"); + DebugConsole.ThrowError("Your file: "+map.Name+"(MD5 hash : "+map.Hash.MD5Hash+")"); DebugConsole.ThrowError("Server's file: " + mapName + "(MD5 hash : " + md5Hash + ")"); return false; } diff --git a/Subsurface/Subsurface.csproj b/Subsurface/Subsurface.csproj index 11d227d65..34f6dd413 100644 --- a/Subsurface/Subsurface.csproj +++ b/Subsurface/Subsurface.csproj @@ -127,7 +127,9 @@ - + + + @@ -161,7 +163,7 @@ - + diff --git a/Subsurface/ToolBox.cs b/Subsurface/ToolBox.cs index 57b9bdd3d..e2d183aa4 100644 --- a/Subsurface/ToolBox.cs +++ b/Subsurface/ToolBox.cs @@ -25,10 +25,20 @@ namespace Subsurface public static float RandomFloat(float minimum, float maximum) { - return (float)Game1.localRandom.NextDouble() * (maximum - minimum) + minimum; + return (float)Game1.random.NextDouble() * (maximum - minimum) + minimum; } public static int RandomInt(int minimum, int maximum) + { + return Game1.random.Next(maximum - minimum) + minimum; + } + + public static float RandomFloatLocal(float minimum, float maximum) + { + return (float)Game1.localRandom.NextDouble() * (maximum - minimum) + minimum; + } + + public static int RandomIntLocal(int minimum, int maximum) { return Game1.localRandom.Next(maximum - minimum) + minimum; } diff --git a/Subsurface_Solution.v12.suo b/Subsurface_Solution.v12.suo index 83392413fd386fc231ccf1f83ee523adcf2924bf..ccbf38e728604c587abbad881141ddad11c3e2f7 100644 GIT binary patch delta 21777 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