From c6f52cc68fcb14199cf9ed196177812e6daa694b Mon Sep 17 00:00:00 2001 From: Regalis Date: Fri, 12 Feb 2016 19:39:24 +0200 Subject: [PATCH] Voting for round to end, level generation improvements --- Subsurface/Content/Characters/Husk/husk.xml | 1 + Subsurface/Content/Items/Weapons/railgun.xml | 2 +- Subsurface/Source/Characters/Character.cs | 4 +- Subsurface/Source/DebugConsole.cs | 10 +- .../Source/Events/Quests/MonsterQuest.cs | 2 +- Subsurface/Source/GUI/GUIScrollBar.cs | 4 +- .../GameModes/Tutorials/BasicTutorial.cs | 6 +- .../GameModes/Tutorials/TutorialType.cs | 2 +- Subsurface/Source/GameSettings.cs | 4 +- Subsurface/Source/Map/Levels/Level.cs | 88 +++++++++------- Subsurface/Source/Map/Submarine.cs | 29 +++++- Subsurface/Source/Map/SubmarineBody.cs | 98 ++++++++++-------- Subsurface/Source/Networking/GameClient.cs | 43 +++++++- Subsurface/Source/Networking/GameServer.cs | 24 ++++- .../Source/Networking/GameServerSettings.cs | 43 ++++++-- Subsurface/Source/Networking/NetworkMember.cs | 13 ++- Subsurface/Source/Screens/EditMapScreen.cs | 1 + Subsurface/Source/Screens/NetLobbyScreen.cs | 4 + Subsurface/Source/Screens/NetLobbyVoting.cs | 45 ++++++-- Subsurface_Solution.v12.suo | Bin 903680 -> 873984 bytes 20 files changed, 294 insertions(+), 129 deletions(-) diff --git a/Subsurface/Content/Characters/Husk/husk.xml b/Subsurface/Content/Characters/Husk/husk.xml index 052271843..137bd0eeb 100644 --- a/Subsurface/Content/Characters/Husk/husk.xml +++ b/Subsurface/Content/Characters/Husk/husk.xml @@ -11,6 +11,7 @@ + diff --git a/Subsurface/Content/Items/Weapons/railgun.xml b/Subsurface/Content/Items/Weapons/railgun.xml index f77e5ead4..ad75a5009 100644 --- a/Subsurface/Content/Items/Weapons/railgun.xml +++ b/Subsurface/Content/Items/Weapons/railgun.xml @@ -110,7 +110,7 @@ - + diff --git a/Subsurface/Source/Characters/Character.cs b/Subsurface/Source/Characters/Character.cs index c32565245..1a7c16bac 100644 --- a/Subsurface/Source/Characters/Character.cs +++ b/Subsurface/Source/Characters/Character.cs @@ -915,7 +915,7 @@ namespace Barotrauma if (isDead) return; - if (!(AnimController is FishAnimController)) + if (needsAir) { bool protectedFromPressure = PressureProtection > 0.0f; @@ -1445,7 +1445,7 @@ namespace Barotrauma else { Character character = FindEntityByID(characterId) as Character; - if (character != null) SelectCharacter(character, false); + if (character != null && character.IsHumanoid) SelectCharacter(character, false); } return; case NetworkEventType.KillCharacter: diff --git a/Subsurface/Source/DebugConsole.cs b/Subsurface/Source/DebugConsole.cs index 0e0ee524d..d16e443e1 100644 --- a/Subsurface/Source/DebugConsole.cs +++ b/Subsurface/Source/DebugConsole.cs @@ -382,16 +382,8 @@ namespace Barotrauma return; } if (Submarine.SaveCurrent(fileName +".sub")) NewMessage("map saved", Color.Green); + Submarine.Loaded.CheckForErrors(); - if (WayPoint.WayPointList.Find(wp => !wp.MoveWithLevel && wp.SpawnType == SpawnType.Path)==null) - { - DebugConsole.ThrowError("No waypoints found in the submarine. AI controlled crew members won't be able to navigate without waypoints."); - } - - if (WayPoint.WayPointList.Find(wp => wp.SpawnType == SpawnType.Cargo) == null) - { - DebugConsole.ThrowError("The submarine doesn't have a waypoint marked as ''Cargo'', which are used for determining where to place bought items."); - } break; case "loadmap": case "loadsub": diff --git a/Subsurface/Source/Events/Quests/MonsterQuest.cs b/Subsurface/Source/Events/Quests/MonsterQuest.cs index 13581fe82..6a7882541 100644 --- a/Subsurface/Source/Events/Quests/MonsterQuest.cs +++ b/Subsurface/Source/Events/Quests/MonsterQuest.cs @@ -27,7 +27,7 @@ namespace Barotrauma public override void Start(Level level) { - Vector2 position = level.GetRandomInterestingPosition(monster.Mass > 500.0f, true); + Vector2 position = level.GetRandomInterestingPosition(true, true); monster = Character.Create(monsterFile, position); monster.Enabled = false; diff --git a/Subsurface/Source/GUI/GUIScrollBar.cs b/Subsurface/Source/GUI/GUIScrollBar.cs index df635b1ca..b83047cbf 100644 --- a/Subsurface/Source/GUI/GUIScrollBar.cs +++ b/Subsurface/Source/GUI/GUIScrollBar.cs @@ -17,7 +17,7 @@ namespace Barotrauma private bool enabled; - public delegate bool OnMovedHandler(float barScroll); + public delegate bool OnMovedHandler(GUIScrollBar scrollBar, float barScroll); public OnMovedHandler OnMoved; public bool IsHorizontal @@ -174,7 +174,7 @@ namespace Barotrauma barScroll = (float)newY / ((float)frame.Rect.Height - (float)bar.Rect.Height); } - if (moveAmount != 0 && OnMoved != null) OnMoved(barScroll); + if (moveAmount != 0 && OnMoved != null) OnMoved(this, barScroll); bar.Rect = new Rectangle(newX + frame.Rect.X, newY + frame.Rect.Y, bar.Rect.Width, bar.Rect.Height); diff --git a/Subsurface/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs b/Subsurface/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs index 5caeb23fd..65fe392fb 100644 --- a/Subsurface/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs +++ b/Subsurface/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs @@ -17,7 +17,7 @@ namespace Barotrauma.Tutorials public override IEnumerable UpdateState() { - Submarine.Loaded.SetPosition(new Vector2(Submarine.Loaded.Position.X, 38500.0f)); + //Submarine.Loaded.SetPosition(new Vector2(Submarine.Loaded.Position.X, 38500.0f)); //spawn some fish next to the player GameMain.GameScreen.BackgroundCreatureManager.SpawnSprites(2, @@ -91,7 +91,7 @@ namespace Barotrauma.Tutorials { yield return CoroutineStatus.Running; } - + infoBox = CreateInfoFrame("The reactor is now fueled up. Try turning it on by increasing the fission rate."); while (reactor.FissionRate <= 0.0f) @@ -275,7 +275,7 @@ namespace Barotrauma.Tutorials infoBox = CreateInfoFrame("Steer the submarine downwards, heading further into the cavern."); - while (Submarine.Loaded.WorldPosition.Y > 31000.0f) + while (Submarine.Loaded.WorldPosition.Y > 39000.0f) { yield return CoroutineStatus.Running; } diff --git a/Subsurface/Source/GameSession/GameModes/Tutorials/TutorialType.cs b/Subsurface/Source/GameSession/GameModes/Tutorials/TutorialType.cs index 4914ddb00..4c1811280 100644 --- a/Subsurface/Source/GameSession/GameModes/Tutorials/TutorialType.cs +++ b/Subsurface/Source/GameSession/GameModes/Tutorials/TutorialType.cs @@ -42,7 +42,7 @@ namespace Barotrauma.Tutorials GameMain.GameSession = new GameSession(Submarine.Loaded, "", GameModePreset.list.Find(gm => gm.Name.ToLower() == "tutorial")); (GameMain.GameSession.gameMode as TutorialMode).tutorialType = this; - GameMain.GameSession.StartShift("tuto"); + GameMain.GameSession.StartShift("tutorial"); GameMain.GameSession.TaskManager.Tasks.Clear(); diff --git a/Subsurface/Source/GameSettings.cs b/Subsurface/Source/GameSettings.cs index a09070837..f15c828ac 100644 --- a/Subsurface/Source/GameSettings.cs +++ b/Subsurface/Source/GameSettings.cs @@ -248,7 +248,7 @@ namespace Barotrauma doc.Save(filePath); } - private bool ChangeSoundVolume(float barScroll) + private bool ChangeSoundVolume(GUIScrollBar scrollBar, float barScroll) { UnsavedSettings = true; SoundVolume = MathHelper.Clamp(barScroll, 0.0f, 1.0f); @@ -256,7 +256,7 @@ namespace Barotrauma return true; } - private bool ChangeMusicVolume(float barScroll) + private bool ChangeMusicVolume(GUIScrollBar scrollBar, float barScroll) { UnsavedSettings = true; MusicVolume = MathHelper.Clamp(barScroll, 0.0f, 1.0f); diff --git a/Subsurface/Source/Map/Levels/Level.cs b/Subsurface/Source/Map/Levels/Level.cs index 530003b63..70c495875 100644 --- a/Subsurface/Source/Map/Levels/Level.cs +++ b/Subsurface/Source/Map/Levels/Level.cs @@ -150,18 +150,21 @@ namespace Barotrauma float minWidth = Submarine.Loaded == null ? 3000.0f : Math.Max(Submarine.Borders.Width, Submarine.Borders.Height); - startPosition = new Vector2(minWidth * 2, borders.Height); - endPosition = new Vector2(borders.Width - minWidth * 2, borders.Height); + startPosition = new Vector2((int)minWidth * 2, Rand.Range((int)minWidth * 2, borders.Height - (int)minWidth * 2, false)); + endPosition = new Vector2(borders.Width - (int)minWidth * 2, Rand.Range((int)minWidth * 2, borders.Height - (int)minWidth * 2, false)); + List pathNodes = new List(); Rectangle pathBorders = borders;// new Rectangle((int)minWidth, (int)minWidth, borders.Width - (int)minWidth * 2, borders.Height - (int)minWidth); pathBorders.Inflate(-minWidth*2, -minWidth*2); - pathNodes.Add(startPosition); - //pathNodes.Add(new Vector2(minWidth * 3, Rand.Range(minWidth * 2, borders.Height - minWidth * 2, false))); - for (float x = startPosition.X; x < endPosition.X; x += Rand.Range(2000.0f, 10000.0f, false)) + pathNodes.Add(new Vector2(startPosition.X, borders.Height)); + pathNodes.Add(startPosition); + + + for (float x = startPosition.X; x < endPosition.X; x += Rand.Range(5000.0f, 10000.0f, false)) { pathNodes.Add(new Vector2(x, Rand.Range(pathBorders.Y, pathBorders.Bottom, false))); @@ -171,8 +174,8 @@ namespace Barotrauma //} } - //pathNodes.Add(new Vector2(borders.Width - minWidth * 3, borders.Height / 2)); pathNodes.Add(endPosition); + pathNodes.Add(new Vector2(endPosition.X, borders.Height)); int smallTunnelCount = 5; @@ -283,7 +286,8 @@ namespace Barotrauma // pathNodes.Reverse(); //} - List pathCells = GeneratePath(pathNodes, cells, pathBorders, minWidth, 0.3f, mirror, true); + List pathCells = GeneratePath(pathNodes, cells, + new Rectangle(pathBorders.X, pathBorders.Y, pathBorders.Width, borders.Height), minWidth, 0.3f, mirror, true); foreach (InterestingPosition positionOfInterest in positionsOfInterest) { @@ -291,8 +295,8 @@ namespace Barotrauma wayPoint.MoveWithLevel = true; } - startPosition = pathCells[0].Center; - endPosition = pathCells[pathCells.Count - 1].Center; + //startPosition = pathCells[0].Center; + //endPosition = pathCells[pathCells.Count - 1].Center; foreach (List tunnel in smallTunnels) { @@ -475,9 +479,7 @@ namespace Barotrauma pathCells.Add(currentCell); int currentTargetIndex = 1; - - wanderAmount = 0.0f; - + do { int edgeIndex = 0; @@ -617,30 +619,41 @@ namespace Barotrauma minDistance *= 0.5f; do { - var closeCells = GetCells(position, 2); + var closeCells = GetCells(position, 1); foreach (VoronoiCell cell in closeCells) { - if (!cell.edges.Any(e => Vector2.Distance(position, e.point1) < minDistance || Vector2.Distance(position, e.point2) < minDistance)) continue; + bool tooClose = false; + foreach (GraphEdge edge in cell.edges) + { + if (Math.Abs(position.X - edge.point1.X) < minDistance || + Math.Abs(position.Y - edge.point1.Y) < minDistance || + Math.Abs(position.X - edge.point2.X) < minDistance || + Math.Abs(position.Y - edge.point2.Y) < minDistance) + { + tooClose = true; + break; + } + } - if (!tooCloseCells.Contains(cell)) tooCloseCells.Add(cell); + if (tooClose && !tooCloseCells.Contains(cell)) tooCloseCells.Add(cell); } - //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); + 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]); - // } - // } - //} + 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; @@ -664,7 +677,7 @@ namespace Barotrauma foreach (GraphEdge edge in cell.edges) { VoronoiCell adjacent = edge.AdjacentCell(cell); - if (!newCells.Contains(adjacent)) newCells.Add(adjacent); + if (adjacent!=null && !newCells.Contains(adjacent)) newCells.Add(adjacent); } } @@ -964,7 +977,7 @@ namespace Barotrauma int tries = 0; do { - Vector2 startPos = ConvertUnits.ToSimUnits(positionsOfInterest[Rand.Int(positionsOfInterest.Count, false)].Position); + Vector2 startPos = ConvertUnits.ToSimUnits(Level.Loaded.GetRandomInterestingPosition(true, false)); Vector2 endPos = startPos - ConvertUnits.ToSimUnits(Vector2.UnitY * Size.Y); @@ -989,20 +1002,21 @@ namespace Barotrauma return position; } - public Vector2 GetRandomInterestingPosition(bool requireSpace, bool useSyncedRand) + public Vector2 GetRandomInterestingPosition(bool useSyncedRand, bool? preferLarge) { if (!positionsOfInterest.Any()) return Size * 0.5f; - if (requireSpace) + if (preferLarge==null) { - var positionsWithSpace = positionsOfInterest.FindAll(p => p.IsLarge); - if (!positionsWithSpace.Any()) return Size * 0.5f; + return positionsOfInterest[Rand.Int(positionsOfInterest.Count, !useSyncedRand)].Position; - return positionsWithSpace[Rand.Int(positionsOfInterest.Count, !useSyncedRand)].Position; } else { - return positionsOfInterest[Rand.Int(positionsOfInterest.Count, !useSyncedRand)].Position; + var positionsWithSpace = positionsOfInterest.FindAll(p => (bool)preferLarge == p.IsLarge); + if (!positionsWithSpace.Any()) return Size * 0.5f; + + return positionsWithSpace[Rand.Int(positionsOfInterest.Count, !useSyncedRand)].Position; } } diff --git a/Subsurface/Source/Map/Submarine.cs b/Subsurface/Source/Map/Submarine.cs index d5ea1d62b..907764a7b 100644 --- a/Subsurface/Source/Map/Submarine.cs +++ b/Subsurface/Source/Map/Submarine.cs @@ -524,6 +524,34 @@ namespace Barotrauma return loaded.SaveAs(SavePath+System.IO.Path.DirectorySeparatorChar+fileName); } + public void CheckForErrors() + { + if (!Hull.hullList.Any()) + { + DebugConsole.ThrowError("No hulls found in the submarine. Hulls determine the ''borders'' of an individual room and are required for water and air distribution to work correctly."); + } + + foreach (Item item in Item.ItemList) + { + if (item.GetComponent() == null) continue; + + if (!item.linkedTo.Any()) + { + DebugConsole.ThrowError("The submarine contains vents which haven't been linked to an oxygen generator. Select a vent and click an oxygen generator while holding space to link them."); + } + } + + if (WayPoint.WayPointList.Find(wp => !wp.MoveWithLevel && wp.SpawnType == SpawnType.Path) == null) + { + DebugConsole.ThrowError("No waypoints found in the submarine. AI controlled crew members won't be able to navigate without waypoints."); + } + + if (WayPoint.WayPointList.Find(wp => wp.SpawnType == SpawnType.Cargo) == null) + { + DebugConsole.ThrowError("The submarine doesn't have a waypoint marked as ''Cargo'', which are used for determining where to place bought items."); + } + } + public static void Preload() { @@ -669,7 +697,6 @@ namespace Barotrauma { DebugConsole.ThrowError("Could not find the method ''Load'' in " + t + ".", e); } - } subBody = new SubmarineBody(this); diff --git a/Subsurface/Source/Map/SubmarineBody.cs b/Subsurface/Source/Map/SubmarineBody.cs index 3b6667486..381f08f59 100644 --- a/Subsurface/Source/Map/SubmarineBody.cs +++ b/Subsurface/Source/Map/SubmarineBody.cs @@ -87,57 +87,67 @@ namespace Barotrauma { this.submarine = sub; - - List convexHull = GenerateConvexHull(); - HullVertices = convexHull; - - for (int i = 0; i < convexHull.Count; i++) + if (!Hull.hullList.Any()) { - convexHull[i] = ConvertUnits.ToSimUnits(convexHull[i]); + + body = BodyFactory.CreateRectangle(GameMain.World, 1.0f, 1.0f, 1.0f); + DebugConsole.ThrowError("WARNING: no hulls found, generating a physics body for the submarine failed."); } - - convexHull.Reverse(); - - //get farseer 'vertices' from vectors - Vertices shapevertices = new Vertices(convexHull); - - AABB hullAABB = shapevertices.GetAABB(); - - Borders = new Rectangle( - (int)ConvertUnits.ToDisplayUnits(hullAABB.LowerBound.X), - (int)ConvertUnits.ToDisplayUnits(hullAABB.UpperBound.Y), - (int)ConvertUnits.ToDisplayUnits(hullAABB.Extents.X * 2.0f), - (int)ConvertUnits.ToDisplayUnits(hullAABB.Extents.Y * 2.0f)); - - //var triangulatedVertices = Triangulate.ConvexPartition(shapevertices, TriangulationAlgorithm.Bayazit); - - body = BodyFactory.CreateBody(GameMain.World, this); - - foreach (Hull hull in Hull.hullList) + else { - Rectangle rect = hull.Rect; - foreach (Structure wall in Structure.WallList) + List convexHull = GenerateConvexHull(); + HullVertices = convexHull; + + for (int i = 0; i < convexHull.Count; i++) { - if (!Submarine.RectsOverlap(wall.Rect, hull.Rect)) continue; - - Rectangle wallRect = wall.IsHorizontal ? - new Rectangle(hull.Rect.X, wall.Rect.Y, hull.Rect.Width, wall.Rect.Height) : - new Rectangle(wall.Rect.X, hull.Rect.Y, wall.Rect.Width, hull.Rect.Height); - - rect = Rectangle.Union( - new Rectangle(wallRect.X, wallRect.Y-wallRect.Height, wallRect.Width, wallRect.Height), - new Rectangle(rect.X, rect.Y - rect.Height, rect.Width, rect.Height)); - rect.Y = rect.Y + rect.Height; + convexHull[i] = ConvertUnits.ToSimUnits(convexHull[i]); } - FixtureFactory.AttachRectangle( - ConvertUnits.ToSimUnits(rect.Width), - ConvertUnits.ToSimUnits(rect.Height), - 5.0f, - ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width/2, rect.Y - rect.Height/2)), - body, this); + convexHull.Reverse(); + + //get farseer 'vertices' from vectors + Vertices shapevertices = new Vertices(convexHull); + + AABB hullAABB = shapevertices.GetAABB(); + + Borders = new Rectangle( + (int)ConvertUnits.ToDisplayUnits(hullAABB.LowerBound.X), + (int)ConvertUnits.ToDisplayUnits(hullAABB.UpperBound.Y), + (int)ConvertUnits.ToDisplayUnits(hullAABB.Extents.X * 2.0f), + (int)ConvertUnits.ToDisplayUnits(hullAABB.Extents.Y * 2.0f)); + + //var triangulatedVertices = Triangulate.ConvexPartition(shapevertices, TriangulationAlgorithm.Bayazit); + + body = BodyFactory.CreateBody(GameMain.World, this); + + foreach (Hull hull in Hull.hullList) + { + Rectangle rect = hull.Rect; + foreach (Structure wall in Structure.WallList) + { + if (!Submarine.RectsOverlap(wall.Rect, hull.Rect)) continue; + + Rectangle wallRect = wall.IsHorizontal ? + new Rectangle(hull.Rect.X, wall.Rect.Y, hull.Rect.Width, wall.Rect.Height) : + new Rectangle(wall.Rect.X, hull.Rect.Y, wall.Rect.Width, hull.Rect.Height); + + rect = Rectangle.Union( + new Rectangle(wallRect.X, wallRect.Y - wallRect.Height, wallRect.Width, wallRect.Height), + new Rectangle(rect.X, rect.Y - rect.Height, rect.Width, rect.Height)); + rect.Y = rect.Y + rect.Height; + } + + FixtureFactory.AttachRectangle( + ConvertUnits.ToSimUnits(rect.Width), + ConvertUnits.ToSimUnits(rect.Height), + 5.0f, + ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2)), + body, this); + } } + + body.BodyType = BodyType.Dynamic; body.CollisionCategories = Physics.CollisionMisc | Physics.CollisionWall; body.CollidesWith = Physics.CollisionLevel | Physics.CollisionCharacter; @@ -275,7 +285,7 @@ namespace Barotrauma volume += hull.FullVolume; } - float waterPercentage = waterVolume / volume; + float waterPercentage = volume==0.0f ? 0.0f : waterVolume / volume; float neutralPercentage = 0.07f; diff --git a/Subsurface/Source/Networking/GameClient.cs b/Subsurface/Source/Networking/GameClient.cs index abfc37b62..ac5709976 100644 --- a/Subsurface/Source/Networking/GameClient.cs +++ b/Subsurface/Source/Networking/GameClient.cs @@ -15,6 +15,8 @@ namespace Barotrauma.Networking private ReliableChannel reliableChannel; + private GUIButton endRoundButton; + private bool connected; private int myID; @@ -28,8 +30,17 @@ namespace Barotrauma.Networking get { return myID; } } + public List OtherClients + { + get { return otherClients; } + } + public GameClient(string newName) { + endRoundButton = new GUIButton(new Rectangle(GameMain.GraphicsWidth - 170, 20, 150, 25), "End round", Alignment.TopLeft, GUI.Style, inGameHUD); + endRoundButton.OnClicked = EndRoundClicked; + endRoundButton.Visible = false; + GameMain.DebugDraw = false; Hull.EditFire = false; Hull.EditWater = false; @@ -230,7 +241,6 @@ namespace Barotrauma.Networking new GUIMessageBox("Connection timed out", "You were disconnected for too long and your Character was deleted. Please wait for another round to start."); } - GameMain.NetLobbyScreen.ClearPlayers(); //add the names of other connected clients to the lobby screen @@ -590,6 +600,8 @@ namespace Barotrauma.Networking gameStarted = true; + endRoundButton.Visible = Voting.AllowEndVoting; + GameMain.GameScreen.Select(); AddChatMessage("Press TAB to chat", ChatMessageType.Server); @@ -659,7 +671,7 @@ namespace Barotrauma.Networking public override void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch) { base.Draw(spriteBatch); - + if (!GameMain.DebugDraw) return; int width = 200, height = 300; @@ -696,13 +708,16 @@ namespace Barotrauma.Networking { case VoteType.Sub: msg.Write(((Submarine)userData).Name); - client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); break; case VoteType.Mode: msg.Write(((GameModePreset)userData).Name); - client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); + break; + case VoteType.EndRound: + msg.Write((bool)userData); break; } + + client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } public bool SpectateClicked(GUIButton button, object userData) @@ -717,6 +732,23 @@ namespace Barotrauma.Networking return false; } + public bool EndRoundClicked(GUIButton button, object userData) + { + if (!gameStarted) return false; + + if (!Voting.AllowEndVoting) + { + button.Visible = false; + return false; + } + + Vote(VoteType.EndRound, true); + + return false; + } + + + public void SendCharacterData() { if (characterInfo == null) return; @@ -794,7 +826,8 @@ namespace Barotrauma.Networking if (client.ServerConnection == null) return; - type = (gameStarted && myCharacter != null && myCharacter.IsDead) ? ChatMessageType.Dead : ChatMessageType.Default; + type = (Screen.Selected == GameMain.GameScreen && + (myCharacter == null || myCharacter.IsDead)) ? ChatMessageType.Dead : ChatMessageType.Default; ReliableMessage msg = reliableChannel.CreateMessage(); msg.InnerMessage.Write((byte)PacketTypes.Chatmessage); diff --git a/Subsurface/Source/Networking/GameServer.cs b/Subsurface/Source/Networking/GameServer.cs index a1ddbcf48..6bce006b0 100644 --- a/Subsurface/Source/Networking/GameServer.cs +++ b/Subsurface/Source/Networking/GameServer.cs @@ -473,6 +473,12 @@ namespace Barotrauma.Networking break; case (byte)PacketTypes.Vote: Voting.RegisterVote(inc, ConnectedClients); + + if (Voting.AllowEndVoting && + ((float)EndVoteCount / (float)EndVoteMax) >= EndVoteRequiredRatio) + { + EndButtonHit(null,null); + } break; case (byte)PacketTypes.RequestNetLobbyUpdate: UpdateNetLobby(null, null); @@ -860,9 +866,11 @@ namespace Barotrauma.Networking private bool EndButtonHit(GUIButton button, object obj) { + if (!gameStarted) return false; + string endMessage = "The round has ended." + '\n'; - if (TraitorManager!=null) + if (TraitorManager != null) { endMessage += TraitorManager.GetEndMessage(); } @@ -1092,7 +1100,7 @@ namespace Barotrauma.Networking base.Draw(spriteBatch); if (settingsFrame != null) settingsFrame.Draw(spriteBatch); - + if (!ShowNetStats) return; int width = 200, height = 300; @@ -1465,14 +1473,22 @@ namespace Barotrauma.Networking jobPreferences = new List(JobPrefab.List.GetRange(0,3)); } - public object GetVote(VoteType voteType) + public T GetVote(VoteType voteType) { - return votes[(int)voteType]; + return (votes[(int)voteType] is T) ? (T)votes[(int)voteType] : default(T); } public void SetVote(VoteType voteType, object value) { votes[(int)voteType] = value; } + + public void ResetVotes() + { + for (int i = 0; i { endRoundAtLevelEnd = GUITickBox.Selected; return true; }; + + var endVoteBox = new GUITickBox(new Rectangle(0, 90, 20, 20), "End round by voting", Alignment.Left, innerFrame); + endVoteBox.Selected = Voting.AllowEndVoting; + endVoteBox.OnSelected = (GUITickBox) => + { + Voting.AllowEndVoting = !Voting.AllowEndVoting; + GameMain.Server.UpdateVoteStatus(); + return true; + }; + + var votesRequiredText = new GUITextBlock(new Rectangle(20, 110, 20, 20), "Votes required: 50 %", GUI.Style, innerFrame, GUI.SmallFont); + + var votesRequiredSlider = new GUIScrollBar(new Rectangle(150,115, 100, 10), GUI.Style, 0.1f, innerFrame); + votesRequiredSlider.UserData = votesRequiredText; + votesRequiredSlider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + GUITextBlock voteText = scrollBar.UserData as GUITextBlock; + + scrollBar.BarScroll = MathUtils.Round(barScroll, 0.2f); + EndVoteRequiredRatio = barScroll/2.0f + 0.5f; + voteText.Text = "Votes required: " + (int)MathUtils.Round(EndVoteRequiredRatio * 100.0f, 10.0f) + " %"; + return true; + }; - new GUITextBlock(new Rectangle(0, 95, 100, 20), "Submarine selection:", GUI.Style, innerFrame); - var selectionFrame = new GUIFrame(new Rectangle(0, 120, 300, 20), null, innerFrame); + new GUITextBlock(new Rectangle(0, 95+50, 100, 20), "Submarine selection:", GUI.Style, innerFrame); + var selectionFrame = new GUIFrame(new Rectangle(0, 120 + 50, 300, 20), null, innerFrame); for (int i = 0; i<3; i++) { var selectionTick = new GUITickBox(new Rectangle(i * 100, 0, 20, 20), ((SelectionMode)i).ToString(), Alignment.Left, selectionFrame); @@ -110,9 +135,9 @@ namespace Barotrauma.Networking selectionTick.OnSelected = SwitchSubSelection; selectionTick.UserData = (SelectionMode)i; } - - new GUITextBlock(new Rectangle(0, 145, 100, 20), "Mode selection:", GUI.Style, innerFrame); - selectionFrame = new GUIFrame(new Rectangle(0, 170, 300, 20), null, innerFrame); + + new GUITextBlock(new Rectangle(0, 145 + 50, 100, 20), "Mode selection:", GUI.Style, innerFrame); + selectionFrame = new GUIFrame(new Rectangle(0, 170 + 50, 300, 20), null, innerFrame); for (int i = 0; i<3; i++) { var selectionTick = new GUITickBox(new Rectangle(i*100, 0, 20, 20), ((SelectionMode)i).ToString(), Alignment.Left, selectionFrame); @@ -120,8 +145,8 @@ namespace Barotrauma.Networking selectionTick.OnSelected = SwitchModeSelection; selectionTick.UserData = (SelectionMode)i; } - - var allowSpecBox = new GUITickBox(new Rectangle(0, 210, 20, 20), "Allow spectating", Alignment.Left, innerFrame); + + var allowSpecBox = new GUITickBox(new Rectangle(0, 210 + 50, 20, 20), "Allow spectating", Alignment.Left, innerFrame); allowSpecBox.Selected = true; allowSpecBox.OnSelected = ToggleAllowSpectating; diff --git a/Subsurface/Source/Networking/NetworkMember.cs b/Subsurface/Source/Networking/NetworkMember.cs index fb47c8bd2..767a9eaaa 100644 --- a/Subsurface/Source/Networking/NetworkMember.cs +++ b/Subsurface/Source/Networking/NetworkMember.cs @@ -47,7 +47,7 @@ namespace Barotrauma.Networking class NetworkMember { - protected static Color[] messageColor = { Color.White, Color.Red, Color.LightBlue, Color.LightGreen }; + protected static Color[] messageColor = { Color.White, Color.Red, new Color(63,72,204), Color.LightGreen }; protected NetPeer netPeer; @@ -60,6 +60,10 @@ namespace Barotrauma.Networking protected GUIListBox chatBox; protected GUITextBox chatMsgBox; + + public int EndVoteCount, EndVoteMax; + //private GUITextBlock endVoteText; + public int Port; protected bool gameStarted; @@ -122,7 +126,6 @@ namespace Barotrauma.Networking chatMsgBox.Padding = Vector4.Zero; chatMsgBox.OnEnterPressed = EnterChatMessage; - Voting = new Voting(); } @@ -247,6 +250,12 @@ namespace Barotrauma.Networking GameMain.GameSession.CrewManager.Draw(spriteBatch); inGameHUD.Draw(spriteBatch); + + if (EndVoteCount > 0) + { + GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 150.0f, 40), "Votes: " + EndVoteCount + "/" + EndVoteMax, Color.White); + } + } public virtual bool SelectCrewCharacter(GUIComponent component, object obj) diff --git a/Subsurface/Source/Screens/EditMapScreen.cs b/Subsurface/Source/Screens/EditMapScreen.cs index 44f863ada..f5d9285ff 100644 --- a/Subsurface/Source/Screens/EditMapScreen.cs +++ b/Subsurface/Source/Screens/EditMapScreen.cs @@ -292,6 +292,7 @@ namespace Barotrauma } Submarine.SaveCurrent(nameBox.Text + ".sub"); + Submarine.Loaded.CheckForErrors(); GUI.AddMessage("Submarine saved to " + Submarine.Loaded.FilePath, Color.Green, 3.0f); diff --git a/Subsurface/Source/Screens/NetLobbyScreen.cs b/Subsurface/Source/Screens/NetLobbyScreen.cs index 8607b25df..133383100 100644 --- a/Subsurface/Source/Screens/NetLobbyScreen.cs +++ b/Subsurface/Source/Screens/NetLobbyScreen.cs @@ -355,6 +355,8 @@ namespace Barotrauma playYourself.UserData = "playyourself"; } + GameMain.Server.Voting.ResetVotes(GameMain.Server.ConnectedClients); + if (GameMain.Server.RandomizeSeed) LevelSeed = ToolBox.RandomSeed(8); if (GameMain.Server.SubSelectionMode == SelectionMode.Random) subList.Select(Rand.Range(0,subList.CountChildren)); if (GameMain.Server.ModeSelectionMode == SelectionMode.Random) modeList.Select(Rand.Range(0, modeList.CountChildren)); @@ -368,6 +370,8 @@ namespace Barotrauma spectateButton.UserData = "spectateButton"; } + GameMain.Client.Voting.ResetVotes(GameMain.Client.OtherClients); + UpdatePlayerFrame(GameMain.Client.CharacterInfo); } diff --git a/Subsurface/Source/Screens/NetLobbyVoting.cs b/Subsurface/Source/Screens/NetLobbyVoting.cs index 6f6bee116..c4b5a53f6 100644 --- a/Subsurface/Source/Screens/NetLobbyVoting.cs +++ b/Subsurface/Source/Screens/NetLobbyVoting.cs @@ -12,6 +12,8 @@ namespace Barotrauma { private bool allowSubVoting, allowModeVoting; + public bool AllowEndVoting = true; + public bool AllowSubVoting { get { return allowSubVoting; } @@ -83,7 +85,12 @@ namespace Barotrauma UpdateVoteTexts(connectedClients, voteType); break; case VoteType.EndRound: + if (sender.Character == null) return; sender.SetVote(voteType, inc.ReadBoolean()); + + GameMain.NetworkMember.EndVoteCount = connectedClients.Count(c => c.Character != null && c.GetVote(VoteType.EndRound)); + GameMain.NetworkMember.EndVoteMax = connectedClients.Count(c => c.Character != null); + break; } @@ -107,7 +114,7 @@ namespace Barotrauma SetVoteText(listBox, votable.First, votable.Second); } } - + private void SetVoteText(GUIListBox listBox, object userData, int votes) { if (userData == null) return; @@ -124,14 +131,14 @@ namespace Barotrauma voteText.Text = votes.ToString(); } } - + private List> GetVoteList(VoteType voteType, List voters) { List> voteList = new List>(); foreach (Client voter in voters) { - object vote = voter.GetVote(voteType); + object vote = voter.GetVote(voteType); if (vote == null) continue; var existingVotable = voteList.Find(v => v.First == vote); @@ -167,6 +174,20 @@ namespace Barotrauma return selected; } + + public void ResetVotes(List connectedClients) + { + foreach (Client client in connectedClients) + { + client.ResetVotes(); + } + + GameMain.NetworkMember.EndVoteCount = 0; + GameMain.NetworkMember.EndVoteMax = 0; + + UpdateVoteTexts(connectedClients, VoteType.Mode); + UpdateVoteTexts(connectedClients, VoteType.Sub); + } public void WriteData(NetOutgoingMessage msg, List voters) { @@ -198,17 +219,22 @@ namespace Barotrauma } } - + msg.Write(AllowEndVoting); + if (AllowEndVoting) + { + msg.Write((byte)voters.Count); + msg.Write((byte)voters.Count(v => v.GetVote(VoteType.EndRound))); + } } public void ReadData(NetIncomingMessage msg) { - AllowSubVoting = msg.ReadBoolean(); + AllowSubVoting = msg.ReadBoolean(); if (allowSubVoting) { int votableCount = msg.ReadByte(); - for (int i = 0; iK~WQHWi7Rki2~5$`z3-13v%{GMlYA!b`w*#2296Lf~;= zDXU%s?}{dxPhlF54aMEQ?sx8XArc^~5)k$)Cp7qq_? zVGP1yKpwz>yMW2Q-b!<~csn0&-GiK`fsUxX9O;&bi!z^AiIj4fZ;evI-tcu$n<}59 z#5YLqFXrNVT1{}zL-n3`tp!3aG9LtLfmOg{+KEC!YU zO94^79AR;Nd<8-uupXd{kEBk@>mv5VM^d4u60tqNUf>O2AMhsd7VsxvKX3pLZ3-Mj z{19*$co(4LR%&?Y2Z$X5M2+JJKcufel3LRfe~}&@jO73L38;Yyh(UK0gPye_B2Bv$ zd};^Kp1x8OUsmJB(h<`)Gona(&-8s4aae1LvQ7=l?|aH_vppM)EIhYCKz0LZh-X5? z3K6EG{1EaM0|S6nKr@u}M+m}Q8Ho6D)H@G+0~8>?E%KgYlzxFl%^QmJF5o!I(3I_E z#5W_H2UGzr)D`U*i0=g60S=>17v%d8|98Y+1ImHMNOwYhF5+Uy8HncrLU?x}U5)(n zz-S;7_#?B?XBSv#%v29_D>Gv2#e9zM7#UW1&Bn0`l0L#$e9F(5` z?nSyk;6!{S@HNl_>AUdG4#Xcq-M4|k$csm~8hM?8a>Ty`8X^7yFcIY&knREe3F%Hi zbF?v!*>1KyiA)t09!0nZ1#ZN}fci+j%dO(J$A;C&K5x%h9vh<6F!H{{;(Y$^qPF*p z+=&H) zm$6@p^KHB}dV9}4CruuApH$6g=ozCM6;~S(+tV}3?;-DJEN*+8=Q7(KySb+23L{(- zRxJW%%olB>>P-=e1TOnV8(m#j&0-sogKC>LYbsM_ippEfL}i|cRGV(eYX$}H@Z}ah zcZ$N}wz5miSNe^zJ?`A42Yv3kmb&eEtZG6PuNfTm5?D#}y~f(mh7Z_K^(dOFr1x4& zw@__sDVi#)O^0vb*B@+h4`cB@cYlTA{7R|T5z>AuaIGOa?^mj%9uf`isI>AWOS*Lr z*s~aYiHDXC(Qc-ykBo4B%N+Jia~@l#e9Y)~^Cai;(q!&g$7-F|N9e~WuQmKLBNQX{ z#U{jRFZ{!l{`^x@c<1YTd~R2Qa!13qeTA;UzBj@<_|Cfq>$clb)bGaP&?Qtd(h4Eh zi&`AjjijI!Ic{|Z6|QB2=?ynap>Nl+be#zsH~a>=6gE~eD6K~BK;Jf2 z+HG?0U~cX{$!b}}G3Ivp+>xN@M#b&hCnfX3jmlQ$bFTsOPO<7nCFQJC5t2+B%UN8T zp&;a0;4R=z;BjC+`uY?~dK2&w(kp;-^z|U=W=WPPWIKzOHZtn6oyEip*(pP{gQ)Ta z!qo_U2>$?l2n&Ai>lRTJ@pHg9pcb%D zF6xCNd>F6+u>i8J7}Q~a+?*|ER8UYV0Wo;H8tDxPCjke5LG^EzA}$7= zinJ3*2lgSa5aCh0Q-}CP;7wp1z>wbo=nH&{@(vj7F@#N!z6-crvh}rHM23ROVaPa+ zFa+t}BNWSR8uDI2{7ax2ULJ|G@OeGJ1mtx`*w#*M2g*_N?m_%cpbgL-Xbtp2od*yO z1&R>A0E`6sqV3ZNTcS-tfE2_Z0Vbf{36%W}n2GdbKtJTg)YpqZzNjxqJ01DoqFkgq z*lek`6cjG3uOKv16bNcvecrCGd^If^5zjQ}*JzJ=GmpB&PIf49NpU=GD63{{=BnP3 zhxU%JM^fomdOV+(EX_AdEM>p)GP=2@lTwvQ-&snLJrARd989yF$^h+V)NnVfWxNeV z)Q|(G{PJy*6cStyHLQnh2lXDM_^96tS}dQ@aY`iTd=-UzE?tCvYLxHBiX%x-|)&i}88elo{ z?*^6v-vHf#5k9?9uX)oE%R*CW2>%T=TOs@%Fb&8?`Uo%p<=-J>$nyh&rW(TUfdn8N zSb*|GkmnJEXOJEXWB_}S?uz$MA^r)F2fPmC1F6i`)mDheXjFIz;Tm8YFc`1{FW}`N zz>A2#1PlY*=)?i1BRP)8W=b`UDk>!=ah5AxG5r^rRN|5M5L+sbq2w|a=JLIqk~MDW z`scfU=IIx5AgP>2XR>g*x3|2BJ?Wc&?_Bcc*+ck}BB`*oKUI=^>%9>?VzXq}x%(xS zz!c%oU6TVUe3>ormq|*+MX8Q8`Vni13Z4u1KsIgs$}18xekEf=BBcJ3Z)Ql$FY;Fq z`d`3bAy)rl{_@LvBuObrkgIC=rhnC;b(=i;_>Zg!-B+Oa*+iPJ%blpW7wg0))kviJ{gUKA zDTTA4Fay1~XQWao8tWZ_<*n+9qaT0dlJuC&W}&nXmDQiX&;PV@VkZK$q;t58-!+N0y&6}M@|R)9gV+Z5hmepfo=1@CA^?s z3bj96sFiO%Hv7?lh_f}mJ>7ay`H$KlJ}6q=ag%?PEcv@(krZn=k1J9(GDR5eYr?I* zrNZ&`eV^RM7x%YlU(awmPn#n>%4kSOrKRw2%=fR^HyedcG^g^Mp7KgK2MID7?4~@& zgl}QB#FTf7IpXp6k?rjn6jlOcKmk<14mf~-^+rVlaT91ty_wo>d8xvB)BI1BrWsp7 zy!$ZBn}Bx^7aAd+NTlCykQR9#)TguS!}e$}3AhF542U|k?-M0QKZzRS(L!#0ci}Ya zQzcDW!f5xWN^Hy{^_9g7a}b{k6an)9uiaPF6Q(BuX5F`mELCki0gOhQLv5 zbY;o7@l7ti-DG2{@MQhwB6_a}i|}PlRrv!GmFJm1OO|}zCb7N^wXxEj@-_F77anEk zzFd*JP;HWBaL*@FYvyYH=lCH9mUX&x!9B!!Kkn`%nqO@;rDdN=`Q+&!=~So5ZfaAm zRJh*yGEV)=rd9d155C7@m)(-izbaQkSu-((XE9r$(gF>iAD1t0N}E5lyI38C-mHGH zytE2R1zvt9V{ObQHbognwc8vTpPD24LjBbWhW-P#cwV{2v7(bZ=()WFhu{CCES1cb z9&)W3>Bx@1Kk>OX6X%R;U3_$GKo!hbbOBwcC7JTdaf)7!DOSZ7!W1o{= z%~p&8m5$0aa?o?$SnSq>^K>_GP2Z?+r7~D%>DRXqbUu(Xq(M&MDrJfPBU!3=OZtRK zc9TxZY7d{gvBKkOz1Nt-X#-FH;mqpNgb;gy zLGwS9=armO?swH1f6knn`F+CU@15PU=IM#2f;^gcPMObGF)=1g;V7kmg&d05GCX5X z_?KU0+IIBIqxn1JUgZ5=DEkGuTBQ@8n&JO5jo&}W0~KG8l8Mc>H+iLr@`XyC4az{u zOVJ`+`(4AvH~nJb;K^Ojo;Z-Yxtvd*tvtmlCd#VBRYUoK(YEI1O;q=z5y?A@lN&MV z)M?-J#HPODM^qY>EpM2&07gM*)k1`i0*?WUfX9I+fZqX60*iqqz*1ltuns5&mIEsQ zAFvWw1(W~;JPoV{)&S1{Yk_A0VK$#bSZc?C&U!>P0M7#(ffoQjunE`3wZm0~N4b=ftYnw+vg_ZEb=vdg#C8$)7pykkb${C@C=BoKQrfeUav4$_E{<~+ z&665YqX9~R>t)%$_2blm%vc{_ZoT^6@IGplP(7h zeT!c<^l(;%(D$L_aYhoK9;H0hs3J>g&RmbSniMl|=<}U-)GWMk>CtU6P2a+zY2aw( z6}C&P=2Li1qOwvC>}Lh5{!8|=LdJf>e)cXYK195FeLw3fioyDoA^5uIfYyvR>!JLO zQHyDMFY47p-sO5}*E=EUGh>@)|LN%|;r`>%w0;&`DEF;$t-q&Yl?;%zHot8nEY_c2 zvk?~cN+Ak@bpKy%gvG>u%|`eEXzVX+gnRt zxrRNruB>gJhe!PTsyC-K&dVQ0y;J3$>~8Atn$nN=8>;M7{ne6GGD~;}_bJtKML&5D z3v8h4hFZz4Lt$mlEH0Xm^1C0}&z`Zo`3~MYQhA-RqM&o`dQ*<1C9S1Y{`?r_0o89; zBsPY6mns7&{Wd+4kDaVM>%ejbGc#Gf&(-Pkmy^1G`|8`B2YcRM+`VNOe|WO|I4kL* z6xzRro7z2`W_Q6J9Q3@>8SdltMfH3aB_oiXa6@)sS7jLW+plz^J*$L3FDsJ&NkuBzuV51(cIItG zt*hTcsBk|{&h}L*sU-)LG_4KFy4+ZP0KUL7MWS%0J)FX8l`NJ)Giou4!SpQxH67Jc zecwwhY3;|#NXMfX$gIy8|6#eJN+p#_q14u-nU%^Roe8=nHEdi;K+zs0T}wo+I0?Tt zEHnPP*Uzs!t}x*l&Yz)-q}2+8-hUYsfQT1&m3l;_d8Dg(Q=r zB30SMqQ;ahEN`)AVat|}*?&9`UVH`32mSa#K@l73KZsd{eXoY`u+7R@xgu4@PQ2ss zX1w`JI5zbcf@ViSlJRBC1aR*Yl)1+^V-w_Ew(zhFW8@87x9FKCmbU$b+; zrdpBgK5q=x^-I_hEBK~;ozrywdnM85o(SLaZ3P>E$+V}RJb=n_>~22gvb>7<4=Z*W zov!?^^7=|37`}~TqkX$$y70n_%98&Aw*Ei!Q_LV+oBg7n64=A?(hrs6tRhZLV3gog zkNa!HW~~-R?35hGlpEBB3O|>Zu)@HxhP#86!e^XN7BhbijJqz*22O(e6^1HK)uQAp z4EE{`oe6t&qdhvm|Ef*(9#Fjgvx*{&3=V2$R4P|kCe7HR3=Wv$TN_H5DRee?N(u}4 zs&P9{JFo0w!URCc3)Sr~?|-C+kHYod63|;UptpffHon-pq4nQKZv0}i{hhkbtl|^9 z#`HJv{yyMM3jNgHn!4R4CDOPLrAV6dsXaX9Q{6p}lq@NJx zNSw~db}bn_U_*IbfqCI+h_`UoT|yk5X0cPSF69DZs^Xc3cpfkv$Oj5!diz6r7ymK4 zRB}@JJ3XPjF~M~vIdIn9ykS>2tUQA?v5>WFSpIiwkP+*14s(O zJ}BOt(@K4UcBbM`ExN{DsJFu66%TZx4v}hCUR7gHzCk8q)jU)=SkM(P)=#Nw-*8&UxtzB7&P&CV3we~u>B1%0k z1-juEqE(IgijOELL*r>Rj$Mpi+^8AcIOq7T85M8RQYh=RrtmId+S@w0W;+Jax-=t^&x+BWXyi{+ z6&jjngwf%nS~!nS(ms-DLk8CBjQiEIt!PD!SnN}@T1F`u>Z?>-q8gOt09!%*X%}RpXn2M=};|;(T4X_Kjl@a zF1ozM9?j2gQ+KIBDwP~jCpRLgxsgbvLo|z$PvGU>mX%V_x&^Ikf*SP$blFKH{Cgv5k!&u*>K>8q+9%7-4-USc$; zzkUa~zj4IyaYwbaj5des$vpYER>i1Hvf5JC8B8^)T00{9hh%sJ|IM|K;gA++IBHIj-Z=&`)V7=4&Uk3O%*kZToWS~#I45r&;Me53cIGSi6Rr*rkS7C-U2 zeT7sqQr$phZ`h-FQMtWb@`dg0MpZ5&o`3R)9vxD#LSF>h_5{aDJKLjaYlZq2mHE_Y z@+8`;X>?}=q&T8S^Zl#z-Of$P+%8dyY^3qGpVz@^=@oTNP)M!YwE;2}4>tPn{V(YF zUS58{?&W2#*}aTv|LSm4RJvjCvQ7Fi$oDoinR}~L@ZdYEn5tV^4c-$7q}=9&Gdw8r70nr(V)Nq0=p`$s*A=j_mF?T4 z$h0iWh@%&|KAfs%Il}17gP3r)SM~ieRpmO;dHP>juB!yPiIKuhbH}uo2qMvqA zqT(L-68ch=de%QkQz$GKvQRl&cTtgKHsSBRslUSflQo0FrfCzYE(tmwvR|JC2OZK- zcL!5SVznspeho8`vQaw>wRM?xs=cJSc*HL4G^5JyY7|wCQevq{*6h5`K@GI3X>Q&} zt}-Kv7glLYV8woPB=Ce|+Hr<8<1TvY6JrRuFFPWGC(#!V=&)?zRZ0r)^pSQD^xUb9 zq}thb505!0q$;IQ%i>ulv{IdlZn4Kvom0!<#b-4f2fr1fr%{Y%ddagMqIUGWmZ-A5 z6uR2Do$?mK;9WR`#y9E;xmsztJlC$TV06xFl=E&?f_il~V=EV?7-w0@EXaFpHOQRN z8$z5FWt2h*x|--YUPRqa4T?6-0F%Y{ARaQ=I49GBcc2CD7AP83FE`rqWqC$ERK*U_ z&-+>p=Ls{7<2qFiF}skv2rF;xb~BofUu3LdCHEVNB~8pQ-t$RgFQejhIBm*vE3vQi zG?85Nu0Ebz9_bFgRWXO5N3O+iO!EX9ccbyDvoN!+X?Bg=I}D3=2{$t^t;_m=k~6Rw zBQ<@q>UZf7?VFT2v~?YeXF?yI<-)M2X5rQ4y3_nHtd>0JysVwrv#(tlX%Cd_#(zpq^P zqnp>YNFKRUKLsItNsHo9yYXR&qQ5YbdBy!Eco%y~ji7alaahu`QU|58W;)QrQT^O4 zjagv*baOUcmP{=3g(i&63e%oIWnVzJyTuv%Aqwjxcnt|g<*k%epjq4+ zZ=9FOo$DA+XZPv}d|9p#!Q{Duruha)l<}U9)!b{;s#LdGwP@5hdm;2XgGP-=q@Q!rpg-4T{2BeqrA&T zGA-L{UhtP0k{_kTHVa9|2~AXbebPwPN(pZMjy(GFyr{dDiaNqry3a? z(p<*i%ng(%E6iNOM@5i#5y|>7=0pq$t`t!x6?4 zD~+Qn)iwbYV&1O`YT-*D$LiMJ+X7pquWo9;2zxn$77bliE8c*{0cYL6ri0I?koPFe(1;vU1j$ z!>P}+=75Ne!QllyRHulq)|q%~26b5GXhKa!TJx#$Nqp*Q`@S4u3Kw2HcH0JOvBc_3 zp2g-cTE5dvByXwZttd5<8dK6X^N9IJ94?6S1sz;Q%_)@E(Wy|*PSc`yS6J<^y9?yL z)7jaJ+)J!%%G+%=p~bJ7omjaj8YeOuHyWEi`k|cc{LK20mhCp%vq0a$7S-%oc{$VQ zxg{2qvc4ION;^pQkcYBI=S|Bk$jl!-DQChAS}NXrF4*g-T}mYVm7C3IV}+Sbu0NQL zsO*{9xrGIp(`RHCAef3b3$ybBDSD5a?L_N2O(zv*Pc6tCJ8ddj4pc9kk)NMk7_6~6 zTM3~>ORXOC6gQ_Tx-G5YBp>ST452;SG4-*ut-G^D#BwZf!KZ!YD#XRPTiQRHyJgl> zB5Bst99o}ewWqx4R#W=qH8U(CaJL}PWMriqA$F{`w4Wwch;_RlT*0Jixk+OSlBxIG zrdz*jbY5m4n_8@}n)=@|q?;oHcg#iv8Wn$9SB}QtRzMnszG}WiAH8m78&0HWh`fgM zed;wcM(YoPp@a6co}116`*AO{^1P}E7deh6O6E~4Qe|QXVHfIGrpR!)go8H7Fn8$m z(mn|mI>Lm7u5DpL9TUwXvY8oVg7`}r`^}p|pA)0ap5EQTzFkvJryhoP-^PW3Z>Hw zN@%WNX%Ep2PN;XAK`Cy0qZ9|~Q0;4GL=&-X4n9g{J|fM4KuL? zx_`E|m{i5R&kDo zMJOLDVu{;=FErCUCv(q1v0hcaZFz#XZ~U`Od&yz5l6%S&4_56sE0bq>&1eT5dB`z_ zvIgq$!kSvmam)UyHKnW}R%y@*?pk7wqQ1kdWAyq`)3wQS#FRAKR9uw#Pg~l?FI!JQ zH{YbC&zgNCjCs1x^pLt7448oSr{Zc(0gxZKq?2zepyk#%y}kU^jl& zd>D33G(tbTZ0)0z0!Q>uTG*w~C1E!mwty*^(kwT-@S$n>Qzqq3$eb{GYIZ?({><$B zg6zUVIGC=t$)VPfU#vWHnrJXsC1A008qJuL zIbp^mv@$IxXUypQVA+eJuX?Ttj?w5PR=XxYImiYtO!@m7V{<0Keu^3t+2G~+=+x|~ zW3q!ClCY}5CE=gZV*YC;ZepbcM)(UIwhM9I2}1S_i~t%yL!MTm=r-O7|F{2yBqRYDKBXN1!$`L`)}^@!qNO4<2Ws|Bt9q^snzwWxeT#D(?0Ge8&5VR%E$&sd{L>T7y< zW$Mt;*-I)A^?Q{}RlM9=VJ%>EG=(Kl z4Q@73?GSAT9sN-qPQw=}ZE1eI98SYtvvTO`hxA+N))VIK#&y#`hyH*q<_MC)Sa(6> z7FWwaYnrmkii;S2)yo4#XVu4py;CA6Ss|&-5H|H6whJw*nIC zor;BHxcJ)Khc49`;nZ}$vkj&6HMdgCCTwM9t%7e+J5`G%&jDv;$$8mBbwb9y=VdP| zSqBAZu@kB=eUX$x2cxWLet4ag!YZoeN=BZ)p~O2;xtH!sv0~_PVRz@q-HCv_=x%pu|hN~6V}8nK92{Vu*&|3gaQqhZkk@E zbo{4g!ci{;^!qZUIi36p`ypX7N=mJJsP7>-$HMuC-C+MaeqBkPRyftx;?+)p-zNIU zSyD-ju!DmY4|%4@3V*c5yd;UkX%7qX=a*^a+jQx}N<39nxh3^AfDNO5)*81$v8Q2wNxVRuJbIB)WJd3B$ zi=g@tFG|_0^`XkuIB3W_V-}mRhR_1&l7|+|bxx$zi)JoAcEN0m zbM(IEsFDY9C@2oP=)3pLCxe$*jmxS#XpI*1HFJc$T^ZEVx6|&6% zO=+O1cqyoR4DsN(}yd;;ep1SLV~UuTrYjR z!rqZQhT_@efswm*U%bU3vn%$Ey_@Qt>2Q=4#Saf~rZV6CjU&nPjd{m)j+lW9iQ=Zw zzvx0@+h2Df5d(Z6@Ih`{X(GeTM1u^wRqlMG-m?mXwESDM9Y1fk_OZWXZfM0uXM(JM z4$oY0{Bz#nHtSufWQ_HDzNOM>XH@jGl|Xs#$S%5jsufPV3#=}DPL36ZBdLYpd(jxn zBv+*wN?Vr7PJVovm7vh5TuY;ju@C`QkrhTodz>0ior%tA)L1K=htIKAGx}~WOkSXk zGhtX|o2{{$(A+s_vbF%CluVY%=M`DW68+m;t3O@zS~ZkARSu)F=WyhYTKM^qE(>i4y0a1jDEw7?P71}1jFiyZh)KHkn2}l(nPi&Zi zrP;OwnjWRM8t4vG9BWIox$&ciV6{~Ijw}apVOtArJy9|?aH^GyoUwss#@$e(z}5zX zkf`@}=6c#X)SBrOzXBH9a1q`a9Nd#M`ZlXM&A!`;Zz{$#+BOdVgM!$`6(BwVvEYcD zS4Sk;d@R^z!+DEeLj`mfVg0cZ+V=&8#MlI_?gk3+FG$G(+Z`wiZaotkOqea8ZHCpCa2u~JRqfUy ro#IzuLOn!?LpmrEO^sOA9fHhv};xPW_)tQ1kx_l5fzJ delta 16442 zcmeHu4P2GQw)dXrYd>EbH;90UxJ5)lA|OOGL_{(&Lr^j^GW@{M5DC%LOwrSa85z2D zFDo-r+uAp2G2ZeRYIV%a$joaU4u?Ey~I<%p)t#PIOlY3t7mf?*)Smx&e7Vq29Kq6!lnfi}p3@ zK8|!MLK)>E5f%XD00(Xc7PtpV-CQwmPV01L%%=s_@R8Knt(}uzK!pk*k&RXCA)11ItvkU;93^ zwZDDat#AEzw%r;VwEadVxIJ>*Z)vz|+3sLfFbiedryuX5`5B)X$ zpZKO{j1U}tzr_id$qksfOoZKlEZ}uU31fr=VJ)MvV}uZC1ZXsCj1b|n`f)2#Z{dwU z13w~u#F}@6VF>BEP>pms@HS8Z9u@MvY-_8-%d{`U-M!3ik)~GBKF{c}vd;C}C)IDcR z+@|O~=em>YtW+TXgq1^-Ka$-<4H>4Le{fdn!#oAdwbpgA$nAPs;?W5L=Cf*(5Y3|t z*twomYch$VPYchucL+&z>a>ujK8wa~04_2ZRsK_`rls@kU8r%95zMcd%l^^L9rZ+v zl#i9ruGmI4Tw^0Z8+94|6zIO{D>qq@Tap!w1%wJMm@jnV&ehBru-0`#aJ$0Lz*mH5 zM)}uDJ=}?cMwPGP%R-o%OwD(wPM%fAwldEdRx1TtUs!jBMGxtQaXk*a2iyQW3{)cb zFuKqg_#EkFfHe<$5H1J)3VJQV1qgG1O^gD5U@^jt0;T@IB4eh2CZdkrC}Yjl6A0a) z{TuKZa1+u~8O`~D#jyx_?g!RG*lMF)s3M>lS$E)77m6K`-RBdddDQFdd^d897o&wC zA`QP*>S^_jSB@8}S^a+Ia&p%`*23IXkyw&nvE~lB&*0^$s3aSe6(X!e_zh?u0*?Yi zffc|i;6^||nVW#6D0d3@2QVGnMG&kb^MEsAyXrwzL-4*dN0L3qupjqKWq;E$2U4*BAaX=yP7PGm(bOuIR zWi;gLp!GnQ3&CX3VvsfjcSvCF{A)l8LRJ89J+K6y>W%Oqirj|qbA%3HIq)6OAG9A3 zo(296>_YkBpq)Yd3E)=5H-h#J&;jY^fD+KAA>AMN6VfTbaj>LautnG=A=6^v2avf9 z87{Jl zquT5B7(PEySYUX)g5b>%0>I3{0bB5`kAa_nt&F11u$exaVK&ki8U3o?%INv@-5J(J z_%)tJvwka2_myG-FXw3(05s^d=%$=&wJ2VnD-1O}2@-@^ZZlh!1Qsq7nmc$8NrJab zFg?}cz0N=VF?wu`7CAi5T=SItwCfETSIC0-Q`O>wjJwjs4NU5So?a9r<~hhQ6%_07 z&J$w2hXjENu9ZS~$Ob`IxNDKv!q^;Q!Db=VhAEN!zB=JSu|7*wm@~#ZcJQb~d+!yF zQ^7l?XMajx927$+ez3UDTQBJKrIuOEWkF8VnQ)@WRvOs{bv>-xA zr7ioE-%h(8k-h_`wO^U0AX@XDgJV6S)RRE%h%+SW&0m zQ{t(yuNFW>j|vfdbOu(UXRfe~2E8qgr^GrIN5xF1>g{QaO=2GLc()xc5Y4Fqn-JLO2vL;P=uk488cWoLtS0(FKX zOa|>f;4Z``1AjyQG=vX=mV@vclq*JP4W-4y7SfRZ9{EENCfRHQe5(5ocySFd02#%A zHCj3fECy{C(nwx>4uaN}@D3A$ys4t#EdrpM>P;z=i79v9McsP{U1+RBT0?u^SL1nn zq*=h6Uk&dYbo05pAHOwc{RdnzucxA;a&Ctn7hXS9KIi>6PkcFM{Wb5+ru;hAv0jyA zNUaKKqI2{25BFd7p3=*_>F;+%pQ%2>XIDrMGjFLF3OufH;Vl4=eOHhNAUmi8)oIdm){nPLj9X!l)$5wh|RqyaJkWm zZtMv*=r>3D6T6exebNQ*X2`SG{vf!xE7C0bJzVGABC3>ml@v`avOS1uUob=Xuso@l zb$I@fB?qS-7P=2zJNn$=h?CTiFJ*e)6HUs0MCr!6)k{pET93tR`@|-Z6;&*m7i`W6E{;Fegz#F%U&oj#GEg59w znSJah2E$arB4M8y{n#2}3~g-4Gc%dzOX~|$uxnpd0#oda*etUtv|ze4iZVS~059Gx zJ;l~Zinl}(-~&L}Vb~FRbxH7~3p*uvDKD)&VU9+p(}j7;caT2kZlE~>grRCEO1RpW zl5a&*RQK%A-kYLBnwUHPu@Gt*B}+WqDIH;+jVxQW4D#Q?R@t!Lo{YiI5l zb!Nk?gr?GwxfN@CGi-^&3&UtzzLdd6crzr^^Ofi%XNXkcq&GHdvNz-7mtVcFH2Tvq z9sE|cH$)o4=kSRY{z1Eo# zE4=Z*Kfl=XP*T~bZr?oQvXo6YzhkVpma*})U;#^^ya6nPa=*uW6Q)Q{+o_=jtF7oE zC4#EPYw_+A&G2C~2`f63hyG&?EB&!-=7-X%wIko7k5@~b=(fMWP^psy+V+IhnaXFg zbbiZB=|zzh(V%=W#}lQ5IeTn*XJX!CNA}0=*k80|yn2D(qezdl`ni$|9*G=`R)29F zlP+UJ$9v!rYvXJ^6KTN)X%Nqy-G{5JGyOtz8I&{aq#oeCnMqj+e zI=Nld183L6@*h?@3=CF0DWzb#MK9f$(VU6y0f)kBfU}8Pm zT5sSFaz`GtK{_sax1eiD2O!~Jk-oTsc+QJ9>nQUxqk)PB=vAIc;$NMsb_d0*?mFfl zHQSC&e&qPhPx-f##6aeKT@rq2{xnWBQz&Svl+ISuw%+Dr6mYXyVYi&XcXWzvEh#g4*}_M4QYwVS>3U}QFfvue<(e40@M_Q<2}5y->Q zsC4%aLJwNCR2uE*i6NZzrEekGDlK(5ni_gwuQKDH^eDSpz*JlEQA8*KGN1r zD02Z*>5qej&OGHSaktMq=w25V;m!~h^;L`#xc}13^GZpsW(>Zj2oajT39{tgQRe&Y zWXanv@BUM6*2?gyQ6GFWZ1bP@&=)&|P>N$x2%X3koR+MB5#c>9NuE>U?aqbYJfn<% zYhb75KaYK*UyoVu`<2W5Q{n=2Y$XK3iRH}2vrkD|*niGK^EfOY^iHXksa?^N#P+?R zQ}0Xh+_hh7VV-hJQ}2|#OuDRy(F?AOD7=J+Uw9Z++y8~!5O|3j{QOngI^oV=FhyFx z4)BrX)|du1`2~acd{x3ZosRrP?C&2L%fp7FLD@QvbhF-W^d@6L3_ zB4RP3L&mk$yY0)e^g3+}?9(zbSz`RE# z;R>G5u(r}a_k8-wTTZrctHhO^Z2Wv@SrC@iN?dZuCj==2>Vcn$l2zvOUInz(?e|`} z=S9Mu-Xg_Z!D_A{DMT)npN6zrO*2B|Uh)P+pQmRLl{O(tyF=tH>`QvKi`-H8yG*M? zXJ4B{T>Mv0M=RfDBw0=G|<+f+uOy*w#I302YC^L2k#Sp zAVyxdRS0{F<{g&DYVXC<;BUr#%o6E~5~U9hKOzsk!!uf5gB^hqE?&6Mjq-0%`cm#> zMdd@klq<8SV3iy}HMgreRX-p{Qq(CeoB|${6X{%%QbMP%Q#?E`PFXE+JyDs+*zJ_R zO72FPA1kSpeGROmeWT?ebT&_RQq>KLN^f4H zvfy`IEv;24r9^ZFwfQ#M!W}a0vdgKoH(yyrbsObqUNc+C6zO0un3oYwxl*&HBL-R` zg_6SMzP!;XCo<5Vm#|d70y-o80m3kWw*Mq0bodEn&Rg#QxYa?gHS0Yn zDq0@PvS>lHJWxeh+x0gODWE;ga+-bgM{vo4FYP_$*`A(qY}nsYU)GsBrP5 zKc;c3co`0VC_h&U=KFff@e$NG7f!otuLCEI4b#&2^2JIi^XAAUYz=4glna4W@rWEz z|AD;34*rt(D_kC-(8j@93MEDku#ie4@` zxpSX+Z-W;`vgGqjjI^4^a6L%|)s6?YlfokMm5GN%4Y}y@*kRgp=^Z8XSTS z^V)F_qP&Be!c*$(yG3dl!qAL*Uo&piW2ov0Ba=_sZ-0`JbEOhPi6^v3p51K6C^&!6 zCCad)+0iHMO`6xK>2Bwi03Hybo$Ta2XAh=~lWI5}bLdVU7o_fo&;J%RnI|V}dt~>r znaNcCXBI}zt~d(G3sdVvdgl(zuPauIq1~Z+5HF2Xmpe!(x8uMhO*_hH`wdzmPmEU^ z9hCK|lEt(7YdB{bvBaKBd4EzAUeI5KWgWzfG2|4Ca9)|NRg2giDP%5C0x0n#OpEIo zoU=?!S64Ag_Gl42B2znrXoEJ4ioVfed1Z!LjXD}73`n-Nn|U);i4q5DDp&K=GbkCM z74f1RtyUx91tpe>j;W^QlZfGCMyOs`6^9K}Is%n?3-IQ`_ml(tK!Fx3dq=B^w-D1L z&9hIWhI9-{L_w0;QJL@wIw2*j4)y$9d(nZj8}J~Q}cy1 zp0-ch*^^GjsTI@^qz6;OUL}aHoT!$7H=ok8>GV+9#V4NCRx$4+RiK62lrenFIn8UQ zV|Ep5NzspJ-V)>l+0_tw)1k)jbf^A~OluBgx5N(Fa7;bHJZ~sDRJ#f)V)O|Ww_0{!O{|VW`^UI+Oyu}Y;Fg-f zER7#Jp+<<5curR7@#EOWN1jy+X}cSH`LHPcD@F&0upnN2Rz+8`-j#!SaI}7mQR6p| zH>)3&VtGcaz8#FSTg&A;&#P&0fUU+NZTS$*OOUj^j7}t~F_c#e7HwRthw_^Ov^N+f zJ}NGxmG5KrmV|2+tUgP_S;rK+8c&(KwNaGt3Pe)ghe7~fIYcXA-Z9{c?Cu)G(s=Ex zNDDh)M2^Q`cGBYXUDr^72O?$3Bn=GRi<*nCM{&|UpW11>mp%bqwV`+Gu6idx3RfSjM zL=b!1UP>xYt=4xj+WWjw1VdgA=clG<4>4Nvf;x(iTB5&j9RrvFjM{PI`tgniF~?EshqSq}uNx7SkTqvM^yUSf^$ijn_3?y;9-`4KjJ%A}tSsyGWel z-C_vq=Pc8LX}imSc-?C35xqMNfs)rMLkUd|;h%f87=!X8Md$kK+G(GZ52l)4SV}|R z)apelU$3Bd+qJqtDv*t2sw0TcVUiBr(DI~_!*jy*&5RkMp;%Jn8tt;HEIQ$MLu%kzRnb*~u;*B{bPGx|1N zGRa)5mr+oif|=Q6G%y%Da|Noo3NphLt%Y-}i$ticU1~Zxf5Jr7Jf}PDg)p+KZP)V) z9~ij()6%F!`PXMd3hK_-Yi!5d%(JJ`M-`%rkNVt*veU^O`XaT>sYPnKkyf8>RH5bp zM&^ntp#x>sKwESgVc=kf28}QVQsxL_6xD{9aXzcZXXq>)Y$nj+GDG)_Gz#d8DMmb1 zjWjaN>Okz#Q3CdkL9gW+8)@qyqmY&lH=d&thYcrv)K8JcI2(j+KP8w)d}YLTpcQAJ zt+wVF-Pjl8`oYK&pqRS%e8uA_hNWR_Ynr&3W|HnF&Zu9<8sqnaSo z9(vcfsm0Tar%#_et*o$J-2d$j{sv8-P*Gf5HocG*=a{MFEHeUFyB>*}E&De$sYAfU zCRQZOnp#SYQI0|MWw{X)*w>n3-xJ?p-NUlcl6Q^rUu~Q-;082(q`&D26Q=j41=9@I zzkKEKM6+|qUH)(2H>hD2ey!QbzS5{`_8d$AYVA!QM>#rE%}~=$E%z9bWYNi2S&e03 z`<|W+)}1Sh!L$GM?CP3trhOe>+Q*UU?|4>g74ShdSR2OzDtpJchw{n|gZ51}QtACl zqgcNYIgwWN?I*QmyD>84g5@>#J>d;5tY6Comi_X@sc%2wy;y&G_ORU9(@D#w@|TQ6 zQkikaD?w(poDgbwjcSxL+VDpsfC7Sz(y%@$seM!KcMdC_SzJ28$2P?k7?JCIkQie z1T}O)+&9v{To;Bm(zi%Z<5d@@O_lu%$7*4>-fi_@{u>duDzea8KMmTYTu0y5u>cw` z8mlj9CwFGQ2ul788XRu#cs7Aw_{dnmT2DPZ!Da&meP*Q5tY~wQo}9csR1n7ew%eNMp2v8fW5c)%mG0gpW=(qEuS*vWZREPV;lzVM~QAUVaLe z?QNsXNGfW=Xr1YARPirP8UpjC8$!U>?WBIIXunLx|4FXjWx^S8@EAqQydv2E3WPv_3P zZ&2#szA5!NjueUZ|A-;1snTL~>$--;n8gB}s5BFpmy)W@&hv+%0W*P8TLP}D7Grl` z02MV2H$qDBI~|n4zT8s8V{OBryr!c}I$TiG(D-Ss3azb7hRuc2Q}I`hO55yqZ^nZ@ z$yehzS=LvzOX&q))Wgn^u}1uNzDp=M8a z%aCBoZZYF6DMbsoIKaA+hBI->yvc0vxEwFi)mJ&fsVLdeg~HMt!Td#+!(`rxrr<5W zq`&5JL{a$%qLY8!-BBpg@dOk;F<1zofqflOyr!okR`QO40J~rnL@hy7)!m`eQ8%vp z>2Z!%n77c7=(Tc~*}Ho?o?sV0XF{?#da%K?w5OvtMfY(8Q)rr_6ZMZpMKwJf3SHCP zVe)RN9y5*y7Kqi**=uq0$JX=id??ye7%<7gk?nB-lIp zv?^w~|MmR~N6r{OeMZHll{=4?mcY?oHWJ&+#wo^_f2p{9M!=P%sHKQ=+RLDOX26ED z^gZmdns<$|U#+sk&(tTq*4@!@{nTq6LeCi$lg3RbE*v>+Mg@X)TDE=MFPX2XP}_6; z?^RM)2mV6g5VU3ZB@IjgI}B58Q%Jv4^924}^MH--F}kUKRRjy?e2y7?+m-Z9J2hb| z99%N4V%&sEpFRX<`wwfa2%a$C#jyu2>gC=IPa+O+Wy@Y~>E$p!wU6TkMlbaOQ~jZ@ zqlji%?B(ORL~uWC?(c}?^V1x-`4pe#7)cFSA^b#t$IFnh!;S90YTgpe+fJSlW~O1a z7ZTWd+^;qDp{%#zEYsA~QdrvUuwB!txnY%Y8qV5!%%}(o%-Yv&49j?eH5fT_+nx@c3~P?!K#H+KjS^e)G2w1PBJf(+Fiy zm2EC$T-a=$H*ibCcf4mE^%IWzqiIEkc{f!xLa|<2a$BhZ1i!M}8!aTz@o$A75H^mU1*$ak;dqmTA3jYM#-BDbe!X{Z)AaeQe-sX4MJ`d1K34%X@7EP7X>Jxl* zTgC`xCDDPyk0l$UWS_H*OzvbI=P<93y`qjOk0y6H2$V%*Qjn_-M3tc-z@Y|C!9PDOTORw7iR=_pjwfxVwiCtY)ua+5HpyCt4zy0!xg^p%)paoxPYc zD6MrvFzzR#Yk&#s!p~eNlW5i5slD6_Yp2d{1}k1H?HiB|ZG&KiP#Hb}8R+|CaeJjl zOS_oYw3n9lR`o*naIqgX6q|6ek2LbAWq|H;%~U~3XL@&;!j)wxt(k{wM2#b{4}GA~ zNWV;+T5`I*I6ZBqGp_G8Pha7dLfg(Or%g<5A8Dk^mgV{j5$W5SSr?K2L&^9VzVDNa zQBNshZAUru$`NCPPdHjzEx%l(MJ;9;l`pX;Qs#EZ&zWzUyDpcGm&sO-Pdd7T;!yp) z<`BAdB#WZVN6jI;Y>(-Nf|nuAee!Z|GV{yd^$?iiW#YG0NYX}2#-5@DL!=wIUSrm` zm&7}p&CUK>dYA5o?M+3`FUj2BbmItj^cqbZ>|2|}p6xe@5Wj)HPt@KO%ws<=59sw- zjuid>)xC~1e68?oeGP8uQgWpkOv{3Vc-dMA_`6Sk$1McwKb*=_93A;1E=Me*VM&f` zf-&c)-*1kQC=0$~$749*4aO^0m`F<>!^S({ddE3x_{1DU2S0(+AT-y!icURf2FvA8 h_P%|3FW(kw$1Uc7z;>pc&62juDF1%>8t-tE{V)59TxI|O