private void UpdatePreview(bool force = false) { if (force) { SetPreviews(PreviewType.Default, null); } // Figure out what to show. try // evil: using try-catch for flow-control, but who can stop me!? muahahaha { // Nothing selected, so skip. if (pgProperties.SelectedObject == null) { SetPreviews(PreviewType.Default, null); return; } var item = pgProperties.SelectedGridItem; while (item != null) { // If an image asset is selected we simply show that. if (item.PropertyDescriptor != null) { if (item.PropertyDescriptor.Attributes[typeof(EditorAttribute)] != null) { var editorType = Type.GetType(((EditorAttribute)item.PropertyDescriptor.Attributes[typeof(EditorAttribute)]).EditorTypeName); if (editorType == typeof(TextureAssetEditor)) { RenderTextureAssetPreview(item.Value as string); return; } if (editorType == typeof(EffectAssetEditor)) { RenderEffectAssetPreview(item.Value as string); return; } if (editorType == typeof(PlanetEditor)) { RenderPlanetPreview(FactoryManager.GetFactory(item.Value as string) as PlanetFactory); return; } if (editorType == typeof(SunEditor)) { RenderSunPreview(FactoryManager.GetFactory(item.Value as string) as SunFactory); return; } if (editorType == typeof(ItemInfoEditor)) { RenderItemPreview(FactoryManager.GetFactory(item.Value as string) as ItemFactory); return; } } // Render next-best orbit system if possible. if (item.Parent != null && item.Parent.Parent != null && item.Parent.Parent.PropertyDescriptor != null && item.Parent.Parent.PropertyDescriptor.PropertyType == typeof(Orbiter)) { RenderOrbiterPreview(item.Parent.Parent.Value as Orbiter); return; } // Render next-best item system if possible. if (item.PropertyDescriptor.PropertyType == typeof(ShipFactory.ItemInfo)) { var info = item.Value as ShipFactory.ItemInfo; if (info != null) { RenderItemPreview(FactoryManager.GetFactory(info.Name) as ItemFactory); return; } } if (item.PropertyDescriptor.PropertyType == typeof(ItemPool.DropInfo)) { var info = item.Value as ItemPool.DropInfo; if (info != null) { RenderItemPreview(FactoryManager.GetFactory(info.ItemName) as ItemFactory); return; } } // Render next-best projectile if possible. if (item.PropertyDescriptor.PropertyType == typeof(ProjectileFactory[])) { RenderProjectilePreview(item.Value as ProjectileFactory[]); return; } } item = item.Parent; } // We're not rendering based on property grid item selection at this point, so // we just try to render the selected object. if (pgProperties.SelectedObject is PlanetFactory) { RenderPlanetPreview(pgProperties.SelectedObject as PlanetFactory); return; } if (pgProperties.SelectedObject is SunFactory) { RenderSunPreview(pgProperties.SelectedObject as SunFactory); return; } if (pgProperties.SelectedObject is SunSystemFactory) { RenderSunSystemPreview(pgProperties.SelectedObject as SunSystemFactory); return; } if (pgProperties.SelectedObject is ItemFactory) { RenderItemPreview(pgProperties.SelectedObject as ItemFactory); return; } if (pgProperties.SelectedObject is ShipFactory) { RenderShipPreview(pgProperties.SelectedObject as ShipFactory); return; } // Could not render anything, clear previews. SetPreviews(PreviewType.Default, null); } finally { // Update the picture box. pbPreview.Invalidate(); } }
private bool HandleObjectNameChanged(object oldValue, object newValue, GridItem changedItem) { // See if what we changed is the name of the selected object. if (ReferenceEquals(changedItem.PropertyDescriptor, TypeDescriptor.GetProperties(pgProperties.SelectedObject)["Name"])) { // Yes, get old and new value. var oldName = oldValue as string; var newName = newValue as string; // Adjust manager layout, this will throw as necessary. tvData.BeginUpdate(); try { if (pgProperties.SelectedObject is IFactory) { FactoryManager.Rename(oldName, newName); } else if (pgProperties.SelectedObject is ItemPool) { ItemPoolManager.Rename(oldName, newName); } else if (pgProperties.SelectedObject is AttributePool) { AttributePoolManager.Rename(oldName, newName); } } catch (ArgumentException ex) { // Revert to old name. if (pgProperties.SelectedObject is IFactory) { ((IFactory)pgProperties.SelectedObject).Name = oldName; } else if (pgProperties.SelectedObject is ItemPool) { ((ItemPool)pgProperties.SelectedObject).Name = oldName; } else if (pgProperties.SelectedObject is AttributePool) { ((AttributePool)pgProperties.SelectedObject).Name = oldName; } // Tell the user why. MessageBox.Show(this, ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return(false); } finally { // Stop updating the tree. tvData.EndUpdate(); } if (pgProperties.SelectedObject is IFactory) { SelectFactory((IFactory)pgProperties.SelectedObject); } else if (pgProperties.SelectedObject is ItemPool) { SelectItemPool((ItemPool)pgProperties.SelectedObject); } else if (pgProperties.SelectedObject is AttributePool) { SelectAttributePool((AttributePool)pgProperties.SelectedObject); } SelectProperty("Name"); } else { // Rescan for issues related to this property. if (changedItem.PropertyDescriptor != null && changedItem.PropertyDescriptor.Attributes[typeof(TriggersFullValidationAttribute)] == null) { if (pgProperties.SelectedObject is IFactory) { ScanForIssues((IFactory)pgProperties.SelectedObject); } else if (pgProperties.SelectedObject is ItemPool) { ScanForIssues((ItemPool)pgProperties.SelectedObject); } // Done, avoid the full rescan. return(true); } } // Do a full scan when we come here. ScanForIssues(); return(true); }
private void RenderOrbit(Orbit orbit, float origin, float scale, System.Drawing.Graphics graphics, SolidBrush brush, int number = 0) { if (orbit == null) { return; } if (orbit.Orbiters != null) { // Check each orbiting object. foreach (var orbiter in orbit.Orbiters) { var localOrigin = origin; // We can only really draw something useful if we have a radius... if (orbiter.OrbitRadius != null) { // Figure out max radius of children. var maxOrbit = scale * GetMaxRadius(orbiter.Moons); // Draw actual stuff at max bounds. localOrigin += orbiter.OrbitRadius.High * scale; var orbiterFactory = FactoryManager.GetFactory(orbiter.Name) as PlanetFactory; if (orbiterFactory != null) { brush.Color = System.Drawing.Color.FromArgb(150, orbiterFactory.SurfaceTint.R, orbiterFactory.SurfaceTint.G, orbiterFactory.SurfaceTint.B); if (orbiterFactory.Radius != null) { var diameter = orbiterFactory.Radius.Low * scale * 2; graphics.FillEllipse(brush, localOrigin - diameter / 2f, pbPreview.Image.Height / 2f - diameter / 2f, diameter, diameter); diameter = orbiterFactory.Radius.High * scale * 2; graphics.FillEllipse(brush, localOrigin - diameter / 2f, pbPreview.Image.Height / 2f - diameter / 2f, diameter, diameter); maxOrbit = Math.Max(maxOrbit, orbiterFactory.Radius.High * scale); } } // Half the interval of variance we have for our radius. var halfVariance = scale * (orbiter.OrbitRadius.High - orbiter.OrbitRadius.Low) / 2f; // Add it to the max orbit to get the overall maximum possible // when rendering a circle with its center in the middle of the // variance interval. (and times two for width/height) maxOrbit = (maxOrbit + halfVariance) * 2; // Show the indicator of the maximum bounds for this orbiter. brush.Color = System.Drawing.Color.FromArgb(20, 255, 165, 0); graphics.FillEllipse(brush, origin + scale * orbiter.OrbitRadius.Low + halfVariance - maxOrbit / 2f, pbPreview.Image.Height / 2f - maxOrbit / 2f, maxOrbit, maxOrbit); // Draw own orbit. var color = OrbitColors[number % OrbitColors.Length]; using (var p = new Pen(System.Drawing.Color.FromArgb(210, color.R, color.G, color.B))) { float orbitX, orbitY; var eccentricity = orbiter.Eccentricity.Low; Orbiter.ComputeRadii(orbiter.OrbitRadius.High * scale, eccentricity, out orbitX, out orbitY); graphics.DrawEllipse(p, origin - orbitX + orbitX * eccentricity, pbPreview.Image.Height / 2f - orbitY, orbitX * 2, orbitY * 2); p.Color = System.Drawing.Color.FromArgb(110, color.R, color.G, color.B); eccentricity = orbiter.Eccentricity.High; Orbiter.ComputeRadii(orbiter.OrbitRadius.Low * scale, eccentricity, out orbitX, out orbitY); graphics.DrawEllipse(p, origin - orbitX + orbitX * eccentricity, pbPreview.Image.Height / 2f - orbitY, orbitX * 2, orbitY * 2); } } // Render children. RenderOrbit(orbiter.Moons, localOrigin, scale, graphics, brush, ++number); } } }
public override T Load <T>(string assetName) { var g = (IGraphicsDeviceService)ServiceProvider.GetService(typeof(IGraphicsDeviceService)); if (typeof(T) == typeof(Texture2D)) { if (_textures.ContainsKey(assetName)) { return((T)(object)_textures[assetName]); } using (var img = Image.FromFile(ContentProjectManager.GetTexturePath(assetName))) { var bmp = new Bitmap(img); var data = new uint[bmp.Width * bmp.Height]; for (var y = 0; y < bmp.Height; ++y) { for (var x = 0; x < bmp.Width; ++x) { var pixel = bmp.GetPixel(x, y); data[x + y * bmp.Width] = Microsoft.Xna.Framework.Color.FromNonPremultiplied(pixel.R, pixel.G, pixel.B, pixel.A). PackedValue; } } if (g != null) { var t = new Texture2D(g.GraphicsDevice, img.Width, img.Height); t.SetData(data); _textures.Add(assetName, t); return((T)(object)t); } else { throw new InvalidOperationException("Must wait with loading until graphics device is initialized."); } } } else if (typeof(T) == typeof(Effect)) { if (_shaders.ContainsKey(assetName)) { return((T)(object)_shaders[assetName]); } var shaderPath = ContentProjectManager.GetShaderPath(assetName); if (shaderPath != null) { using (var file = File.OpenText(shaderPath)) { var sourceCode = file.ReadToEnd(); var effectSource = new EffectContent { EffectCode = sourceCode, Identity = new ContentIdentity { SourceFilename = assetName } }; var processor = new EffectProcessor(); var compiledEffect = processor.Process(effectSource, new DummyProcessorContext()); var effect = new Effect(g.GraphicsDevice, compiledEffect.GetEffectCode()); _shaders.Add(assetName, effect); return((T)(object)effect); } } } else if (typeof(T) == typeof(ParticleEffect)) { using (var xmlReader = XmlReader.Create(ContentProjectManager.GetEffectPath(assetName))) { var effect = IntermediateSerializer.Deserialize <ParticleEffect>(xmlReader, null); effect.Initialise(); effect.LoadContent(this); return((T)(object)effect); } } else if (typeof(T) == typeof(IFactory[])) { return((T)(object)FactoryManager.GetFactoriesFromFile(assetName).ToArray()); } else if (typeof(T) == typeof(ItemPool[])) { return((T)(object)ItemPoolManager.GetItemPools().ToArray()); } else if (typeof(T) == typeof(AttributePool[])) { return((T)(object)AttributePoolManager.GetAttributePools().ToArray()); } return(default(T)); }
private void RemoveClick(object sender, EventArgs e) { if (pgProperties.SelectedObject == null) { return; } if (tvData.Focused || (sender == miDelete && miDelete.Visible)) { if (pgProperties.SelectedObject is IFactory) { var factory = (IFactory)pgProperties.SelectedObject; if ((ModifierKeys & Keys.Shift) != 0 || MessageBox.Show(this, "Are you sure you wish to delete the factory '" + factory.Name + "'?", "Confirmation", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { FactoryManager.Remove(factory); // Add undo command. PushUndo("remove factory", () => { FactoryManager.Add(factory); return(true); }); } } else if (pgProperties.SelectedObject is ItemPool) { var itemPool = (ItemPool)pgProperties.SelectedObject; if ((ModifierKeys & Keys.Shift) != 0 || MessageBox.Show(this, "Are you sure you wish to delete the item pool '" + itemPool.Name + "'?", "Confirmation", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { ItemPoolManager.Remove(itemPool); // Add undo command. PushUndo("remove item pool", () => { ItemPoolManager.Add(itemPool); return(true); }); } } else if (pgProperties.SelectedObject is AttributePool) { var attributePool = (AttributePool)pgProperties.SelectedObject; if ((ModifierKeys & Keys.Shift) != 0 || MessageBox.Show(this, "Are you sure you wish to delete the attribute pool '" + attributePool.Name + "'?", "Confirmation", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { AttributePoolManager.Remove(attributePool); // Add undo command. PushUndo("remove attribute pool", () => { AttributePoolManager.Add(attributePool); return(true); }); } } } }
public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, System.IServiceProvider provider, object value) { var svc = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService; if ((value is string || value == null) && svc != null) { // Preselect old entry. _dialog.SelectedItemName = value as string; // Restrict selection. var giContext = context as GridItem; if (giContext == null || giContext.Parent == null || giContext.Parent.Parent == null || giContext.Parent.Parent.Parent == null || giContext.Parent.Parent.Parent.Value == null) { MessageBox.Show("Cannot edit here.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); return(value); } if (giContext.Parent.Parent.Parent.Value is ShipFactory) { // Top level item, only allow fuselage. _dialog.AvailableSlots = new[] { new ItemFactory.ItemSlotInfo { Size = ItemSlotSize.Small, Type = ItemFactory.ItemSlotInfo.ItemType.Fuselage } }; } else { // Somewhere in the tree, get parent. var containingItem = giContext.Parent.Parent.Parent.Value as ShipFactory.ItemInfo; if (containingItem == null || string.IsNullOrWhiteSpace(containingItem.Name)) { // Null parent, don't allow adding items. MessageBox.Show("Cannot add to empty items.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); return(value); } var containingFactory = FactoryManager.GetFactory(containingItem.Name) as ItemFactory; if (containingFactory == null) { // Invalid parent, don't allow adding items. MessageBox.Show("Cannot determine valid items due to invalid parent entry.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); return(value); } // Figure out available slots. var availableSlots = new List <ItemFactory.ItemSlotInfo>(); if (containingFactory.Slots != null) { availableSlots.AddRange(containingFactory.Slots); } else { // Item has no slots, don't allow adding items. MessageBox.Show("Cannot add any more items: no more free slots.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); return(value); } // Remove used ones. foreach (var slot in containingItem.Slots) { // Skip self. if (slot == context.Instance) { continue; } // Skip empty ones. if (string.IsNullOrWhiteSpace(slot.Name)) { continue; } // Get the item of that type. var slotItemFactory = FactoryManager.GetFactory(slot.Name) as ItemFactory; if (slotItemFactory == null) { continue; } // OK, try to consume a slot (the smallest one possible). var type = slotItemFactory.GetType().ToItemType(); var size = slotItemFactory.RequiredSlotSize; ItemFactory.ItemSlotInfo bestSlot = null; foreach (var availableSlot in availableSlots) { if (availableSlot.Type != type || availableSlot.Size < size) { continue; } if (bestSlot == null || availableSlot.Size < bestSlot.Size) { bestSlot = availableSlot; } } if (bestSlot != null) { availableSlots.Remove(bestSlot); } } // Skip if no slots remain. if (availableSlots.Count == 0) { MessageBox.Show("Cannot add any more items: no items known that would fit the remaining slots.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); return(value); } // Set remaining slots. _dialog.AvailableSlots = availableSlots; } if (svc.ShowDialog(_dialog) == DialogResult.OK) { return(_dialog.SelectedItemName); } } return(base.EditValue(context, provider, value)); }
private void NameChanged(object sender, EventArgs e) { btnOK.Enabled = !string.IsNullOrWhiteSpace(tbName.Text) && !FactoryManager.HasFactory(tbName.Text.Trim()); }
/// <summary> /// Check all known asset reference types for validity (i.e. whether the referenced object exists). /// </summary> /// <param name="main">The main object this relates to.</param> /// <param name="current">The current object being checked (that is some child of the main object).</param> /// <param name="prefix">The "path" to the current object in the main object, separated by dots ('.').</param> private void ScanReferences(object main, object current, string prefix = null) { // Skip null objects. if (main == null || current == null) { return; } // Adjust our prefix. prefix = string.IsNullOrWhiteSpace(prefix) ? "" : (prefix + "."); // Known checks: editor type to recognize the property, display name (in issue) and method to check validity. var checks = new[] { Tuple.Create <Type, string, Func <string, bool> >(typeof(TextureAssetEditor), "texture asset", ContentProjectManager.HasTextureAsset), Tuple.Create <Type, string, Func <string, bool> >(typeof(EffectAssetEditor), "effect asset", ContentProjectManager.HasEffectAsset), Tuple.Create <Type, string, Func <string, bool> >(typeof(ItemInfoEditor), "item", s => FactoryManager.GetFactory(s) as ItemFactory != null), Tuple.Create <Type, string, Func <string, bool> >(typeof(ItemPoolEditor), "item", s => FactoryManager.GetFactory(s) as ItemFactory != null), Tuple.Create <Type, string, Func <string, bool> >(typeof(ItemPoolChooserEditor), "item pool", s => ItemPoolManager.GetItemPool(s) != null), Tuple.Create <Type, string, Func <string, bool> >(typeof(PlanetEditor), "planet", s => FactoryManager.GetFactory(s) as PlanetFactory != null), Tuple.Create <Type, string, Func <string, bool> >(typeof(SunEditor), "sun", s => FactoryManager.GetFactory(s) as SunFactory != null) }; // Perform all checks. foreach (var check in checks) { // Get properties we can handle with this check. foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(current, new Attribute[] { new EditorAttribute(check.Item1, typeof(UITypeEditor)) })) { // See if the actual value is a string, which is the format we store the references in. if (property.PropertyType != typeof(string)) { AddIssue("Property marked as " + check.Item2 + " is not of type string.", main, prefix + property.Name, IssueType.Warning); } else { // Check if the reference is valid, ignore empty ones. var path = (string)property.GetValue(current); if (!string.IsNullOrWhiteSpace(path) && !check.Item3(path.Replace('\\', '/'))) { AddIssue("Invalid or unknown " + check.Item2 + " name.", main, prefix + property.Name, IssueType.Error); } } } } }
/// <summary> /// Checks orbits for validity. /// </summary> /// <param name="factory">The factory.</param> private void ScanFactory(SunSystemFactory factory) { if (factory == null) { return; } // Get sun factory, to extract base radius offset, if possible. var sunFactory = FactoryManager.GetFactory(factory.Sun) as SunFactory; var orbits = new Stack <Tuple <Orbit, float, string> >(); orbits.Push(Tuple.Create(factory.Planets, sunFactory != null && sunFactory.OffsetRadius != null ? sunFactory.OffsetRadius.High : 0f, "Planets")); while (orbits.Count > 0) { var data = orbits.Pop(); var orbit = data.Item1; var radius = data.Item2; var prefix = string.IsNullOrWhiteSpace(data.Item3) ? "" : (data.Item3 + "."); if (orbit == null) { continue; } for (var i = 0; i < orbit.Orbiters.Length; i++) { var orbiter = orbit.Orbiters[i]; var localPrefix = prefix + "Orbiters[" + i + "]."; var childRadius = radius; ScanReferences(factory, orbiter, localPrefix); if (orbiter.ChanceToExist <= 0) { AddIssue("Planet will never be generated (probability <= 0).", factory, localPrefix + "ChanceToExist", IssueType.Information); } if (orbiter.Eccentricity != null && (orbiter.Eccentricity.Low < 0 || orbiter.Eccentricity.High < 0 || orbiter.Eccentricity.Low > 1 || orbiter.Eccentricity.High > 1)) { AddIssue("Planet orbit ellipse eccentricity is in invalid value-range (should be in [0, 1]).", factory, localPrefix + "Eccentricity", IssueType.Warning); } if (orbiter.OrbitRadius == null) { AddIssue("Nor orbit radius set for planet.", factory, localPrefix + "OrbitRadius", IssueType.Error); } else { // Check if we're too large (exceeding cell size). Only trigger this message // for the first object exceeding the bounds, not for its children. childRadius += orbiter.OrbitRadius.High; if (radius < Engine.Util.UnitConversion.ToScreenUnits(CellSystem.CellSize) / 2f && childRadius >= Engine.Util.UnitConversion.ToScreenUnits(CellSystem.CellSize) / 2f) { AddIssue("Accumulative radii of orbits potentially exceed cell size.", factory, localPrefix + "OrbitRadius", IssueType.Warning); } if (orbiter.OrbitRadius.Low <= 0 || orbiter.OrbitRadius.High <= 0) { AddIssue("Orbit radius should be larger than zero for planet.", factory, localPrefix + "OrbitRadius", IssueType.Warning); } } orbits.Push(Tuple.Create(orbiter.Moons, childRadius, localPrefix + "Moons")); } } }
/// <summary> /// Check default loadout (equipment) of ships for validity. /// </summary> /// <param name="factory">The factory.</param> private void ScanFactory(ShipFactory factory) { if (factory == null) { return; } // Check collision bounds. if (factory.CollisionRadius <= 0f) { AddIssue("Ship has no collision radius.", factory, "CollisionRadius", IssueType.Information); } // Validate selected items. Make sure the root item is a fuselage. var root = factory.Items; if (root != null) { // We only check if the root item is a fuselage here, other checks will // we done in the loop below. var rootItem = FactoryManager.GetFactory(root.Name) as ItemFactory; if (rootItem != null && !(rootItem is FuselageFactory)) { AddIssue("Root item must always be a fuselage.", factory, "Items", IssueType.Error); } } else { // Nothing to do. return; } // Start with the fuselage. var items = new Stack <Tuple <ShipFactory.ItemInfo, string> >(); items.Push(Tuple.Create(root, "Items")); while (items.Count > 0) { // Get entry info, skip empty ones. var entry = items.Pop(); var item = entry.Item1; var prefix = entry.Item2; if (item == null) { continue; } // Check asset references. ScanReferences(factory, item, prefix); // Adjust prefix for further access. prefix = string.IsNullOrWhiteSpace(prefix) ? "" : (prefix + "."); // Notify about empty slots that can be removed, but keep going // to properly warn if any items are equipped in this null item. if (string.IsNullOrWhiteSpace(item.Name)) { AddIssue("Empty item name, branch will be skipped when generating the ship.", factory, prefix + "Name", IssueType.Information); } // Queue checks for children. for (var i = 0; i < item.Slots.Length; i++) { items.Push(Tuple.Create(item.Slots[i], prefix + "Slots[" + i + "]")); } // Get slot information to validate occupied slots. var itemFactory = FactoryManager.GetFactory(item.Name) as ItemFactory; var availableSlots = new List <ItemFactory.ItemSlotInfo>(); if (itemFactory != null && itemFactory.Slots != null) { availableSlots.AddRange(itemFactory.Slots); } for (var i = 0; i < item.Slots.Length; i++) { var slot = item.Slots[i]; // Skip empty ones. if (string.IsNullOrWhiteSpace(slot.Name)) { continue; } // Get the item of that type. Skip if unknown (warning will come when that // item itself is processed). var slotItemFactory = FactoryManager.GetFactory(slot.Name) as ItemFactory; if (slotItemFactory == null) { continue; } // OK, try to consume a slot (the smallest one possible). var type = slotItemFactory.GetType().ToItemType(); var size = slotItemFactory.RequiredSlotSize; ItemFactory.ItemSlotInfo bestSlot = null; foreach (var availableSlot in availableSlots) { if (availableSlot.Type != type || availableSlot.Size < size) { continue; } if (bestSlot == null || availableSlot.Size < bestSlot.Size) { bestSlot = availableSlot; } } if (bestSlot == null) { AddIssue("Equipped item cannot be fit into any slot.", factory, prefix + "Slots[" + i + "]", IssueType.Error); } else { availableSlots.Remove(bestSlot); } } } }