Unstable 1.2.4.0

This commit is contained in:
Markus Isberg
2023-11-30 13:53:00 +02:00
parent 8a2e2ea0ae
commit fb5ea537bf
210 changed files with 4201 additions and 1283 deletions
@@ -86,7 +86,7 @@ namespace Barotrauma
MathUtils.RoundTowardsClosest(center.Y, Submarine.GridSize.Y) - center.Y - Submarine.GridSize.Y / 2);
MapEntity.SelectedList.Clear();
assemblyEntities.ForEach(e => MapEntity.AddSelection(e));
entities.ForEach(e => MapEntity.AddSelection(e));
foreach (MapEntity mapEntity in assemblyEntities)
{
@@ -1,10 +1,9 @@
using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Linq;
using System.Collections.Generic;
using FarseerPhysics;
namespace Barotrauma
{
@@ -162,6 +162,8 @@ namespace Barotrauma
CanBeVisible =
Sprite != null ||
Prefab.DeformableSprite != null ||
ParticleEmitters is { Length: > 0 } ||
(GameMain.DebugDraw && Triggers is { Count: > 0 }) ||
Prefab.OverrideProperties.Any(p => p != null && (p.Sprites.Any() || p.DeformableSprite != null));
}
@@ -23,7 +23,14 @@ namespace Barotrauma
private Rectangle currentGridIndices;
public bool ForceRefreshVisibleObjects;
partial void RemoveProjSpecific()
{
visibleObjectsBack.Clear();
visibleObjectsMid.Clear();
visibleObjectsFront.Clear();
}
partial void UpdateProjSpecific(float deltaTime)
{
foreach (LevelObject obj in visibleObjectsBack)
@@ -133,6 +133,8 @@ namespace Barotrauma.Lights
public Rectangle BoundingBox { get; private set; }
public bool IsInvalid { get; private set; }
public ConvexHull(Rectangle rect, bool isHorizontal, MapEntity parent)
{
shadowEffect ??= new BasicEffect(GameMain.Instance.GraphicsDevice)
@@ -481,15 +483,34 @@ namespace Barotrauma.Lights
for (int i = 0; i < 4; i++)
{
vertices[i].WorldPos = vertices[i].Pos;
ValidateVertex(vertices[i].WorldPos, "vertices[i].Pos");
segments[i].Start.WorldPos = segments[i].Start.Pos;
ValidateVertex(segments[i].Start.WorldPos, "segments[i].Start.Pos");
segments[i].End.WorldPos = segments[i].End.Pos;
ValidateVertex(segments[i].End.WorldPos, "segments[i].End.Pos");
}
if (ParentEntity == null || ParentEntity.Submarine == null) { return; }
for (int i = 0; i < 4; i++)
{
vertices[i].WorldPos += ParentEntity.Submarine.DrawPosition;
ValidateVertex(vertices[i].WorldPos, "vertices[i].WorldPos");
segments[i].Start.WorldPos += ParentEntity.Submarine.DrawPosition;
ValidateVertex(segments[i].Start.WorldPos, "segments[i].Start.WorldPos");
segments[i].End.WorldPos += ParentEntity.Submarine.DrawPosition;
ValidateVertex(segments[i].End.WorldPos, "segments[i].End.WorldPos");
}
void ValidateVertex(Vector2 vertex, string debugName)
{
if (!MathUtils.IsValid(vertex))
{
IsInvalid = true;
string errorMsg = $"Invalid vertex on convex hull ({debugName}: {vertex}, parent entity: {ParentEntity?.ToString() ?? "null"}).";
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#endif
GameAnalyticsManager.AddErrorEventOnce("ConvexHull.RefreshWorldPositions:InvalidVertex", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
}
}
}
@@ -234,6 +234,15 @@ namespace Barotrauma.Lights
}
}
public void DebugDrawVertices(SpriteBatch spriteBatch)
{
foreach (LightSource light in lights)
{
if (!light.Enabled) { continue; }
light.DebugDrawVertices(spriteBatch);
}
}
public void RenderLightMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, RenderTarget2D backgroundObstructor = null)
{
if (!LightingEnabled) { return; }
@@ -259,7 +268,8 @@ namespace Barotrauma.Lights
{
if (!light.Enabled) { continue; }
if ((light.Color.A < 1 || light.Range < 1.0f) && !light.LightSourceParams.OverrideLightSpriteAlpha.HasValue) { continue; }
//above the top boundary of the level (in an inactive respawn shuttle?)
if (Level.Loaded != null && light.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (light.ParentBody != null)
{
light.ParentBody.UpdateDrawPosition();
@@ -801,6 +811,8 @@ namespace Barotrauma.Lights
public void ClearLights()
{
activeLights.Clear();
activeLightsWithLightVolume.Clear();
lights.Clear();
}
}
@@ -37,9 +37,9 @@ namespace Barotrauma.Lights
TextureRange = range;
if (OverrideLightTexture != null)
{
TextureRange += Math.Max(
Math.Abs(OverrideLightTexture.RelativeOrigin.X - 0.5f) * OverrideLightTexture.size.X,
Math.Abs(OverrideLightTexture.RelativeOrigin.Y - 0.5f) * OverrideLightTexture.size.Y);
TextureRange *= 1.0f + Math.Max(
Math.Abs(OverrideLightTexture.RelativeOrigin.X - 0.5f),
Math.Abs(OverrideLightTexture.RelativeOrigin.Y - 0.5f));
}
}
}
@@ -238,7 +238,11 @@ namespace Barotrauma.Lights
private bool needsRecalculationWhenUpToDate;
public bool NeedsRecalculation
{
get { return needsRecalculation; }
get
{
if (ParentBody?.UserData is Item it && it.Prefab.Identifier == "flashlight") { return true; }
return needsRecalculation;
}
set
{
if (!needsRecalculation && value)
@@ -708,6 +712,7 @@ namespace Barotrauma.Lights
{
foreach (ConvexHull hull in chList.List)
{
if (hull.IsInvalid) { continue; }
if (!chList.IsHidden.Contains(hull))
{
//find convexhull segments that are close enough and facing towards the light source
@@ -735,6 +740,7 @@ namespace Barotrauma.Lights
GameMain.LightManager.AddRayCastTask(this, drawPos, rotation);
}
const float MinPointDistance = 6;
public void RayCastTask(Vector2 drawPos, float rotation)
{
@@ -877,12 +883,11 @@ namespace Barotrauma.Lights
}
}
const float MinPointDistance = 6;
//remove points that are very close to each other
for (int i = 0; i < points.Count; i++)
//+= 2 because the points are added in pairs above, i.e. 0 and 1 belong to the same segment
for (int i = 0; i < points.Count; i += 2)
{
for (int j = Math.Min(i + 4, points.Count - 1); j > i; j--)
for (int j = Math.Min(i + 2, points.Count - 1); j > i; j--)
{
if (Math.Abs(points[i].WorldPos.X - points[j].WorldPos.X) < MinPointDistance &&
Math.Abs(points[i].WorldPos.Y - points[j].WorldPos.Y) < MinPointDistance)
@@ -892,14 +897,14 @@ namespace Barotrauma.Lights
}
}
var compareCCW = new CompareSegmentPointCW(drawPos);
try
{
points.Sort(compareCCW);
var compareCW = new CompareSegmentPointCW(drawPos);
points.Sort(compareCW);
}
catch (Exception e)
{
StringBuilder sb = new StringBuilder("Constructing light volumes failed! Light pos: " + drawPos + ", Hull verts:\n");
StringBuilder sb = new StringBuilder($"Constructing light volumes failed ({nameof(CompareSegmentPointCW)})! Light pos: {drawPos}, Hull verts:\n");
foreach (SegmentPoint sp in points)
{
sb.AppendLine(sp.Pos.ToString());
@@ -914,7 +919,11 @@ namespace Barotrauma.Lights
verts.Clear();
foreach (SegmentPoint p in points)
{
Vector2 dir = Vector2.Normalize(p.WorldPos - drawPos);
Vector2 diff = p.WorldPos - drawPos;
float dist = diff.Length();
//light source exactly at the segment point, don't cast a shadow (normalizing the vector would lead to NaN)
if (dist <= 0.0001f) { continue; }
Vector2 dir = diff / dist;
Vector2 dirNormal = new Vector2(-dir.Y, dir.X) * MinPointDistance;
//do two slightly offset raycasts to hit the segment itself and whatever's behind it
@@ -940,9 +949,36 @@ namespace Barotrauma.Lights
{
//the raycasts landed on different segments
//we definitely want to generate new geometry here
verts.Add(isPoint1 ? p.WorldPos : intersection1.pos);
verts.Add(isPoint2 ? p.WorldPos : intersection2.pos);
markAsVisible = true;
if (isPoint1)
{
TryAddPoints(intersection2.pos, p.WorldPos, drawPos, verts);
markAsVisible = true;
}
else if (isPoint2)
{
TryAddPoints(intersection1.pos, p.WorldPos, drawPos, verts);
markAsVisible = true;
}
else
{
//didn't hit either point, completely obstructed
verts.Add(intersection1.pos);
verts.Add(intersection2.pos);
}
static void TryAddPoints(Vector2 intersection, Vector2 point, Vector2 refPos, List<Vector2> verts)
{
//* 0.8f because we don't care about obstacles that are very close (intersecting walls),
//only about obstacles that are clearly between the point and the refPos
bool intersectionCloserThanPoint = Vector2.DistanceSquared(intersection, refPos) < Vector2.DistanceSquared(point, refPos) * 0.8f;
//if the raycast hit a segment that's closer than the point we're aiming towards,
//it means we didn't hit a segment behind the point, but something that's obstructing it
//= we don't want to add vertex at that obstructed point, it could make the light go through obstacles
if (!intersectionCloserThanPoint)
{
verts.Add(point);
}
verts.Add(intersection);
}
}
if (markAsVisible)
{
@@ -959,15 +995,32 @@ namespace Barotrauma.Lights
//remove points that are very close to each other
for (int i = 0; i < verts.Count - 1; i++)
{
for (int j = Math.Min(i + 4, verts.Count - 1); j > i; j--)
for (int j = verts.Count - 1; j > i; j--)
{
if (Math.Abs(verts[i].X - verts[j].X) < 6 &&
Math.Abs(verts[i].Y - verts[j].Y) < 6)
if (Math.Abs(verts[i].X - verts[j].X) < MinPointDistance &&
Math.Abs(verts[i].Y - verts[j].Y) < MinPointDistance)
{
verts.RemoveAt(j);
}
}
}
try
{
var compareCW = new CompareCW(drawPos);
verts.Sort(compareCW);
}
catch (Exception e)
{
StringBuilder sb = new StringBuilder($"Constructing light volumes failed ({nameof(CompareSegmentPointCW)})! Light pos: {drawPos}, verts:\n");
foreach (Vector2 v in verts)
{
sb.AppendLine(v.ToString());
}
DebugConsole.ThrowError(sb.ToString(), e);
}
calculatedDrawPos = drawPos;
state = LightVertexState.PendingVertexRecalculation;
}
@@ -1114,7 +1167,7 @@ namespace Barotrauma.Lights
//add the normals together and use some magic numbers to create
//a somewhat useful/good-looking blur
float blurDistance = 40.0f;
float blurDistance = 25.0f;
Vector2 nDiff = nDiff1 * blurDistance;
if (MathUtils.GetLineIntersection(vertex + (nDiff1 * blurDistance), nextVertex + (nDiff1 * blurDistance), vertex + (nDiff2 * blurDistance), prevVertex + (nDiff2 * blurDistance), true, out Vector2 intersection))
{
@@ -1230,7 +1283,8 @@ namespace Barotrauma.Lights
/// <param name="spriteBatch"></param>
public void DrawSprite(SpriteBatch spriteBatch, Camera cam)
{
if (GameMain.DebugDraw)
//uncomment if you want to visualize the bounds of the light volume
/*if (GameMain.DebugDraw)
{
Vector2 drawPos = position;
if (ParentSub != null)
@@ -1269,7 +1323,7 @@ namespace Barotrauma.Lights
{
GUI.DrawLine(spriteBatch, boundaryCorners[i].Pos, boundaryCorners[(i + 1) % 4].Pos, Color.White, 0, 3);
}
}
}*/
if (DeformableLightSprite != null)
{
@@ -1367,6 +1421,38 @@ namespace Barotrauma.Lights
}
}
public void DebugDrawVertices(SpriteBatch spriteBatch)
{
if (Range < 1.0f || Color.A < 1 || CurrentBrightness <= 0.0f) { return; }
//commented out because this is mostly just useful in very specific situations, otherwise it just makes debugdraw very messy
//(you may also need to add a condition here that only draws this for the specific light you're interested in)
if (GameMain.DebugDraw && vertices != null)
{
if (ParentBody?.UserData is Item it && it.Prefab.Identifier == "flashlight")
for (int i = 1; i < vertices.Length - 1; i += 2)
{
Vector2 vert1 = new Vector2(vertices[i].Position.X, vertices[i].Position.Y);
int nextIndex = (i + 2) % vertices.Length;
//the first vertex is the one at the position of the light source, skip that one
//(we just want to draw lines between the vertices at the circumference of the light volume)
if (nextIndex == 0) { nextIndex++; }
Vector2 vert2 = new Vector2(vertices[nextIndex].Position.X, vertices[nextIndex].Position.Y);
if (ParentSub != null)
{
vert1 += ParentSub.DrawPosition;
vert2 += ParentSub.DrawPosition;
}
vert1.Y = -vert1.Y;
vert2.Y = -vert2.Y;
var randomColor = ToolBox.GradientLerp(i / (float)vertices.Length, Color.Magenta, Color.Blue, Color.Yellow, Color.Green, Color.Cyan, Color.Red, Color.Purple, Color.Yellow);
GUI.DrawLine(spriteBatch, vert1, vert2, randomColor * 0.8f, width: 2);
}
}
}
public void DrawLightVolume(SpriteBatch spriteBatch, BasicEffect lightEffect, Matrix transform, bool allowRecalculation, ref int recalculationCount)
{
if (Range < 1.0f || Color.A < 1 || CurrentBrightness <= 0.0f) { return; }
@@ -291,14 +291,14 @@ namespace Barotrauma
private readonly List<MapNotification> mapNotifications = new List<MapNotification>();
partial void ChangeLocationTypeProjSpecific(Location location, string prevName, LocationTypeChange change)
partial void ChangeLocationTypeProjSpecific(Location location, LocalizedString prevName, LocationTypeChange change)
{
var messages = change.GetMessages(location.Faction);
if (!messages.Any()) { return; }
string msg = messages.GetRandom(Rand.RandSync.Unsynced)
.Replace("[previousname]", $"‖color:gui.yellow‖{prevName}‖end‖")
.Replace("[name]", $"‖color:gui.yellow‖{location.Name}‖end‖");
.Replace("[name]", $"‖color:gui.yellow‖{location.DisplayName}‖end‖");
location.LastTypeChangeMessage = msg;
mapNotifications.Add(new MapNotification(msg, GUIStyle.SubHeadingFont, mapNotifications, location));
@@ -377,7 +377,7 @@ namespace Barotrauma
bool showReputation = hudVisibility > 0.0f && location.Type.HasOutpost && location.Reputation != null;
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Name, font: GUIStyle.LargeFont) { Padding = Vector4.Zero };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.DisplayName, font: GUIStyle.LargeFont) { Padding = Vector4.Zero };
if (!location.Type.Name.IsNullOrEmpty())
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
@@ -1080,6 +1080,7 @@ namespace Barotrauma
}
float dist = Vector2.Distance(start, end);
var connectionSprite = connection.Passed ? generationParams.PassedConnectionSprite : generationParams.ConnectionSprite;
if (connectionSprite?.Texture == null) { continue; }
Color segmentColor = connectionColor;
int segmentWidth = width;
@@ -1092,9 +1093,6 @@ namespace Barotrauma
segmentWidth /= 2;
segmentColor = connection.Passed ? generationParams.ConnectionColor : generationParams.UnvisitedConnectionColor;
}
else
{
}
}
spriteBatch.Draw(connectionSprite.Texture,
@@ -162,21 +162,9 @@ namespace Barotrauma
{
if (SelectedAny)
{
if (SelectedList.Any(static t => t is Item it && it.GetComponent<CircuitBox>() is not null))
{
GUI.AskForConfirmation(SubEditorScreen.CircuitBoxDeletionWarningHeader, SubEditorScreen.CircuitBoxDeletionWarningBody, onConfirm: Delete);
}
else
{
Delete();
}
void Delete()
{
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity>(SelectedList), true));
SelectedList.ForEach(static e => { if (!e.Removed) { e.Remove(); } });
SelectedList.Clear();
}
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity>(SelectedList), true));
SelectedList.ForEachMod(static e => { if (!e.Removed) { e.Remove(); } });
SelectedList.Clear();
}
}
@@ -1332,12 +1320,15 @@ namespace Barotrauma
HashSet<MapEntity> foundEntities = new HashSet<MapEntity>();
Rectangle selectionRect = Submarine.AbsRect(pos, size);
Quad2D selectionQuad = Quad2D.FromSubmarineRectangle(selectionRect);
foreach (MapEntity entity in MapEntityList)
{
if (!entity.SelectableInEditor) { continue; }
if (Submarine.RectsOverlap(selectionRect, entity.rect))
Quad2D entityQuad = entity.GetTransformedQuad();
if (selectionQuad.Intersects(entityQuad))
{
foundEntities.Add(entity);
entity.IsIncludedInSelection = true;
@@ -236,11 +236,14 @@ namespace Barotrauma
public override bool IsVisible(Rectangle worldView)
{
Rectangle worldRect = WorldRect;
RectangleF worldRect = Quad2D.FromSubmarineRectangle(WorldRect).Rotated(
FlippedX != FlippedY
? rotationRad
: -rotationRad).BoundingAxisAlignedRectangle;
Vector2 worldPos = WorldPosition;
Vector2 min = new Vector2(worldRect.X, worldRect.Y - worldRect.Height);
Vector2 max = new Vector2(worldRect.Right, worldRect.Y);
Vector2 min = new Vector2(worldRect.X, worldRect.Y);
Vector2 max = new Vector2(worldRect.Right, worldRect.Y + worldRect.Height);
foreach (DecorativeSprite decorativeSprite in Prefab.DecorativeSprites)
{
float scale = decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale;
@@ -312,7 +315,12 @@ namespace Barotrauma
Vector2 bodyPos = WorldPosition + BodyOffset * Scale;
GUI.DrawRectangle(spriteBatch, new Vector2(bodyPos.X, -bodyPos.Y), rectSize.X, rectSize.Y, BodyRotation, Color.White,
GUI.DrawRectangle(sb: spriteBatch,
center: new Vector2(bodyPos.X, -bodyPos.Y),
width: rectSize.X,
height: rectSize.Y,
rotation: BodyRotation,
clr: Color.White,
thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom)));
}
@@ -96,9 +96,6 @@ namespace Barotrauma
public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, float rotation = 0.0f, SpriteEffects spriteEffects = SpriteEffects.None)
{
SpriteEffects oldEffects = Sprite.effects;
Sprite.effects ^= spriteEffects;
var position = placeRect.Location.ToVector2().FlipY();
position += placeRect.Size.ToVector2() * 0.5f;
@@ -109,9 +106,8 @@ namespace Barotrauma
color: Color.White * 0.8f,
origin: placeRect.Size.ToVector2() * 0.5f,
rotation: rotation,
textureScale: TextureScale * scale);
Sprite.effects = oldEffects;
textureScale: TextureScale * scale,
spriteEffects: spriteEffects ^ Sprite.effects);
}
}
}
@@ -421,7 +421,7 @@ namespace Barotrauma
float scale = element.GetAttributeFloat("scale", 1f);
Color color = element.GetAttributeColor("spritecolor", Color.White);
float rotation = element.GetAttributeFloat("rotation", 0f);
float rotationRad = MathHelper.ToRadians(element.GetAttributeFloat("rotation", 0f));
MapEntityPrefab prefab;
if (element.NameAsIdentifier() == "item"
@@ -455,7 +455,7 @@ namespace Barotrauma
ItemPrefab itemPrefab = prefab as ItemPrefab;
if (itemPrefab != null)
{
BakeItemComponents(itemPrefab, rect, color, scale, rotation, depth, out overrideSprite);
BakeItemComponents(itemPrefab, rect, color, scale, rotationRad, depth, out overrideSprite);
}
if (!overrideSprite)
@@ -485,13 +485,15 @@ namespace Barotrauma
MathUtils.PositiveModulo((int)-textureOffset.Y, prefab.Sprite.SourceRect.Height));
prefab.Sprite.DrawTiled(
spriteRecorder,
rect.Location.ToVector2() * new Vector2(1f, -1f),
rect.Size.ToVector2(),
spriteBatch: spriteRecorder,
position: new Vector2(rect.X + rect.Width / 2, -(rect.Y - rect.Height / 2)),
targetSize: rect.Size.ToVector2(),
origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f),
color: color,
startOffset: backGroundOffset,
textureScale: textureScale * scale,
depth: depth);
depth: depth,
rotation: rotationRad);
}
else if (itemPrefab != null)
{
@@ -552,7 +554,7 @@ namespace Barotrauma
spritePos * new Vector2(1f, -1f),
color,
prefab.Sprite.Origin,
rotation,
rotationRad,
scale,
prefab.Sprite.effects, depth);
@@ -564,7 +566,7 @@ namespace Barotrauma
if (flippedX) { offset.X = -offset.X; }
if (flippedY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.Draw(spriteRecorder, new Vector2(spritePos.X + offset.X, -(spritePos.Y + offset.Y)), color,
MathHelper.ToRadians(rotation) + rot, decorativeSprite.GetScale(0f) * scale, prefab.Sprite.effects,
rotationRad + rot, decorativeSprite.GetScale(0f) * scale, prefab.Sprite.effects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.Sprite.Depth), 0.999f));
}
}
@@ -577,7 +579,7 @@ namespace Barotrauma
private void BakeItemComponents(
ItemPrefab prefab,
Rectangle rect, Color color,
float scale, float rotation, float depth,
float scale, float rotationRad, float depth,
out bool overrideSprite)
{
overrideSprite = false;
@@ -607,7 +609,7 @@ namespace Barotrauma
Vector2 relativeBarrelPos = barrelPos * prefab.Scale - new Vector2(rect.Width / 2, rect.Height / 2);
var transformedBarrelPos = MathUtils.RotatePoint(
relativeBarrelPos,
MathHelper.ToRadians(rotation));
rotationRad);
Vector2 drawPos = new Vector2(rect.X + rect.Width * relativeScale / 2 + transformedBarrelPos.X * relativeScale, rect.Y - rect.Height * relativeScale / 2 - transformedBarrelPos.Y * relativeScale);
drawPos.Y = -drawPos.Y;
@@ -615,13 +617,13 @@ namespace Barotrauma
railSprite?.Draw(spriteRecorder,
drawPos,
color,
rotation + MathHelper.PiOver2, scale,
rotationRad, scale,
SpriteEffects.None, depth + (railSprite.Depth - prefab.Sprite.Depth));
barrelSprite?.Draw(spriteRecorder,
drawPos,
color,
rotation + MathHelper.PiOver2, scale,
rotationRad, scale,
SpriteEffects.None, depth + (barrelSprite.Depth - prefab.Sprite.Depth));
break;
@@ -781,7 +783,7 @@ namespace Barotrauma
previewFrame = null;
}
spriteRecorder?.Dispose(); spriteRecorder = null;
camera?.Dispose(); camera = null;
camera = null;
isDisposed = true;
}
}