4 using System.Collections.Generic;
5 using System.Collections.Immutable;
8 using Microsoft.Xna.Framework;
12 internal sealed
partial class CircuitBox
14 public CircuitBoxUI? UI;
15 public readonly Dictionary<Character, CircuitBoxCursor> ActiveCursors =
new Dictionary<Character, CircuitBoxCursor>();
16 public Option<ItemPrefab> HeldComponent = Option.None;
18 private const float CursorUpdateInterval = 1f;
19 private float cursorUpdateTimer;
21 private readonly Vector2[] recordedCursorPositions =
new Vector2[10];
22 private Option<Vector2> recordedDragStart = Option.None;
23 private Option<ItemPrefab> recordedHeldPrefab = Option.None;
29 private bool wasInitializedByServer;
31 public Sprite? WireSprite {
get;
private set; }
32 public Sprite? ConnectionSprite {
get;
private set; }
33 public Sprite? WireConnectorSprite {
get;
private set; }
34 public Sprite? ConnectionScrewSprite {
get;
private set; }
35 public UISprite? NodeFrameSprite {
get;
private set; }
36 public UISprite? NodeTopSprite {
get;
private set; }
38 protected override void CreateGUI()
42 UI?.CreateGUI(GuiFrame);
45 partial
void InitProjSpecific(ContentXElement element)
47 UI =
new CircuitBoxUI(
this);
51 foreach (var subElement
in element.Elements())
53 switch (subElement.Name.ToString().ToLowerInvariant())
56 WireSprite =
new Sprite(subElement);
58 case "connectionsprite":
59 ConnectionSprite =
new Sprite(subElement);
61 case "wireconnectorsprite":
62 WireConnectorSprite =
new Sprite(subElement);
64 case "connectionscrewsprite":
65 ConnectionScrewSprite =
new Sprite(subElement);
70 if (GUIStyle.GetComponentStyle(
"CircuitBoxTop") is { } topStyle)
72 NodeTopSprite = topStyle.Sprites[GUIComponent.ComponentState.None][0];
75 if (GUIStyle.GetComponentStyle(
"CircuitBoxFrame") is { } compStyle)
77 NodeFrameSprite = compStyle.Sprites[GUIComponent.ComponentState.None][0];
81 public override bool ShouldDrawHUD(Character character)
82 => character ==
Character.Controlled && (character.SelectedItem ==
item || character.SelectedSecondaryItem ==
item);
84 public override void UpdateHUDComponentSpecific(Character character,
float deltaTime, Camera cam)
86 if (UI is
null) {
return; }
90 if (GameMain.NetworkMember is
null) {
return; }
92 foreach (var (cursorChar, cursor) in ActiveCursors)
94 if (!cursor.IsActive) {
continue; }
96 ActiveCursors[cursorChar].Update(deltaTime);
99 Vector2 cursorPos = UI.GetCursorPosition();
100 int lastCursorPosIndex = recordedCursorPositions.Length - 1;
102 if (cursorUpdateTimer < CursorUpdateInterval)
104 cursorUpdateTimer += deltaTime;
105 int cursorIndex = (int)MathF.Floor(cursorUpdateTimer * lastCursorPosIndex);
106 RecordCursorPosition(cursorIndex);
110 RecordCursorPosition(lastCursorPosIndex);
111 SendCursorState(recordedCursorPositions, recordedDragStart, recordedHeldPrefab.Select(
static c => c.Identifier));
113 recordedDragStart = Option.None;
114 recordedHeldPrefab = Option.None;
115 cursorUpdateTimer = 0f;
118 void RecordCursorPosition(
int index)
120 var dragStart = UI.GetDragStart();
121 if (dragStart.IsSome()) { recordedDragStart = dragStart; }
123 var heldComponent = HeldComponent;
124 if (heldComponent.IsSome()) { recordedHeldPrefab = heldComponent; }
126 if (index >= 0 && index < recordedCursorPositions.Length) { recordedCursorPositions[index] = cursorPos; }
130 public void RemoveComponents(IReadOnlyCollection<CircuitBoxComponent> node)
132 if (Locked) {
return; }
133 var ids = node.Select(
static n => n.ID).ToImmutableArray();
135 if (GameMain.NetworkMember is
null)
137 CreateRefundItemsForUsedResources(ids,
Character.Controlled);
138 RemoveComponentInternal(ids);
142 if (!node.Any()) {
return; }
144 CreateClientEvent(
new CircuitBoxRemoveComponentEvent(ids));
147 public void AddWire(CircuitBoxConnection one, CircuitBoxConnection two)
149 if (Locked) {
return; }
150 if (GameMain.NetworkMember is
null)
152 Connect(one, two,
static delegate { }, CircuitBoxWire.SelectedWirePrefab);
156 if (!VerifyConnection(one, two)) {
return; }
158 CreateClientEvent(
new CircuitBoxClientAddWireEvent(Color.White, CircuitBoxConnectorIdentifier.FromConnection(one), CircuitBoxConnectorIdentifier.FromConnection(two), CircuitBoxWire.SelectedWirePrefab.UintIdentifier));
161 public void RemoveWires(IReadOnlyCollection<CircuitBoxWire> wires)
163 if (Locked) {
return; }
164 var ids = wires.Select(
static w => w.ID).ToImmutableArray();
165 if (GameMain.NetworkMember is
null)
167 RemoveWireInternal(ids);
171 if (!ids.Any()) {
return; }
172 CreateClientEvent(
new CircuitBoxRemoveWireEvent(ids));
175 public void SelectComponents(IReadOnlyCollection<CircuitBoxNode> moveables,
bool overwrite)
177 if (
Character.Controlled is not { ID: var controlledId }) {
return; }
179 var ids = ImmutableArray.CreateBuilder<ushort>();
180 var ios = ImmutableArray.CreateBuilder<CircuitBoxInputOutputNode.Type>();
181 var labelIds = ImmutableArray.CreateBuilder<ushort>();
183 foreach (var moveable
in moveables)
185 if (moveable is { IsSelected:
true, IsSelectedByMe:
false }) {
continue; }
189 case CircuitBoxComponent node:
192 case CircuitBoxInputOutputNode io:
193 ios.Add(io.NodeType);
195 case CircuitBoxLabelNode label:
196 labelIds.Add(label.ID);
201 if (GameMain.NetworkMember is
null)
203 SelectComponentsInternal(ids, controlledId, overwrite);
204 SelectInputOutputInternal(ios, controlledId, overwrite);
205 SelectLabelsInternal(labelIds, controlledId, overwrite);
209 if (!ids.Any() && !ios.Any() && !labelIds.Any() && !overwrite) {
return; }
211 CreateClientEvent(
new CircuitBoxSelectNodesEvent(ids.ToImmutable(), ios.ToImmutable(), labelIds.ToImmutable(), overwrite, controlledId));
214 public void SelectWires(IReadOnlyCollection<CircuitBoxWire> wires,
bool overwrite)
216 if (
Character.Controlled is not { ID: var controlledId }) {
return; }
218 var ids = (from wire in wires where !wire.IsSelected || wire.IsSelectedByMe select wire.ID).ToImmutableArray();
220 if (GameMain.NetworkMember is
null)
222 SelectWiresInternal(ids, controlledId, overwrite);
226 if (!ids.Any() && !overwrite) {
return; }
228 CreateClientEvent(
new CircuitBoxSelectWiresEvent(ids, overwrite,
Character.Controlled.ID));
231 public void MoveComponent(Vector2 moveAmount, IReadOnlyCollection<CircuitBoxNode> moveables)
233 if (Locked) {
return; }
234 var ids = ImmutableArray.CreateBuilder<ushort>();
235 var ios = ImmutableArray.CreateBuilder<CircuitBoxInputOutputNode.Type>();
236 var labelIds = ImmutableArray.CreateBuilder<ushort>();
238 foreach (CircuitBoxNode move
in moveables)
242 case CircuitBoxComponent node:
245 case CircuitBoxInputOutputNode io:
246 ios.Add(io.NodeType);
248 case CircuitBoxLabelNode label:
249 labelIds.Add(label.ID);
254 if (GameMain.NetworkMember is
null)
256 MoveNodesInternal(ids, ios, labelIds, moveAmount);
260 if (!ids.Any() && !ios.Any() && !labelIds.Any()) {
return; }
263 CreateClientEvent(
new CircuitBoxMoveComponentEvent(ids.ToImmutable(), ios.ToImmutable(), labelIds.ToImmutable(), moveAmount));
266 public void AddComponent(ItemPrefab prefab, Vector2 pos)
268 if (Locked) {
return; }
269 if (GameMain.NetworkMember is
null)
273 if (IsFull) {
return; }
277 if (!GetApplicableResourcePlayerHas(prefab,
Character.Controlled).TryUnwrap(out var r)) {
return; }
283 resource = ItemPrefab.Prefabs[Tags.FPGACircuit];
286 AddComponentInternal(ICircuitBoxIdentifiable.FindFreeID(Components), prefab, resource, pos,
Character.Controlled, onItemSpawned:
null);
290 CreateClientEvent(
new CircuitBoxAddComponentEvent(prefab.UintIdentifier, pos));
293 public void RenameLabel(CircuitBoxLabelNode label, Color color, NetLimitedString header, NetLimitedString body)
295 if (Locked) {
return; }
296 if (GameMain.NetworkMember is
null)
298 label.EditText(header, body);
303 CreateClientEvent(
new CircuitBoxRenameLabelEvent(label.ID, color, header, body));
306 public void SetConnectionLabelOverrides(CircuitBoxInputOutputNode node, Dictionary<string, string> newOverrides)
308 if (GameMain.NetworkMember is
null)
310 node.ReplaceAllConnectionLabelOverrides(newOverrides);
314 CreateClientEvent(
new CircuitBoxRenameConnectionLabelsEvent(node.NodeType, newOverrides.ToNetDictionary()));
317 public void ResizeNode(CircuitBoxNode node, CircuitBoxResizeDirection dir, Vector2 amount)
319 if (Locked) {
return; }
320 var resize = node.ResizeBy(dir, amount);
321 if (GameMain.NetworkMember is
null)
323 node.ApplyResize(resize.Size, resize.Pos);
333 if (node is not ICircuitBoxIdentifiable identifiable)
335 DebugConsole.ThrowError(
"Tried to resize a node that doesn't have an ID.");
339 CreateClientEvent(
new CircuitBoxResizeLabelEvent(identifiable.ID, resize.Pos, resize.Size));
344 if (Locked) {
return; }
345 if (GameMain.NetworkMember is
null)
347 AddLabelInternal(ICircuitBoxIdentifiable.FindFreeID(Labels), GUIStyle.Blue, pos, CircuitBoxLabelNode.DefaultHeaderText, NetLimitedString.Empty);
351 CreateClientEvent(
new CircuitBoxAddLabelEvent(pos, GUIStyle.Blue, CircuitBoxLabelNode.DefaultHeaderText, NetLimitedString.Empty));
354 public void RemoveLabel(IReadOnlyCollection<CircuitBoxLabelNode> labels)
356 if (Locked) {
return; }
357 if (!labels.Any()) {
return; }
359 var ids = labels.Select(
static n => n.ID).ToImmutableArray();
361 if (GameMain.NetworkMember is
null)
363 RemoveLabelInternal(ids);
367 CreateClientEvent(
new CircuitBoxRemoveLabelEvent(ids));
370 public partial
void OnViewUpdateProjSpecific()
372 UI?.MouseSnapshotHandler.UpdateConnections();
373 UI?.UpdateComponentList();
376 protected override void OnResolutionChanged()
378 base.OnResolutionChanged();
383 public partial
void OnDeselected(Character c)
385 cursorUpdateTimer = 0f;
388 if (GameMain.NetworkMember is not
null) {
return; }
389 ClearAllSelectionsInternal(c.ID);
392 public void ClientRead(INetSerializableStruct data)
396 case NetCircuitBoxCursorInfo cursorInfo:
398 ClientReadCursor(cursorInfo);
401 case CircuitBoxErrorEvent errorData:
403 DebugConsole.ThrowError($
"The server responded with an error: {errorData.Message}");
407 throw new ArgumentOutOfRangeException(nameof(data), data,
"This data cannot be handled using direct network messages.");
411 public void SendMessage(
CircuitBoxOpcode opcode, INetSerializableStruct data)
415 msg.WriteNetSerializableStruct(
new NetCircuitBoxHeader(
420 msg.WriteNetSerializableStruct(data);
422 DeliveryMethod deliveryMethod =
423 UnrealiableOpcodes.Contains(opcode)
424 ? DeliveryMethod.Unreliable
425 : DeliveryMethod.Reliable;
427 GameMain.Client?.ClientPeer?.Send(msg, deliveryMethod);
430 private void SendCursorState(Vector2[] cursorPositions, Option<Vector2> dragStart, Option<Identifier> heldComponent)
432 if (!IsRoundRunning()) {
return; }
434 var msg =
new NetCircuitBoxCursorInfo(
435 RecordedPositions: cursorPositions,
436 DragStart: dragStart,
437 HeldItem: heldComponent);
442 public void ClientReadCursor(NetCircuitBoxCursorInfo info)
444 if (Entity.FindEntityByID(info.CharacterID) is not Character character) {
return; }
446 if (!ActiveCursors.ContainsKey(character))
448 var newCursor =
new CircuitBoxCursor(info);
449 ActiveCursors.Add(character, newCursor);
453 var activeCursor = ActiveCursors[character];
454 activeCursor.UpdateInfo(info);
455 activeCursor.ResetTimers();
458 public void CreateClientEvent(INetSerializableStruct data)
459 =>
item.CreateClientEvent(
this,
new CircuitBoxEventData(data));
463 if (extraData is
null) {
return; }
464 var eventData = ExtractEventData<CircuitBoxEventData>(extraData);
466 msg.WriteNetSerializableStruct(eventData.Data);
469 public void ClientEventRead(
IReadMessage msg,
float sendingTime)
477 var data = INetSerializableStruct.Read<CircuitBoxServerCreateComponentEvent>(msg);
478 AddComponentFromData(data);
483 var data = INetSerializableStruct.Read<CircuitBoxRemoveComponentEvent>(msg);
484 RemoveComponentInternal(data.TargetIDs);
489 var data = INetSerializableStruct.Read<CircuitBoxMoveComponentEvent>(msg);
490 MoveNodesInternal(data.TargetIDs, data.IOs, data.LabelIDs, data.MoveAmount);
495 var data = INetSerializableStruct.Read<CircuitBoxServerUpdateSelection>(msg);
497 var nodeDict = data.ComponentIds.ToImmutableDictionary(
static s => s.ID,
static s => s.SelectedBy);
498 var wireDict = data.WireIds.ToImmutableDictionary(
static s => s.ID,
static s => s.SelectedBy);
499 var ioDict = data.InputOutputs.ToImmutableDictionary(
static s => s.Type,
static s => s.SelectedBy);
500 var labelDict = data.LabelIds.ToImmutableDictionary(
static s => s.ID,
static s => s.SelectedBy);
502 UpdateSelections(nodeDict, wireDict, ioDict, labelDict);
507 var data = INetSerializableStruct.Read<CircuitBoxServerCreateWireEvent>(msg);
508 AddWireFromData(data);
513 var data = INetSerializableStruct.Read<CircuitBoxRemoveWireEvent>(msg);
514 RemoveWireInternal(data.TargetIDs);
523 var data = INetSerializableStruct.Read<CircuitBoxInitializeStateFromServerEvent>(msg);
524 foreach (var compData
in data.Components) { AddComponentFromData(compData); }
525 foreach (var wireData
in data.Wires) { AddWireFromData(wireData); }
527 foreach (var labelData
in data.Labels)
529 AddLabelInternal(labelData.ID, labelData.Color, labelData.Position, labelData.Header, labelData.Body);
530 ResizeLabelInternal(labelData.ID, labelData.Position, labelData.Size);
533 foreach (var node
in InputOutputNodes)
535 node.Position = node.NodeType
switch
537 CircuitBoxInputOutputNode.Type.Input => data.InputPos,
538 CircuitBoxInputOutputNode.Type.Output => data.OutputPos,
543 foreach (var labelOverride
in data.LabelOverrides)
545 RenameConnectionLabelsInternal(labelOverride.Type, labelOverride.Override.ToDictionary());
548 wasInitializedByServer =
true;
553 var data = INetSerializableStruct.Read<CircuitBoxRenameLabelEvent>(msg);
554 RenameLabelInternal(data.LabelId, data.Color, data.NewHeader, data.NewBody);
559 var data = INetSerializableStruct.Read<CircuitBoxServerAddLabelEvent>(msg);
560 AddLabelInternal(data.ID, data.Color, data.Position, data.Header, data.Body);
561 ResizeLabelInternal(data.ID, data.Position, data.Size);
566 var data = INetSerializableStruct.Read<CircuitBoxRemoveLabelEvent>(msg);
567 RemoveLabelInternal(data.TargetIDs);
572 var data = INetSerializableStruct.Read<CircuitBoxResizeLabelEvent>(msg);
573 ResizeLabelInternal(data.ID, data.Position, data.Size);
578 var data = INetSerializableStruct.Read<CircuitBoxRenameConnectionLabelsEvent>(msg);
579 RenameConnectionLabelsInternal(data.Type, data.Override.ToDictionary());
583 throw new ArgumentOutOfRangeException(nameof(header), header,
"This opcode cannot be handled using entity events");
587 public void AddComponentFromData(CircuitBoxServerCreateComponentEvent data)
589 if (ItemPrefab.Prefabs.Find(p => p.UintIdentifier == data.UsedResource) is not { } prefab)
591 throw new Exception($
"No item prefab found for \"{data.UsedResource}\"");
594 AddComponentInternalUnsafe(data.ComponentId, FindItemByID(data.BackingItemId), prefab, data.Position);
597 public void AddWireFromData(CircuitBoxServerCreateWireEvent data)
599 var (req, wireId, possibleItemId) = data;
600 var prefab = ItemPrefab.Prefabs.Find(p => p.UintIdentifier == req.SelectedWirePrefabIdentifier);
603 throw new Exception($
"No prefab found for \"{req.SelectedWirePrefabIdentifier}\"");
606 if (!req.Start.FindConnection(
this).TryUnwrap(out var start))
608 throw new Exception($
"No connection found for ({req.Start})");
611 if (!req.End.FindConnection(
this).TryUnwrap(out var end))
613 throw new Exception($
"No connection found for ({req.Start})");
616 if (possibleItemId.TryUnwrap(out var backingItem))
618 CreateWireWithItem(start, end, wireId, FindItemByID(backingItem));
622 CreateWireWithoutItem(start, end, wireId, prefab);
626 public static Item FindItemByID(ushort
id)
627 => Entity.FindEntityByID(
id) as
Item ??
throw new Exception($
"No item with ID {id} exists.");
629 public override void AddToGUIUpdateList(
int order = 0)
631 base.AddToGUIUpdateList(order);
632 UI?.AddToGUIUpdateList();
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
virtual void ClearChildren()
int GetComponentIndex(ItemComponent component)
void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData=null)