diff --git a/.github/workflows/computerraria-tests.yml b/.github/workflows/computerraria-tests.yml index 1af7e81..771b0f5 100644 --- a/.github/workflows/computerraria-tests.yml +++ b/.github/workflows/computerraria-tests.yml @@ -4,6 +4,8 @@ on: push: paths: - '**.cs' + branches: + - main jobs: trigger: name: Send Repository Trigger diff --git a/Accelerator.cs b/Accelerator.cs index 4f19b2f..3392fd0 100644 --- a/Accelerator.cs +++ b/Accelerator.cs @@ -13,712 +13,774 @@ using Terraria.ID; using Terraria.ModLoader; -namespace WireHead +namespace WireHead; + +// This lightly inspired by https://github.com/RussDev7/WireShark +// There are some questionable software engineering choices made there though so I wanted to rewrite it +internal static class Accelerator { - // This lightly inspired by https://github.com/RussDev7/WireShark - // There are some questionable software engineering choices made there though so I wanted to rewrite it - internal static class Accelerator + + /********************************************************************** + * Wire Caching Variables + *********************************************************************/ + + // Which group each tile belongs to + // -1 is no group, 0 is junction box with two groups + // Format is x,y,color + // For preprocessing it's more efficient to have color,x,y, but for run time performance the bottleneck + // is checking the same tile so for data locality it makes sense to do it this way + public static int[,,] wireGroup; + + // Copy of which wires are at each tile in a more usable form + // Entries are bitmasks of 1< list of standard lamps this is top of + public static List[] groupStandardLamps; + + // Set of groups that were triggered in this iteration + // Used for pixel boxes + // List.Clear is O(n) for idiotic reasons so I'm recreating it as an array + public static int[] groupsTriggered; + public static int numGroupsTriggered = 0; + + // Toggleable/triggerable tiles attached to each point + // First index is group, second is list of tiles + // uint is a bit concatenation of both 16 bit x,y coordinates + public static uint[][] toggleable; + public static uint[][] triggerable; + + // For each group, contains all other groups that could make a pixel box trigger + // Assumes that each pair of 2 groups only attaches to one pixel box + // If you wanted to remove this assumtion it should be a list of (int, uints) instead + public static Dictionary[] pixelBoxes; + // Conversion between pixel box ids and coordinates + public static List pbId2Coord; + public static Dictionary pbCoord2Id; + // Number of pixel boxes + public static int numPb = 0; + + // Number of total groups + // All arrays indexed by groups are guaranteed to be of this size + public static int numGroups = 0; + // State of a group, indexed by group + public static bool[] groupState; + // Groups that are out of sync with the world, value is original state of group + public static bool[] groupOutOfSync; + + // Terracc wire activation summary + // Guaranteed to be of size [maxTriggers, colors] + public static int[,] toHit; + // Number of new groups in toHit + public static int numToHit = 0; + + + /********************************************************************** + * Monitor Variables + *********************************************************************/ + + public static int clockGroup = -1; + public static int clockCount = 0; + public static int clockMax = int.MaxValue; + public static Point default_clock_coord = new Point(3194,153); + public static int default_clock_clr = 3; + + /********************************************************************** + * Construction Variables + *********************************************************************/ + + // Variables to help in the construction of toggleable/triggerable + public static Dictionary> toggleableDict = new Dictionary>(); + public static Dictionary> triggerableDict = new Dictionary>(); + + + /********************************************************************** + * Constants + *********************************************************************/ + + public static readonly int maxTriggers = 10000; + + // Used to have enum, removed for efficiency + // In array indexing, red=0, blue=1, green=2, yellow=3 + public const int colors = 4; + + // Maximum number of connected toggleable tiles for fast refresh to be enabled + public const int maxFastRefresh = 100; + + public static readonly HashSet triggeredIDs = new HashSet + { + TileID.LogicGateLamp, // treated specially + TileID.ClosedDoor, + TileID.OpenDoor, + TileID.TrapdoorClosed, + TileID.TrapdoorOpen, + TileID.TallGateClosed, + TileID.TallGateOpen, + TileID.InletPump, + TileID.OutletPump, + TileID.Traps, + TileID.GeyserTrap, + TileID.Explosives, + TileID.VolcanoLarge, + TileID.VolcanoSmall, + TileID.Toilets, + TileID.Cannon, + TileID.MusicBoxes, + TileID.Statues, + TileID.Chimney, + TileID.Teleporter, + TileID.Firework, + TileID.FireworkFountain, + TileID.FireworksBox, + TileID.Confetti, + TileID.BubbleMachine, + TileID.SillyBalloonMachine, + TileID.LandMine, + TileID.Campfire, + TileID.AnnouncementBox, + TileID.FogMachine, + }; + + // Purposefully excludes multi-block toggleable tiles like fireplaces + // Also no actuators since they might result in multiple triggers on one tile + public static readonly HashSet toggleableIDs = new HashSet + { + TileID.LogicGateLamp, // treated specially + TileID.Candles, + TileID.Torches, + TileID.WireBulb, + TileID.Timers, + TileID.ActiveStoneBlock, + TileID.InactiveStoneBlock, + TileID.Grate, + TileID.AmethystGemspark, + TileID.TopazGemspark, + TileID.SapphireGemspark, + TileID.EmeraldGemspark, + TileID.RubyGemspark, + TileID.DiamondGemspark, + TileID.AmberGemspark, + }; + + /********************************************************************** + * Private Functions + *********************************************************************/ + + /* + * Register a tile as part of a group + */ + private static void RegisterTile(int x, int y, int c, int group) { + Tile tile = Main.tile[x, y]; - /********************************************************************** - * Wire Caching Variables - *********************************************************************/ - - // Which group each tile belongs to - // -1 is no group, 0 is junction box with two groups - // Format is x,y,color - // For preprocessing it's more efficient to have color,x,y, but for run time performance the bottleneck - // is checking the same tile so for data locality it makes sense to do it this way - public static int[,,] wireGroup; - - // Copy of which wires are at each tile in a more usable form - // Entries are bitmasks of 1<[] pixelBoxes; - - // Number of total groups - // All arrays indexed by groups are guaranteed to be of this size - public static int numGroups = 0; - // Whether group is toggleable or not - public static bool[] groupToggleable; - // State of a group, indexed by group - public static bool[] groupState; - // Groups that are out of sync with the world, value is original state of group - public static bool[] groupOutOfSync; + // Junction boxes always have id -1 + // To avoid an infinite loop we ensure we pass through them one way + // Since each junction box has exactly one entrance per channel and one exit this works + if (tile.TileType != TileID.WirePipe) + wireGroup[x, y, c] = group; - - /********************************************************************** - * Monitor Variables - *********************************************************************/ - - public static int clockGroup = -1; - public static int clockCount = 0; - public static int clockMax = int.MaxValue; - - /********************************************************************** - * Construction Variables - *********************************************************************/ - - // Variables to help in the construction of toggleable/triggerable - public static Dictionary> toggleableDict = new Dictionary>(); - public static Dictionary> triggerableDict = new Dictionary>(); - - - /********************************************************************** - * Constants - *********************************************************************/ - // const data - // Used to have enum, removed for efficiency - // In array indexing, red=0, blue=1, green=2, yellow=3 - public static readonly int colors = 4; - //[Flags] - //public enum Color - //{ - // None = 0, - // Yellow = 1, - // Green = 2, - // Blue = 4, - // Red = 8, - //} - //// List of actual (non-None) colors to loop over - //public static readonly HashSet Colors = new HashSet - //{ - // Color.Yellow, Color.Green, Color.Blue, Color.Red, - //}; - //// Corresponds to internal Terraria wiring representations - //public static readonly Dictionary intToColor = new Dictionary - //{ - // { 1, Color.Red }, - // { 2, Color.Blue }, - // { 3, Color.Green }, - // { 4, Color.Yellow }, - //}; - - public static readonly HashSet triggeredIDs = new HashSet - { - TileID.LogicGateLamp, // treated specially - TileID.ClosedDoor, - TileID.OpenDoor, - TileID.TrapdoorClosed, - TileID.TrapdoorOpen, - TileID.TallGateClosed, - TileID.TallGateOpen, - TileID.InletPump, - TileID.OutletPump, - TileID.Traps, - TileID.GeyserTrap, - TileID.Explosives, - TileID.VolcanoLarge, - TileID.VolcanoSmall, - TileID.Toilets, - TileID.Cannon, - TileID.MusicBoxes, - TileID.Statues, - TileID.Chimney, - TileID.Teleporter, - TileID.Firework, - TileID.FireworkFountain, - TileID.FireworksBox, - TileID.Confetti, - TileID.BubbleMachine, - TileID.SillyBalloonMachine, - TileID.LandMine, - TileID.Campfire, - TileID.AnnouncementBox, - TileID.FogMachine, - }; - - // Purposefully excludes multi-block toggleable tiles like fireplaces - // Also no actuators since they might result in multiple triggers on one tile - public static readonly HashSet toggleableIDs = new HashSet - { - TileID.LogicGateLamp, // treated specially - TileID.Candles, - TileID.Torches, - TileID.WireBulb, - TileID.Timers, - TileID.ActiveStoneBlock, - TileID.InactiveStoneBlock, - TileID.Grate, - TileID.AmethystGemspark, - TileID.TopazGemspark, - TileID.SapphireGemspark, - TileID.EmeraldGemspark, - TileID.RubyGemspark, - TileID.DiamondGemspark, - TileID.AmberGemspark, - }; - - /********************************************************************** - * Private Functions - *********************************************************************/ - - /* - * Register a tile as part of a group - */ - private static void RegisterTile(int x, int y, int c, int group) + // Treat logic lamps specially since they mean different things based on frame + if (tile.TileType == TileID.LogicGateLamp) { - Tile tile = Main.tile[x, y]; - - // Junction boxes always have id -1 - // To avoid an infinite loop we ensure we pass through them one way - // Since each junction box has exactly one entrance per channel and one exit this works - if (tile.TileType != TileID.WirePipe) - wireGroup[x, y, c] = group; - - // Treat logic lamps specially since they mean different things based on frame - if (tile.TileType == TileID.LogicGateLamp) - { - // Faulty - if (tile.TileFrameX == 36) - { - triggerableDict[group].Add(new Point16(x, y)); - groupToggleable[group] = false; - } - // On/off - else - { - toggleableDict[group].Add(new Point16(x, y)); - } - } - else if (tile.TileType == TileID.PixelBox) - { - for (int col = 0; col < colors; ++col) - { - if (col == c) continue; - int g = wireGroup[x, y, col]; - if (g != -1) - { - pixelBoxes[g][group] = xy2uint(x, y); - pixelBoxes[group][g] = xy2uint(x, y); - } - } - - groupToggleable[group] = false; - } - else if (triggeredIDs.Contains(tile.TileType) || tile.HasActuator) + // Faulty + if (tile.TileFrameX == 36) { triggerableDict[group].Add(new Point16(x, y)); - groupToggleable[group] = false; } - else if (toggleableIDs.Contains(tile.TileType)) + // On/off + else { toggleableDict[group].Add(new Point16(x, y)); } } - - /* - * Find each of the wire groups in the world recursively, used for preprocessing - */ - private static void FindGroup(int x, int y, int prevX, int prevY, int c, int group) + else if (tile.TileType == TileID.PixelBox) { - if (!WorldGen.InWorld(x, y, 1)) return; - if ((wireCache[x, y] & (1<> 16), y = (short)(p & 0xFFFF); + // Handle all registration of this tile to caches + RegisterTile(x, y, c, group); - if (standardLamps[x, y]) - { - // Potential performance bottleneck - WiringWrapper._LampsToCheck.Enqueue(new Point16(x, y)); - } - else if (standardLamps[x, y-1]) - { - ToggleLamp(x, y); - } - else + if (tile.TileType != TileID.WirePipe) + { + bool prevJunction = Main.tile[prevX, prevY].TileType == TileID.WirePipe; + // What 30 minutes of stackoverflow searching for marginally cleaner syntax gets you + foreach (var (dx, dy) in new (int, int)[] { (1, 0), (0, 1), (-1, 0), (0, -1) }) { - WiringWrapper.HitWireSingle(x, y); + // Prevents you from going backwards into a junction box you just left + // Important to prevent infinite loops + if (!(prevJunction && prevX == x + dx && prevY == y + dy)) + FindGroup(x + dx, y + dy, x, y, c, group); } } - - /* - * Calculates whether a given x, y tile should toggle - */ - private static bool ShouldChange(int x, int y) + else { - // oldVal is value before last sync, newVal is current value - // We need to do it this way, otherwise we have to know how to individually toggle - // each individual type of toggleable tile, most of which are sprite dependent (which - // on a related note is really dumb, you shouldn't have to check tile.TileFrameX == 66 - // or whatever to see that a torch is on) - bool ret = false; - //for (int c = 0; c < colors; ++c) - //{ - // int group = wireGroup[x, y, c]; - // if (group != -1 && groupToggleable[group]) - // { - // ret = ret != groupOutOfSync[group]; - // } - //} - - // Pretty ugly but want to make sure compiler isn't being dumb in above loop - - int group = wireGroup[x, y, 0]; - if (group != -1 && groupToggleable[group]) + int deltaX = 0, deltaY = 0; + switch (tile.TileFrameX) { - ret = ret != groupOutOfSync[group]; + case 0: // + shape + deltaX = (x - prevX); + deltaY = (y - prevY); + break; + case 18: // // shape + deltaX = -(y - prevY); + deltaY = -(x - prevX); + break; + case 36:// \\ shape + deltaX = (y - prevY); + deltaY = (x - prevX); + break; + default: + throw new UsageException("Junction box frame didn't line up to 0, 18 or 36"); } - group = wireGroup[x, y, 1]; - if (group != -1 && groupToggleable[group]) - { - ret = ret != groupOutOfSync[group]; - } + FindGroup(x + deltaX, y + deltaY, x, y, c, group); + } + } - group = wireGroup[x, y, 2]; - if (group != -1 && groupToggleable[group]) - { - ret = ret != groupOutOfSync[group]; - } + /* + * Checks whether a tile is the top of a standard gate + */ + public static bool IsStandardFaulty(int x, int y) + { + int faultyY = y, lampY = y + 1, gateY = y + 2; + return gateY < Main.maxTilesY && + Main.tile[x, faultyY].TileType == TileID.LogicGateLamp && + Main.tile[x, faultyY].TileFrameX == 36 && + Main.tile[x, lampY].TileType == TileID.LogicGateLamp && + Main.tile[x, lampY].TileFrameX != 36 && + Main.tile[x, gateY].TileType == TileID.LogicGate && + Main.tile[x, gateY].TileFrameX == 36; + } - group = wireGroup[x, y, 3]; - if (group != -1 && groupToggleable[group]) - { - ret = ret != groupOutOfSync[group]; - } + /* + * Toggle a logic lamp + */ + private static void ToggleLamp(int x, int y) + { + // Don't call WorldGen.SquareTileFrame and NetMessage.SendTileSquare + // Also don't enqueue check since it isn't needed + Tile tile = Main.tile[x, y]; + if (tile.TileFrameX == 0) tile.TileFrameX = 18; + else if (tile.TileFrameX == 18) tile.TileFrameX = 0; + } + + /* + * Toggles a pixel box + */ + private static void TogglePixelBox(int x, int y) + { + Tile tile = Main.tile[x, y]; + if (tile.TileFrameX == 0) tile.TileFrameX = 18; + else if (tile.TileFrameX == 18) tile.TileFrameX = 0; + if (Main.netMode == NetmodeID.Server) + { + NetMessage.SendTileSquare(-1, x, y); + } + } - return ret; + /* + * Hit a single wire + */ + public static void HitWireSingle(uint p) + { + // _wireSkip should 100% be a hashset, come on relogic + //if (WiringWrapper._wireSkip.ContainsKey(p)) return; - //return groupToggleable[wireGroup[x, y, 0]] - // != groupToggleable[wireGroup[x, y, 1]] - // != groupToggleable[wireGroup[x, y, 2]] - // != groupToggleable[wireGroup[x, y, 3]]; + short x = (short)(p >> 16), y = (short)(p & 0xFFFF); + if (standardLamps[x, y]) + { + // Potential performance bottleneck + WiringWrapper._LampsToCheck.Enqueue(new Point16(x, y)); + } + else if (standardLamps[x, y-1]) + { + ToggleLamp(x, y); + } + else + { + WiringWrapper.HitWireSingle(x, y); } + } + + /* + * Calculates whether a given x, y tile should toggle + */ + private static bool ShouldChange(int x, int y) + { + // oldVal is value before last sync, newVal is current value + // We need to do it this way, otherwise we have to know how to individually toggle + // each individual type of toggleable tile, most of which are sprite dependent (which + // on a related note is really dumb, you shouldn't have to check tile.TileFrameX == 66 + // or whatever to see that a torch is on) + bool ret = false; + //for (int c = 0; c < colors; ++c) + //{ + // int group = wireGroup[x, y, c]; + // if (group != -1) + // { + // ret = ret != groupOutOfSync[group]; + // } + //} + + // Pretty ugly but want to make sure compiler isn't being dumb in above loop - // Being smarter than this requires run time performance hit, so real time is optimized - private static void SyncClients() + int group = wireGroup[x, y, 0]; + if (group != -1) { - for (int x = 0; x < Main.maxTilesX; ++x) - { - for (int y = 0; y < Main.maxTilesY; ++y) - { - Tile tile = Main.tile[x, y]; - if (toggleableIDs.Contains(tile.TileType)) - { - NetMessage.SendTileSquare(-1, x, y); - } - } - } + ret = ret != groupOutOfSync[group]; } - /* - * Converts a point to/from a bitwise uint - * Most significant 16 bits are x, least are y - */ - private static uint Point2uint(Point16 p) + group = wireGroup[x, y, 1]; + if (group != -1) { - uint x = ((uint)p.X) << 16; - uint y = (uint)p.Y; - return x | y; + ret = ret != groupOutOfSync[group]; } - private static uint xy2uint(int x, int y) + group = wireGroup[x, y, 2]; + if (group != -1) { - uint x1 = ((uint)x) << 16; - uint y1 = (uint)y; - return x1 | y1; + ret = ret != groupOutOfSync[group]; } - private static Point16 uint2Point(uint p) + + group = wireGroup[x, y, 3]; + if (group != -1) { - return new Point16((short)(p >> 16), (short)(p & 0xFFFF)); + ret = ret != groupOutOfSync[group]; } - /********************************************************************** - * Vanilla WiringWrapper Overrides - *********************************************************************/ + return ret; + + } - /* - * Triggers a set of wires of a particular type - */ - public static void HitWire(DoubleStack next, int wireType) + // Being smarter than this requires run time performance hit, so real time is optimized + private static void SyncClients() + { + for (int x = 0; x < Main.maxTilesX; ++x) { - int c = wireType-1; - HashSet alreadyHit = new HashSet(); - WiringWrapper._currentWireColor = wireType; - while (next.Count != 0) + for (int y = 0; y < Main.maxTilesY; ++y) { - var p = next.PopFront(); - int group = wireGroup[p.X, p.Y, c]; + Tile tile = Main.tile[x, y]; + if (toggleableIDs.Contains(tile.TileType)) + { + NetMessage.SendTileSquare(-1, x, y); + } + } + } + } - if (alreadyHit.Contains(group)) continue; - else alreadyHit.Add(group); + /* + * Converts a point to/from a bitwise uint + * Most significant 16 bits are x, least are y + */ + internal static uint Point2uint(Point16 p) + { + uint x = ((uint)p.X) << 16; + uint y = (uint)p.Y; + return x | y; + } - if (group == -1) continue; + internal static uint xy2uint(int x, int y) + { + uint x1 = ((uint)x) << 16; + uint y1 = (uint)y; + return x1 | y1; + } + internal static Point16 uint2Point(uint p) + { + return new Point16((short)(p >> 16), (short)(p & 0xFFFF)); + } - groupsTriggered[numGroupsTriggered++] = group; - - if (group == clockGroup) - { - ++clockCount; - if (clockCount > clockMax) - { - Console.WriteLine("Clock finished"); - clockCount = 0; - clockGroup = -1; - clockMax = int.MaxValue; - } - } + /********************************************************************** + * Vanilla WiringWrapper Overrides + *********************************************************************/ - if (groupToggleable[group]) + /* + * Triggers a set of wires of a particular type + */ + public static void HitWire(DoubleStack next, int wireType) + { + if(WireHead.useTerracc) return; + int c = wireType-1; + HashSet alreadyHit = new HashSet(); + WiringWrapper._currentWireColor = wireType; + while (next.Count != 0) + { + var p = next.PopFront(); + int group = wireGroup[p.X, p.Y, c]; + + + /* if(WireHead.useTerracc && triggerable[group].Length == groupStandardLamps[group].Count) continue; */ + + if (alreadyHit.Contains(group)) continue; + else alreadyHit.Add(group); + + if (group == -1) continue; + + groupsTriggered[numGroupsTriggered++] = group; + + if (group == clockGroup) + { + ++clockCount; + if (clockCount == clockMax) { - // Keep track of syncing - groupOutOfSync[group] = !groupOutOfSync[group]; - // If all toggleable then no need to directly trigger them - groupState[group] = !groupState[group]; + Console.WriteLine("Clock finished"); + clockMax = int.MaxValue; } - else - { - // If even one triggered tile then must trigger all of them - // Supposedly using local variables disables bound checking although I'm doubtful - // https://blog.tedd.no/2020/06/01/faster-c-array-access/ - var tog = toggleable[group]; - for (int i = 0; i < tog.Length; ++i) - { - HitWireSingle(tog[i]); - } - var trig = triggerable[group]; - for (int i = 0; i < trig.Length; ++i) - { - HitWireSingle(trig[i]); - } + } + + // Keep track of syncing + groupOutOfSync[group] = !groupOutOfSync[group]; + groupState[group] = !groupState[group]; + + // If even one triggered tile then must trigger all of them + // Supposedly using local variables disables bound checking although I'm doubtful + // https://blog.tedd.no/2020/06/01/faster-c-array-access/ + /* var tog = toggleable[group]; */ + /* for (int i = 0; i < tog.Length; ++i) */ + /* { */ + /* HitWireSingle(tog[i]); */ + /* } */ + var trig = triggerable[group]; + for (int i = 0; i < trig.Length; ++i) + { + HitWireSingle(trig[i]); + } - // Only bother checking pixel boxes if some are attached to this group - if (pixelBoxes[group].Count != 0) + // Only bother checking pixel boxes if some are attached to this group + if (pixelBoxes[group].Count != 0) + { + for (int i = 0; i < numGroupsTriggered; ++i) + { + int g = groupsTriggered[i]; + if (pixelBoxes[group].ContainsKey((g))) { - for (int i = 0; i < numGroupsTriggered; ++i) - { - int g = groupsTriggered[i]; - if (pixelBoxes[group].ContainsKey((g))) - { - Point16 point = uint2Point(pixelBoxes[group][g]); - TogglePixelBox(point.X, point.Y); - } - } + Point16 point = uint2Point(pixelBoxes[group][g]); + TogglePixelBox(point.X, point.Y); } } } - WiringWrapper.running = false; - Wiring.running = false; - //WiringWrapper._wireSkip.Clear(); } + WiringWrapper.running = false; + Wiring.running = false; + //WiringWrapper._wireSkip.Clear(); + } - /* - * Check a faulty gate and handle it from WiringWrapper.CheckLogicGate - * Requires that X, faultyY are coordinates of a faulty logic lamp with one cell - */ - public static void CheckFaultyGate(int X, int faultyY) - { - int lampY = faultyY + 1; - int gateY = faultyY + 2; - bool on = Main.tile[X, lampY].TileFrameX == 18; - // boolean xor - on = (on != ShouldChange(X, lampY)); + /* + * Check a faulty gate and handle it from WiringWrapper.CheckLogicGate + * Requires that X, faultyY are coordinates of a faulty logic lamp with one cell + */ + public static void CheckFaultyGate(int X, int faultyY) + { + int lampY = faultyY + 1; + int gateY = faultyY + 2; + bool on = Main.tile[X, lampY].TileFrameX == 18; + // boolean xor + on = (on != ShouldChange(X, lampY)); - // From decompiled, not sure if this is needed - WiringWrapper.SkipWire(X, gateY); + // From decompiled, not sure if this is needed + WiringWrapper.SkipWire(X, gateY); - if (on) + if (on) + { + WiringWrapper._GatesDone.TryGetValue(new Point16(X, gateY), out bool alreadyDone); + if (alreadyDone) { - WiringWrapper._GatesDone.TryGetValue(new Point16(X, gateY), out bool alreadyDone); - if (alreadyDone) - { - // Taken from decompiled - Vector2 position = new Vector2(X, gateY) * 16f - new Vector2(10f); - Utils.PoofOfSmoke(position); - NetMessage.SendData(MessageID.PoofOfSmoke, -1, -1, null, (int)position.X, position.Y); - return; - } - WiringWrapper._GatesNext.Enqueue(new Point16(X, gateY)); + // Taken from decompiled + Vector2 position = new Vector2(X, gateY) * 16f - new Vector2(10f); + Utils.PoofOfSmoke(position); + NetMessage.SendData(MessageID.PoofOfSmoke, -1, -1, null, (int)position.X, position.Y); + return; } + WiringWrapper._GatesNext.Enqueue(new Point16(X, gateY)); } + } - /********************************************************************** - * Public Functions - *********************************************************************/ - - /* - * Gets current true value of tile state - * Only compatible with logic gates and candles - */ - public static bool TileState(int x, int y) + /********************************************************************** + * Public Functions + *********************************************************************/ + + /* + * Gets current true value of tile state + * Only compatible with logic gates and candles + */ + public static bool TileState(int x, int y) + { + Tile tile = Main.tile[x, y]; + bool ret = false; + if (tile.TileType == TileID.LogicGateLamp) { - Tile tile = Main.tile[x, y]; - bool ret = false; - if (tile.TileType == TileID.LogicGateLamp) - { - ret = tile.TileFrameX != 0; - } else if (tile.TileType == TileID.Candles) - { - ret = tile.TileFrameX == 0; - } - else - { - throw new UsageException("Attempted to read unsupported tile"); - } - - return ret != ShouldChange(x, y); + ret = tile.TileFrameX != 0; + } else if (tile.TileType == TileID.Candles) + { + ret = tile.TileFrameX == 0; } - - /* - * Preprocess the world into logical wire groups to speed up processing - */ - public static void Preprocess() + else { - // Try and bring in sync before resetting everything - BringInSync(); - - standardLamps = new bool[Main.maxTilesX, Main.maxTilesY]; - wireCache = new int[Main.maxTilesX, Main.maxTilesY]; - wireGroup = new int[Main.maxTilesX, Main.maxTilesY, colors]; + throw new UsageException("Attempted to read unsupported tile"); + } + + return ret != ShouldChange(x, y); + } + + /* + * Preprocess the world into logical wire groups to speed up processing + */ + public static void Preprocess() + { + // Try and bring in sync before resetting everything + BringInSync(); + + standardLamps = new bool[Main.maxTilesX, Main.maxTilesY]; + wireCache = new int[Main.maxTilesX, Main.maxTilesY]; + wireGroup = new int[Main.maxTilesX, Main.maxTilesY, colors]; + + toggleableDict = new Dictionary>(); + triggerableDict = new Dictionary>(); + pbId2Coord = new List(); - toggleableDict = new Dictionary>(); - triggerableDict = new Dictionary>(); + toHit = new int[maxTriggers, colors]; - for (int x = 0; x < Main.maxTilesX; ++x) + for (int x = 0; x < Main.maxTilesX; ++x) + { + for (int y = 0; y < Main.maxTilesY; ++y) { - for (int y = 0; y < Main.maxTilesY; ++y) - { - var tile = Main.tile[x, y]; - int mask = 0; - if (tile.YellowWire) mask |= 8; - if (tile.GreenWire) mask |= 4; - if (tile.BlueWire) mask |= 2; - if (tile.RedWire) mask |= 1; - wireCache[x, y] = mask; + var tile = Main.tile[x, y]; + int mask = 0; + if (tile.YellowWire) mask |= 8; + if (tile.GreenWire) mask |= 4; + if (tile.BlueWire) mask |= 2; + if (tile.RedWire) mask |= 1; + wireCache[x, y] = mask; - if (IsStandardFaulty(x, y)) standardLamps[x, y] = true; - else standardLamps[x, y] = false; + if (IsStandardFaulty(x, y)) standardLamps[x, y] = true; + else standardLamps[x, y] = false; - for (int c = 0; c < colors; ++c) - { - wireGroup[x, y, c] = -1; - } + for (int c = 0; c < colors; ++c) + { + wireGroup[x, y, c] = -1; } } + } - int group = 0; - numGroups = 1 << 10; - groupToggleable = new bool[numGroups]; - groupState = new bool[numGroups]; - groupOutOfSync = new bool[numGroups]; - pixelBoxes = new Dictionary[numGroups]; - for (int x = 0; x < Main.maxTilesX; ++x) + int group = 0; + numGroups = 1 << 10; + groupState = new bool[numGroups]; + groupOutOfSync = new bool[numGroups]; + pixelBoxes = new Dictionary[numGroups]; + for (int x = 0; x < Main.maxTilesX; ++x) + { + for (int y = 0; y < Main.maxTilesY; ++y) { - for (int y = 0; y < Main.maxTilesY; ++y) + for (int c = 0; c < colors; ++c) { - for (int c = 0; c < colors; ++c) + if (wireGroup[x, y,c] == -1 && + (wireCache[x, y] & (1<= numGroups) { - if(group >= numGroups) - { - numGroups *= 2; - Array.Resize(ref groupToggleable, numGroups); - Array.Resize(ref groupState, numGroups); - Array.Resize(ref groupOutOfSync, numGroups); - Array.Resize(ref pixelBoxes, numGroups); - } - groupToggleable[group] = true; - groupState[group] = false; - groupOutOfSync[group] = false; - - toggleableDict[group] = new HashSet(); - triggerableDict[group] = new HashSet(); - pixelBoxes[group] = new Dictionary(); - // Guaranteed not to start on junction box so don't care about prevX,Y - FindGroup(x, y, x, y, c, group); - ++group; + numGroups *= 2; + Array.Resize(ref groupState, numGroups); + Array.Resize(ref groupOutOfSync, numGroups); + Array.Resize(ref pixelBoxes, numGroups); } + groupState[group] = false; + groupOutOfSync[group] = false; + + toggleableDict[group] = new HashSet(); + triggerableDict[group] = new HashSet(); + pixelBoxes[group] = new Dictionary(); + // Guaranteed not to start on junction box so don't care about prevX,Y + FindGroup(x, y, x, y, c, group); + ++group; } } } - // Running resizing was an overestimage, switch back - numGroups = group; - - // Convert toggleableDict/triggerableDict to arrays - toggleable = new uint[numGroups][]; - triggerable = new uint[numGroups][]; - for (int g = 0; g < numGroups; ++g) + } + // Running resizing was an overestimate, switch back + numGroups = group; + + // Convert toggleableDict/triggerableDict to arrays + toggleable = new uint[numGroups][]; + triggerable = new uint[numGroups][]; + groupStandardLamps = new List[numGroups]; + for (int g = 0; g < numGroups; ++g) + { + toggleable[g] = new uint[toggleableDict[g].Count]; + triggerable[g] = new uint[triggerableDict[g].Count]; + groupStandardLamps[g] = new List(); + int i = 0; + foreach (Point16 p in toggleableDict[g]) { - toggleable[g] = new uint[toggleableDict[g].Count]; - triggerable[g] = new uint[triggerableDict[g].Count]; - int i = 0; - foreach (Point16 p in toggleableDict[g]) - { - toggleable[g][i++] = Point2uint(p); - } - i = 0; - foreach (Point16 p in triggerableDict[g]) - { - triggerable[g][i++] = Point2uint(p); + toggleable[g][i++] = Point2uint(p); + } + i = 0; + foreach (Point16 p in triggerableDict[g]) + { + triggerable[g][i++] = Point2uint(p); + if (standardLamps[p.X, p.Y]){ + groupStandardLamps[g].Add(Point2uint(p)); } } + } + + toggleableDict.Clear(); + triggerableDict.Clear(); + groupsTriggered = new int[numGroups]; + + // Inverts pbId2Coord to a dictionary with inverse mapping + pbCoord2Id = pbId2Coord.Select((s, i) => new { s, i }).ToDictionary(x => x.s, x => x.i); + numPb = pbId2Coord.Count(); + // Hardcoded default + clockGroup = wireGroup[default_clock_coord.X, default_clock_coord.Y, default_clock_clr]; + } + + /* + * Brings just pixel boxes into sync + */ + public static void SyncPb() + { + if(WireHead.useTerracc){ + byte[] pb_states = new byte[numPb]; + TerraCC.read_pb(pb_states); + for(int i = 0; i < numPb; ++i){ + if(pb_states[i] == 0) continue; + Point16 p = uint2Point(pbId2Coord[i]); + TogglePixelBox(p.X, p.Y); + } + } + } - toggleableDict.Clear(); - triggerableDict.Clear(); - groupsTriggered = new int[numGroups]; + /* + * Brings back into sync after running efficiently + */ + public static void BringInSync(bool full=true) + { + if(WireHead.useTerracc){ + byte[] states = new byte[numGroups]; + TerraCC.read_states(states); + for(int g = 0; g < numGroups; ++g){ + /* if((states[i]==1) != groupState[i]){ */ + /* Console.WriteLine($"State {i} new state: {states[i]}"); */ + /* } */ + + + groupOutOfSync[g] = (states[g]==1) != groupState[g]; + + // Do our best to handle triggered blocks, this will only work + // if they've triggered at most once since the last sync though + if(groupOutOfSync[g]){ + // Don't update internal state if too big an update + if(!full && toggleable[g].Length >= maxFastRefresh) continue; + + // Note: this means that after a fast sync some states will won't be pulled in + groupState[g] = states[g] == 1; + + // This might not be robust, I think if you make purposefully + // confusing chained teleporters this breaks. + // + // Don't do that. + + WiringWrapper._teleport[0].X = -1f; + WiringWrapper._teleport[0].Y = -1f; + WiringWrapper._teleport[1].X = -1f; + WiringWrapper._teleport[1].Y = -1f; + foreach(uint tile in triggerable[g]){ + Point16 p = uint2Point(tile); + if(standardLamps[p.X,p.Y]) break; + HitWireSingle(tile); + /* Console.WriteLine($"Triggering from sync group: {i}, x:{p.X}, y:{p.Y}"); */ + } + if (WiringWrapper._teleport[0].X >= 0f && WiringWrapper._teleport[1].X >= 0f) + WiringWrapper.Teleport(); + } + } + SyncPb(); } - /* - * Brings back into sync after running efficiently - */ - public static void BringInSync() + HashSet lampsVisited = new HashSet(); + + for (int g = 0; g < numGroups; ++g) { - HashSet lampsVisited = new HashSet(); + if(!groupOutOfSync[g]) continue; + if(!full && toggleable[g].Length >= maxFastRefresh) continue; - for (int g = 0; g < numGroups; ++g) + for (int i = 0; i < toggleable[g].Length; ++i) { - if (groupOutOfSync[g] != true) continue; - //foreach (Point16 toggleablePoint in toggleable[g]) - for (int i = 0; i < toggleable[g].Length; ++i) - { - uint p = toggleable[g][i]; - if (lampsVisited.Contains(p)) continue; + uint p = toggleable[g][i]; + if (lampsVisited.Contains(p)) continue; - lampsVisited.Add(p); + lampsVisited.Add(p); - Point16 point = uint2Point(p); + Point16 point = uint2Point(p); - if (ShouldChange(point.X, point.Y)) - { - HitWireSingle(p); + bool xor = false; + for(int c = 0; c < colors; ++c){ + int c_group = wireGroup[point.X, point.Y, c]; + if(c_group >= 0 && (full || toggleable[c_group].Length < maxFastRefresh)){ + xor = xor != groupOutOfSync[c_group]; } } - groupOutOfSync[g] = false; - } - if (Main.netMode == NetmodeID.Server) - { - SyncClients(); - Console.WriteLine("Sync complete"); + if(xor){ + HitWireSingle(p); + } } + groupOutOfSync[g] = false; + } + if (Main.netMode == NetmodeID.Server) + { + SyncClients(); + Console.WriteLine("Sync complete"); } } + } diff --git a/Commands/AccelCommand.cs b/Commands/AccelCommand.cs index 9b5fd1b..5045b55 100644 --- a/Commands/AccelCommand.cs +++ b/Commands/AccelCommand.cs @@ -1,9 +1,4 @@ -using IL.Terraria; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System; using Terraria.ModLoader; namespace WireHead.Commands @@ -17,31 +12,61 @@ public static void Exec(string[] args) // WARNING: Not thread safe if you call preprocess, enable or disable while wiring is occuring case "preprocess": case "p": - Accelerator.Preprocess(); - Console.WriteLine("Preprocessing complete"); + WireHead.toExec.Enqueue(() => { + Accelerator.Preprocess(); + Console.WriteLine("Preprocessing complete"); + }); break; case "sync": case "s": // Print to console later once sync is actually finished - WireHead.toExec.Enqueue(Accelerator.BringInSync); + WireHead.toExec.Enqueue(() => { + Accelerator.BringInSync(true); + Console.WriteLine("Sync complete"); + }); break; case "enable": case "e": - if (WireHead.vanillaWiring) - { - WireHead.AddEvents(); - Accelerator.Preprocess(); - } - Console.WriteLine("Accelerator enabled"); + WireHead.toExec.Enqueue(() => { + if (WireHead.vanillaWiring) + { + WireHead.AddEvents(); + Accelerator.Preprocess(); + } + TerraCC.disable(); + Console.WriteLine("Traditional accelerator enabled"); + }); break; case "disable": case "d": - if (!WireHead.vanillaWiring) - { - Accelerator.BringInSync(); - WireHead.RemoveEvents(); - } - Console.WriteLine("Accelerator disabled"); + WireHead.toExec.Enqueue(() => { + if (!WireHead.vanillaWiring) + { + Accelerator.BringInSync(); + WireHead.RemoveEvents(); + TerraCC.disable(); + } + Console.WriteLine("Accelerator disabled"); + }); + break; + case "compile": + case "terracc": + case "c": + Console.WriteLine("Received compile command"); + WireHead.toExec.Enqueue(() => { + Console.WriteLine("Starting toExec"); + if (WireHead.vanillaWiring){ + WireHead.AddEvents(); + Accelerator.Preprocess(); + } + // Lazy switch + if(args.Length <= 1 || (args[1] != "l" && args[1] != "lazy")){ + TerraCC.transpile(); + TerraCC.compile(); + } + TerraCC.enable(); + Console.WriteLine("Executed compile command"); + }); break; default: throw new UsageException("cmd not recognized"); diff --git a/Commands/InitCommand.cs b/Commands/InitCommand.cs index ae630e6..ac11f6b 100644 --- a/Commands/InitCommand.cs +++ b/Commands/InitCommand.cs @@ -1,12 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Terraria.ModLoader; +using Terraria.ModLoader; using Terraria.DataStructures; using Terraria.GameContent.Tile_Entities; -using Terraria; namespace WireHead.Commands { diff --git a/Commands/Monitor.cs b/Commands/Monitor.cs index 62df707..662ddda 100644 --- a/Commands/Monitor.cs +++ b/Commands/Monitor.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Text.RegularExpressions; using Terraria.ModLoader; using Terraria; @@ -29,7 +26,7 @@ public static void Exec(string[] args) { Console.WriteLine($"Clock count complete: {Accelerator.clockCount}"); ++i; - break; + return; } if (args.Length <= i+3) throw new UsageException("Too few arguments"); @@ -50,6 +47,9 @@ public static void Exec(string[] args) } Accelerator.clockGroup = group; + Accelerator.clockCount = 0; + if(WireHead.useTerracc) + WireHead.toExec.Enqueue(()=>TerraCC.set_clock(group)); if (max > 0) Accelerator.clockMax = max; i += 3; @@ -100,4 +100,4 @@ public override void Action(CommandCaller caller, string input, string[] args) } } } -} \ No newline at end of file +} diff --git a/Commands/ReadCommand.cs b/Commands/ReadCommand.cs index 9cba3c7..aa32ddc 100644 --- a/Commands/ReadCommand.cs +++ b/Commands/ReadCommand.cs @@ -1,5 +1,4 @@ - -using System; +using System; using Terraria; using Terraria.ModLoader; diff --git a/Commands/TriggerCommand.cs b/Commands/TriggerCommand.cs index c78f4a8..df07076 100644 --- a/Commands/TriggerCommand.cs +++ b/Commands/TriggerCommand.cs @@ -1,9 +1,4 @@ -using IL.Terraria; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System; using Terraria; using Terraria.ModLoader; diff --git a/Commands/WriteCommand.cs b/Commands/WriteCommand.cs index d516c13..828da58 100644 --- a/Commands/WriteCommand.cs +++ b/Commands/WriteCommand.cs @@ -1,6 +1,4 @@ - -using System; -using Terraria; +using Terraria; using Terraria.ModLoader; namespace WireHead.Commands diff --git a/Items/CoordStaff.cs b/Items/CoordStaff.cs new file mode 100644 index 0000000..5d63c3e --- /dev/null +++ b/Items/CoordStaff.cs @@ -0,0 +1,40 @@ +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; +using Microsoft.Xna.Framework; + +namespace WireHead.Items +{ + public class CoordStaff : ModItem + { + public override void SetStaticDefaults() + { + // DisplayName.SetDefault("Coordinate Staff"); + // Tooltip.SetDefault("Prints mouse coordinates to chat on use."); + } + + public override void SetDefaults() + { + Item.width = 24; + Item.height = 28; + Item.useStyle = ItemUseStyleID.Swing; + Item.useTime = 15; + Item.useAnimation = 15; + Item.useTurn = true; + Item.autoReuse = true; + Item.rare = ItemRarityID.Master; + Item.value = Item.sellPrice(platinum: 100); + Item.mana = 0; + Item.mech = true; + } + + public override bool? UseItem(Player player) + { + int x = Player.tileTargetX; + int y = Player.tileTargetY; + Main.NewText($"Mouse Coordinates: X = {x}, Y = {y}"); + + return true; + } + } +} diff --git a/Items/CoordStaff.png b/Items/CoordStaff.png new file mode 100644 index 0000000..c1b027e Binary files /dev/null and b/Items/CoordStaff.png differ diff --git a/Items/GroupStaff.cs b/Items/GroupStaff.cs new file mode 100644 index 0000000..22cf90c --- /dev/null +++ b/Items/GroupStaff.cs @@ -0,0 +1,54 @@ +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; +using Microsoft.Xna.Framework; +using System.Text; + +namespace WireHead.Items +{ + public class GroupStaff : ModItem + { + public override void SetStaticDefaults() + { + // DisplayName.SetDefault("Group Staff"); + // Tooltip.SetDefault("Prints groups under mouse to chat on use."); + } + + public override void SetDefaults() + { + Item.width = 24; + Item.height = 28; + Item.useStyle = ItemUseStyleID.Swing; + Item.useTime = 15; + Item.useAnimation = 15; + Item.useTurn = true; + Item.autoReuse = true; + Item.rare = ItemRarityID.Master; + Item.value = Item.sellPrice(platinum: 100); + Item.mana = 0; + Item.mech = true; + } + + public override bool? UseItem(Player player) + { + int x = Player.tileTargetX; + int y = Player.tileTargetY; + StringBuilder sb = new StringBuilder(); + string[] clrs = {"Red", "Blue", "Green", "Yellow"}; + for(int c = 0; c < Accelerator.colors; ++c){ + sb.Append(clrs[c] + ": "); + int g = Accelerator.wireGroup[x,y,c]; + sb.Append(g); + if(g>= 0){ + sb.Append($" ({(Accelerator.groupState[g] ? 1 : 0)}, {(Accelerator.groupOutOfSync[g] ? 1 : 0)})"); + } + if(c != 3){ + sb.Append(", "); + } + } + Main.NewText(sb.ToString()); + + return true; + } + } +} diff --git a/Items/GroupStaff.png b/Items/GroupStaff.png new file mode 100644 index 0000000..80e880b Binary files /dev/null and b/Items/GroupStaff.png differ diff --git a/Items/StateStaff.cs b/Items/StateStaff.cs new file mode 100644 index 0000000..04a9c56 --- /dev/null +++ b/Items/StateStaff.cs @@ -0,0 +1,45 @@ +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; + +namespace WireHead.Items +{ + public class StateStaff : ModItem + { + public override void SetStaticDefaults() + { + // DisplayName.SetDefault("State Staff"); + // Tooltip.SetDefault("Toggles logic gate lamp under cursor."); + } + + public override void SetDefaults() + { + Item.width = 24; + Item.height = 28; + Item.useStyle = ItemUseStyleID.Swing; + Item.useTime = 15; + Item.useAnimation = 15; + Item.useTurn = true; + Item.autoReuse = true; + Item.rare = ItemRarityID.Master; + Item.value = Item.sellPrice(platinum: 100); + Item.mana = 0; + } + + public override bool? UseItem(Player player) + { + int x = Player.tileTargetX; + int y = Player.tileTargetY; + Tile tile = Main.tile[x, y]; + + if (tile.TileType == TileID.LogicGateLamp && tile.TileFrameX < 36){ + if (tile.TileFrameX == 0) tile.TileFrameX = 18; + else if (tile.TileFrameX == 18) tile.TileFrameX = 0; + } else{ + Main.NewText($"No toggleable lamp not under cursor!"); + } + + return true; + } + } +} diff --git a/Items/StateStaff.png b/Items/StateStaff.png new file mode 100644 index 0000000..a6d4a73 Binary files /dev/null and b/Items/StateStaff.png differ diff --git a/Items/TriggerStaff.cs b/Items/TriggerStaff.cs new file mode 100644 index 0000000..8adcb5c --- /dev/null +++ b/Items/TriggerStaff.cs @@ -0,0 +1,43 @@ +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; +using Microsoft.Xna.Framework; + +namespace WireHead.Items +{ + public class TriggerStaff : ModItem + { + public override void SetStaticDefaults() + { + // DisplayName.SetDefault("Trigger Staff"); + // Tooltip.SetDefault("Triggers the wires at your cursor."); + } + + public override void SetDefaults() + { + Item.width = 24; + Item.height = 28; + Item.useStyle = ItemUseStyleID.Swing; + Item.useTime = 15; + Item.useAnimation = 15; + Item.useTurn = true; + Item.autoReuse = true; + Item.rare = ItemRarityID.Master; + Item.value = Item.sellPrice(platinum: 100); + Item.mana = 0; + Item.mech = true; + } + + public override bool? UseItem(Player player) + { + int x = Player.tileTargetX; + int y = Player.tileTargetY; + if(WireHead.vanillaWiring) + Wiring.TripWire(x, y, 1, 1); + else + WiringWrapper.TripWire(x, y, 1, 1); + + return true; + } + } +} diff --git a/Items/TriggerStaff.png b/Items/TriggerStaff.png new file mode 100644 index 0000000..a45069c Binary files /dev/null and b/Items/TriggerStaff.png differ diff --git a/Localization/en-US_Mods.WireHead.hjson b/Localization/en-US_Mods.WireHead.hjson new file mode 100644 index 0000000..46ff362 --- /dev/null +++ b/Localization/en-US_Mods.WireHead.hjson @@ -0,0 +1,21 @@ +Items: { + CoordStaff: { + DisplayName: Coord Staff + Tooltip: "" + } + + GroupStaff: { + DisplayName: Group Staff + Tooltip: "" + } + + StateStaff: { + DisplayName: State Staff + Tooltip: "" + } + + TriggerStaff: { + DisplayName: Trigger Staff + Tooltip: "" + } +} diff --git a/README.md b/README.md index 87aa488..09cd595 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@

A drastically faster reimplementation of Terraria wiring

+
@@ -117,8 +118,3 @@ Upon seeing this you might instinctively feel like this is "cheating"; the in-ga Of course this approach does have its limitations. The group state only works on groups that only contain toggleable tiles, since triggerable must always be evaluated immediately. Since regular logic gates are all triggerable, it means that the only feasible way to make RAM in this way is with faulty logic gates. However, if as a circuit maker you jump through these hoops and keep these considerations in mind the benefit is huge. Instead of traversing the width of a hypothetical binary tree we've reduced the problem to toggling a single bit, which is obviously much faster. Time complexity wise this approach reduces a wire press (for a group with only toggleable tiles) from $O(m+n)$ to $O(1)$, for real this time. -## Micro-Optimizations - -To be continued - - diff --git a/TerraCC.cs b/TerraCC.cs new file mode 100644 index 0000000..154b7cc --- /dev/null +++ b/TerraCC.cs @@ -0,0 +1,469 @@ +using System; +using System.Text; +using System.Linq; +using System.Collections.Generic; +using System.IO; +using System.Diagnostics; +using System.Runtime.InteropServices; + +using Terraria; +using Terraria.DataStructures; + +namespace WireHead; + +internal static class TerraCC +{ + // Do I use inconsistent method/variable naming conventions? Yes. + // I realy don't like caps in my names though so whatever + + /** + * (Incomplete) Assumptions used for this module: + * 1. Logic gates never smoke + * 2. The same group is never triggered twice in the same wire eval + * 3. Pixel boxes are arranged nicely so that >= 2 group triggers=toggle + */ + + /********************************************************************** + * Constants + *********************************************************************/ + + // File paths for temporary working directory to compile stuff in + private const string work_dir = "/tmp/terracc/"; + private const string c_file_name = "wld.c"; + private const string so_file_name = "wld.so"; + + // libdl parameters + private const int RTLD_LAZY = 1; + private const string DlLibrary = "/usr/lib/libdl.so.2"; + + + /********************************************************************** + * Variables + *********************************************************************/ + + // Handle for libdl functions + private static IntPtr libHandle; + + /********************************************************************** + * Private Functions + *********************************************************************/ + + /** + * Convenience method to write to file + */ + private static void write_file(string file_name, string file){ + try + { + if (!Directory.Exists(work_dir)) + { + Directory.CreateDirectory(work_dir); + Console.WriteLine("Directory created successfully."); + } + // Write the string content to the file + File.WriteAllText(work_dir + file_name, file); + + Console.WriteLine("File written successfully."); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred: {ex.Message}"); + } + } + + /** + * String representing triggering a pixel box trigger + */ + private static string pb_str(){ + string ret = ""; + + ret += "switch(trig[i]){\n"; + + for(int g1 = 0; g1 < Accelerator.numGroups; ++g1){ + var pb_dict = Accelerator.pixelBoxes[g1]; + if(pb_dict.Count == 0) continue; + + ret += $"case {g1}:\n"; + ret += "for(int j = i+1; j < num_trig; ++j){\n"; + ret += "switch(trig[j]){\n"; + + foreach(var entry in pb_dict){ + int g2 = entry.Key; + ret += $"case {g2}:\n"; + /* ret += "printf(\"Toggling pixel box, state %d\\n\", pb_s[0]);\n"; */ + ret += $"tog(pb_s[{Accelerator.pbCoord2Id[entry.Value]}]);\n"; + ret += "break;\n"; + } + + ret += "}\n}\nbreak;\n"; + } + + ret += "}\n"; + + return ret; + } + + /* + * String representing what to do for each possible group + */ + private static string faulty_str(){ + StringBuilder ret = new StringBuilder(); + // Extra standard lamps not in the switch statement + List[] extra_std = new List[Accelerator.numGroups]; + + ret.Append("switch(trig[i]){\n"); + for(int i = 0; i < Accelerator.numGroups; ++i){ + extra_std[i] = new List(); + if(Accelerator.groupStandardLamps[i].Count == 0) continue; + ret.Append($"case {i}:\n"); + foreach(uint std_lamp in Accelerator.groupStandardLamps[i]){ + // Update extra List + // First is current wire state, second is middle lamp groups, last + // is output groups + int[,] lamp_data = new int[3,Accelerator.colors]{ + { + 1, // Entry populated + 0, // Middle gate on + 0, // Unused + 0, // Unused + }, + {-1, -1, -1, -1}, + {-1, -1, -1, -1} + }; + + List xor = new List(); + Point16 p = Accelerator.uint2Point(std_lamp); + for(int c = 0, j = 0; c < Accelerator.colors; ++c){ + int g = Accelerator.wireGroup[p.X, p.Y+1, c]; + if(g >= 0){ + xor.Add($"s[{g}]"); + lamp_data[1, j++] = g; + } + } + bool on = Main.tile[p.X, p.Y+1].TileFrameX == 18; + if(on) lamp_data[0, 1] = 1; + if(xor.Count > 0){ + ret.Append("if("); + if(!on) + ret.Append(string.Join(" ^ ", xor)); + else + ret.Append("!(" + string.Join(" ^ ", xor) + ")"); + ret.Append("){\n"); + } + if(xor.Count > 0 || on){ + for(int c = 0, j = 0; c < Accelerator.colors; ++c){ + int g = Accelerator.wireGroup[p.X, p.Y+2, c]; + if(g >= 0){ + ret.Append($"trig_next[num_trig_next++] = {g};\n"); + lamp_data[2, j++] = g; + } + } + } + extra_std[i].Add(lamp_data); + if(xor.Count > 0){ + ret.Append("}\n"); + } + } + ret.Append("break;\n"); + } + ret.Append(@" +case -1: +case 0: +break; +default: +#ifdef unreachable +unreachable(); +#else +{}; +#endif +"); + ret.Append("}\n"); + + // Extra standard lamps handling + StringBuilder std_lamps = new StringBuilder(); + int max_connections = extra_std.Max(list => list.Count()); + std_lamps.Append($"#define max_connections {max_connections}\n"); + std_lamps.Append( + $"static int std_lamps[{Accelerator.numGroups}][{max_connections}][3][{Accelerator.colors}] = {{\n" + ); + for(int g = 0; g < Accelerator.numGroups; ++g){ + if(extra_std[g].Count() == 0){ + std_lamps.Append("{0},\n"); + continue; + } + std_lamps.Append("{"); + foreach(var std_lamp in extra_std[g]){ + std_lamps.Append("{"); + for(int i = 0; i < 3; ++i){ + std_lamps.Append($"{{{std_lamp[i,0]},{std_lamp[i,1]},{std_lamp[i,2]},{std_lamp[i,3]}}},"); + } + std_lamps.Append("},"); + } + std_lamps.Append("},\n"); + } + + std_lamps.Append("};\n"); + + write_file("std_lamps.c", std_lamps.ToString()); + + return $@" +for(int j = 0; j < max_connections; ++j){{ + if(std_lamps[trig[i]][j][0][0] == 0) break; + bool xor = std_lamps[trig[i]][j][0][1]; + for(int c = 0; c < {Accelerator.colors}; ++c){{ + int g = std_lamps[trig[i]][j][1][c]; + if(g < 0) break; + xor ^= s[g]; + }} + if(xor){{ + for(int c = 0; c < {Accelerator.colors}; ++c){{ + int g = std_lamps[trig[i]][j][2][c]; + if(g < 0) break; + trig_next[num_trig_next++] = g; + }} + }} +}} + "; + + /* return ret.ToString(); */ + } + + + /********************************************************************** + * Public Functions + *********************************************************************/ + + /* + * Transpile a Terraria wiring world into a c program with the same control + * graph + * I wish there was an equivalent of #include in C#, but since there isn't + * I'm sticking to string blocks + */ + public static void transpile() + { + Accelerator.Preprocess(); + string c_file = $@" +#include +#include +#include +#include +#include + +#define tog(x) (x=!x) + +#define num_groups {Accelerator.numGroups+1} +#define num_pb {Accelerator.numPb} +#define colors 4 +#define max_triggers {Accelerator.maxTriggers} +#define max_depth 5000 + +// Wire states +static bool s[num_groups] = {{{string.Join(", ", Accelerator.groupState.Take(Accelerator.numGroups+1).Select(b => b ? "1" : "0"))}}}; + +// Pixel boxes +static bool pb_s[num_pb] = {{0}}; + +// Clock monitor +static int clock_group = {Accelerator.clockGroup}; +static int clock_count = 0; + +// Standard lamp connections +#include ""std_lamps.c"" + +void trigger(int input_groups[][colors], int32_t num_inputs){{ + /* printf(""input: %d\n"", input_groups[0][0]); */ + for(int j = 0; j < num_inputs; ++j){{ + + int num_trig = 0; + int num_trig_next = 0; + int to_trigger1[max_triggers]; + int to_trigger2[max_triggers]; + + // Switch back between these to avoid memcpy + int *trig = to_trigger1; + int *trig_next = to_trigger2; + + // Load initial trigger groups + for(int c = 0; c < colors; ++c){{ + if(input_groups[j][c] < 0) continue; + trig[num_trig] = input_groups[j][c]; + ++num_trig; + }} + + int iter = 0; + while(num_trig > 0){{ + if(iter >= max_depth){{ + printf(""Max depth exceeded!\n""); + break; + }} else ++iter; + + for(int i = 0; i < num_trig; ++i){{ + tog(s[trig[i]]); + if(trig[i] == clock_group) ++clock_count; + {pb_str()} + }} + + for(int i = 0; i < num_trig; ++i){{ + {faulty_str()} + }} + + // Switch buffer assignment + int *tmp = trig_next; + trig_next = trig; + trig = tmp; + + num_trig = num_trig_next; + num_trig_next = 0; + }} + }} +}} + +void read_states(uint8_t *states){{ + memcpy(states, s, num_groups * sizeof(s[0])); +}} + +void read_pb(uint8_t *pb_states){{ + memcpy(pb_states, pb_s, num_pb * sizeof(pb_s[0])); + memset(pb_s, 0, num_pb * sizeof(pb_s[0])); +}} + +int read_clock(){{ + return clock_count; +}} + +void set_clock(int group){{ + clock_group = group; + clock_count = 0; +}} + +int main(void){{ + int triggers[1][4] = {{{{4, -1, -1, -1}}}}; + trigger(triggers, 1); + + uint8_t states[num_groups] = {{0}}; + read_states(states); + uint8_t pb_states[num_groups] = {{0}}; + read_pb(pb_states); + + for(int i = 0; i < num_groups; ++i){{ + printf(""%d "", states[i]); + }} + printf(""\n""); + for(int i = 0; i < num_pb; ++i){{ + printf(""%d "", pb_states[i]); + }} + printf(""\n""); +}} + +"; + write_file(c_file_name, c_file); + + } + + public static void compile(){ + // Create a ProcessStartInfo object + ProcessStartInfo processInfo = new ProcessStartInfo( + "gcc", + $"-fpic -shared -O1 -o {work_dir}{so_file_name} {work_dir}{c_file_name}" + ); + processInfo.RedirectStandardOutput = true; + processInfo.RedirectStandardError = true; + processInfo.UseShellExecute = false; + processInfo.CreateNoWindow = true; + + // Create a new Process instance + Process process = new Process(); + process.StartInfo = processInfo; + + // Start the process + Console.WriteLine($"Started compiling"); + process.Start(); + + // Read the standard output of the command + string output = process.StandardOutput.ReadToEnd(); + string error = process.StandardError.ReadToEnd(); + + // Wait for the process to finish + process.WaitForExit(); + if (process.ExitCode != 0) + { + // Handle the error here or throw an exception if needed + Console.WriteLine($"Error occurred. Exit code: {process.ExitCode}"); + Console.WriteLine($"Error message: {error}"); + throw new InvalidOperationException("Compiling Failed!"); + } + } + + public static void enable(){ + if(libHandle != IntPtr.Zero){ + disable(); + } + + libHandle = dlopen(work_dir + so_file_name, RTLD_LAZY); + if (libHandle == IntPtr.Zero) + { + Console.WriteLine("Error Loading libdl"); + // Handle error if library couldn't be loaded + IntPtr error = dlerror(); + string errorMessage = Marshal.PtrToStringAnsi(error); + Console.WriteLine($"Error loading library: {errorMessage}"); + return; + } + + IntPtr trigger_ptr = dlsym(libHandle, "trigger"); + IntPtr read_states_ptr = dlsym(libHandle, "read_states"); + IntPtr read_pb_ptr = dlsym(libHandle, "read_pb"); + IntPtr read_clock_ptr = dlsym(libHandle, "read_clock"); + IntPtr set_clock_ptr = dlsym(libHandle, "set_clock"); + + trigger = Marshal.GetDelegateForFunctionPointer(trigger_ptr); + read_states = Marshal.GetDelegateForFunctionPointer(read_states_ptr); + read_pb = Marshal.GetDelegateForFunctionPointer(read_pb_ptr); + read_clock = Marshal.GetDelegateForFunctionPointer(read_clock_ptr); + set_clock = Marshal.GetDelegateForFunctionPointer(set_clock_ptr); + + WireHead.useTerracc = true; + if(Accelerator.clockGroup != -1){ + set_clock(Accelerator.clockGroup); + } + Console.WriteLine("terracc enabled"); + } + + public static void disable(){ + if(WireHead.useTerracc){ + WireHead.useTerracc = false; + dlclose(libHandle); + libHandle = IntPtr.Zero; + Console.WriteLine("terracc disabled"); + } + + } + + public static TriggerDelegate trigger; + public static ReadStatesDelegate read_states; + public static ReadPbDelegate read_pb; + public static ReadClockDelegate read_clock; + public static SetClockDelegate set_clock; + + /********************************************************************** + * Imported functions + *********************************************************************/ + + [DllImport(DlLibrary, SetLastError = true)] + private static extern IntPtr dlopen(string filename, int flags); + + [DllImport(DlLibrary)] + private static extern IntPtr dlsym(IntPtr handle, string symbol); + + [DllImport(DlLibrary)] + private static extern IntPtr dlerror(); + + [DllImport(DlLibrary)] + private static extern int dlclose(IntPtr handle); + + public delegate void TriggerDelegate(int[,] input_groups, int num_inputs); + public delegate void ReadStatesDelegate([Out] byte[] states); + public delegate void ReadPbDelegate([Out] byte[] pb_states); + public delegate int ReadClockDelegate(); + public delegate void SetClockDelegate(int group); + +} diff --git a/WireHead.cs b/WireHead.cs index 85449cb..5a5ecff 100644 --- a/WireHead.cs +++ b/WireHead.cs @@ -3,13 +3,15 @@ using WireHead.Commands; using System; using System.Collections.Concurrent; +using System.Runtime.InteropServices; using Steamworks; using Terraria; using SteelSeries.GameSense; using Microsoft.Xna.Framework; using Terraria.DataStructures; using Terraria.GameContent.Tile_Entities; -using IL.Terraria.ID; +/* using IL.Terraria.ID; */ +using Terraria.ID; namespace WireHead { @@ -17,9 +19,10 @@ public class WireHead : Mod { public static bool vanillaWiring = false; + public static bool useTerracc = false; public static ConcurrentQueue toExec = new ConcurrentQueue(); - private static void UpdateConnectedClients(On.Terraria.Netplay.orig_UpdateConnectedClients orig) + private static void UpdateConnectedClients(Terraria.On_Netplay.orig_UpdateConnectedClients orig) { // Forces update even if no clients connected by making it think there are always clients orig(); @@ -40,26 +43,26 @@ public static void AddEvents() vanillaWiring = false; WorldFile.OnWorldLoad += Accelerator.Preprocess; - On.Terraria.Wiring.SetCurrentUser += Events.SetCurrentUser; + Terraria.On_Wiring.SetCurrentUser += Events.SetCurrentUser; //On.Terraria.Wiring.Initialize += Events.Initialize; - On.Terraria.Wiring.UpdateMech += Events.UpdateMech; - On.Terraria.Wiring.HitSwitch += Events.HitSwitch; - On.Terraria.Wiring.PokeLogicGate += Events.PokeLogicGate; - On.Terraria.Wiring.Actuate += Events.Actuate; - On.Terraria.Wiring.ActuateForced += Events.ActuateForced; + Terraria.On_Wiring.UpdateMech += Events.UpdateMech; + Terraria.On_Wiring.HitSwitch += Events.HitSwitch; + Terraria.On_Wiring.PokeLogicGate += Events.PokeLogicGate; + Terraria.On_Wiring.Actuate += Events.Actuate; + Terraria.On_Wiring.ActuateForced += Events.ActuateForced; //On.Terraria.Wiring.MassWireOperation += Events.MassWireOperation; - On.Terraria.Wiring.GetProjectileSource += Events.GetProjectileSource; - On.Terraria.Wiring.GetNPCSource += Events.GetNPCSource; - On.Terraria.Wiring.GetItemSource += Events.GetItemSource; - On.Terraria.Wiring.ToggleHolidayLight += Events.ToggleHolidayLight; - On.Terraria.Wiring.ToggleHangingLantern += Events.ToggleHangingLantern; - On.Terraria.Wiring.Toggle2x2Light += Events.Toggle2x2Light; - On.Terraria.Wiring.ToggleLampPost += Events.ToggleLampPost; - On.Terraria.Wiring.ToggleTorch += Events.ToggleTorch; - On.Terraria.Wiring.ToggleLamp += Events.ToggleLamp; - On.Terraria.Wiring.ToggleChandelier += Events.ToggleChandelier; - On.Terraria.Wiring.ToggleCampFire += Events.ToggleCampFire; - On.Terraria.Wiring.ToggleFirePlace += Events.ToggleFirePlace; + Terraria.On_Wiring.GetProjectileSource += Events.GetProjectileSource; + Terraria.On_Wiring.GetNPCSource += Events.GetNPCSource; + Terraria.On_Wiring.GetItemSource += Events.GetItemSource; + Terraria.On_Wiring.ToggleHolidayLight += Events.ToggleHolidayLight; + Terraria.On_Wiring.ToggleHangingLantern += Events.ToggleHangingLantern; + Terraria.On_Wiring.Toggle2x2Light += Events.Toggle2x2Light; + Terraria.On_Wiring.ToggleLampPost += Events.ToggleLampPost; + Terraria.On_Wiring.ToggleTorch += Events.ToggleTorch; + Terraria.On_Wiring.ToggleLamp += Events.ToggleLamp; + Terraria.On_Wiring.ToggleChandelier += Events.ToggleChandelier; + Terraria.On_Wiring.ToggleCampFire += Events.ToggleCampFire; + Terraria.On_Wiring.ToggleFirePlace += Events.ToggleFirePlace; WiringWrapper.Initialize(); } @@ -71,33 +74,33 @@ public static void RemoveEvents() vanillaWiring = true; WorldFile.OnWorldLoad -= Accelerator.Preprocess; - On.Terraria.Wiring.SetCurrentUser -= Events.SetCurrentUser; + Terraria.On_Wiring.SetCurrentUser -= Events.SetCurrentUser; //On.Terraria.Wiring.Initialize -= Events.Initialize; - On.Terraria.Wiring.UpdateMech -= Events.UpdateMech; - On.Terraria.Wiring.HitSwitch -= Events.HitSwitch; - On.Terraria.Wiring.PokeLogicGate -= Events.PokeLogicGate; - On.Terraria.Wiring.Actuate -= Events.Actuate; - On.Terraria.Wiring.ActuateForced -= Events.ActuateForced; + Terraria.On_Wiring.UpdateMech -= Events.UpdateMech; + Terraria.On_Wiring.HitSwitch -= Events.HitSwitch; + Terraria.On_Wiring.PokeLogicGate -= Events.PokeLogicGate; + Terraria.On_Wiring.Actuate -= Events.Actuate; + Terraria.On_Wiring.ActuateForced -= Events.ActuateForced; //On.Terraria.Wiring.MassWireOperation -= Events.MassWireOperation; - On.Terraria.Wiring.GetProjectileSource -= Events.GetProjectileSource; - On.Terraria.Wiring.GetNPCSource -= Events.GetNPCSource; - On.Terraria.Wiring.GetItemSource -= Events.GetItemSource; - On.Terraria.Wiring.ToggleHolidayLight -= Events.ToggleHolidayLight; - On.Terraria.Wiring.ToggleHangingLantern -= Events.ToggleHangingLantern; - On.Terraria.Wiring.Toggle2x2Light -= Events.Toggle2x2Light; - On.Terraria.Wiring.ToggleLampPost -= Events.ToggleLampPost; - On.Terraria.Wiring.ToggleTorch -= Events.ToggleTorch; - On.Terraria.Wiring.ToggleLamp -= Events.ToggleLamp; - On.Terraria.Wiring.ToggleChandelier -= Events.ToggleChandelier; - On.Terraria.Wiring.ToggleCampFire -= Events.ToggleCampFire; - On.Terraria.Wiring.ToggleFirePlace -= Events.ToggleFirePlace; + Terraria.On_Wiring.GetProjectileSource -= Events.GetProjectileSource; + Terraria.On_Wiring.GetNPCSource -= Events.GetNPCSource; + Terraria.On_Wiring.GetItemSource -= Events.GetItemSource; + Terraria.On_Wiring.ToggleHolidayLight -= Events.ToggleHolidayLight; + Terraria.On_Wiring.ToggleHangingLantern -= Events.ToggleHangingLantern; + Terraria.On_Wiring.Toggle2x2Light -= Events.Toggle2x2Light; + Terraria.On_Wiring.ToggleLampPost -= Events.ToggleLampPost; + Terraria.On_Wiring.ToggleTorch -= Events.ToggleTorch; + Terraria.On_Wiring.ToggleLamp -= Events.ToggleLamp; + Terraria.On_Wiring.ToggleChandelier -= Events.ToggleChandelier; + Terraria.On_Wiring.ToggleCampFire -= Events.ToggleCampFire; + Terraria.On_Wiring.ToggleFirePlace -= Events.ToggleFirePlace; Wiring.Initialize(); } public override void Load() { base.Load(); - On.Terraria.Netplay.UpdateConnectedClients += UpdateConnectedClients; + Terraria.On_Netplay.UpdateConnectedClients += UpdateConnectedClients; //Array.Resize(ref Main.npc, 1000); //for (int i = 201; i < Main.npc.Length; ++i) //{ @@ -105,7 +108,7 @@ public override void Load() // Main.npc[i].whoAmI = i; //} - On.Terraria.NPC.NewNPC += NewNPC; + Terraria.On_NPC.NewNPC += NewNPC; AddEvents(); @@ -114,13 +117,13 @@ public override void Load() public override void Unload() { base.Unload(); - On.Terraria.Netplay.UpdateConnectedClients -= UpdateConnectedClients; - On.Terraria.NPC.NewNPC -= NewNPC; + Terraria.On_Netplay.UpdateConnectedClients -= UpdateConnectedClients; + Terraria.On_NPC.NewNPC -= NewNPC; RemoveEvents(); } - public static int NewNPC(On.Terraria.NPC.orig_NewNPC orig, + public static int NewNPC(Terraria.On_NPC.orig_NewNPC orig, IEntitySource source, int X, int Y, int Type, int Start = 0, float ai0 = 0f, float ai1 = 0f, float ai2 = 0f, float ai3 = 0f, int Target = 255) { @@ -163,82 +166,82 @@ public static int NewNPC(On.Terraria.NPC.orig_NewNPC orig, */ private class Events { - public static void SetCurrentUser(On.Terraria.Wiring.orig_SetCurrentUser orig, int plr) { + public static void SetCurrentUser(Terraria.On_Wiring.orig_SetCurrentUser orig, int plr) { WiringWrapper.SetCurrentUser(plr); } //public static void Initialize(On.Terraria.Wiring.orig_Initialize orig) //{ // WiringWrapper.Initialize(); //} - public static void UpdateMech(On.Terraria.Wiring.orig_UpdateMech orig) + public static void UpdateMech(Terraria.On_Wiring.orig_UpdateMech orig) { WiringWrapper.UpdateMech(); } - public static void HitSwitch(On.Terraria.Wiring.orig_HitSwitch orig, int i, int j) + public static void HitSwitch(Terraria.On_Wiring.orig_HitSwitch orig, int i, int j) { WiringWrapper.HitSwitch(i, j); } - public static void PokeLogicGate(On.Terraria.Wiring.orig_PokeLogicGate orig, int x, int y) + public static void PokeLogicGate(Terraria.On_Wiring.orig_PokeLogicGate orig, int x, int y) { WiringWrapper.PokeLogicGate(x, y); } - public static bool Actuate(On.Terraria.Wiring.orig_Actuate orig, int i, int j) + public static bool Actuate(Terraria.On_Wiring.orig_Actuate orig, int i, int j) { return WiringWrapper.Actuate(i, j); } - public static void ActuateForced(On.Terraria.Wiring.orig_ActuateForced orig, int i, int j) + public static void ActuateForced(Terraria.On_Wiring.orig_ActuateForced orig, int i, int j) { WiringWrapper.ActuateForced(i, j); } - public static void MassWireOperation(On.Terraria.Wiring.orig_MassWireOperation orig, Point ps, Point pe, Player master) + public static void MassWireOperation(Terraria.On_Wiring.orig_MassWireOperation orig, Point ps, Point pe, Player master) { WiringWrapper.MassWireOperation(ps, pe, master); } - public static IEntitySource GetProjectileSource(On.Terraria.Wiring.orig_GetProjectileSource orig, int x, int y) + public static IEntitySource GetProjectileSource(Terraria.On_Wiring.orig_GetProjectileSource orig, int x, int y) { return WiringWrapper.GetProjectileSource(x, y); } - public static IEntitySource GetNPCSource(On.Terraria.Wiring.orig_GetNPCSource orig, int x, int y) + public static IEntitySource GetNPCSource(Terraria.On_Wiring.orig_GetNPCSource orig, int x, int y) { return WiringWrapper.GetNPCSource(x, y); } - public static IEntitySource GetItemSource(On.Terraria.Wiring.orig_GetItemSource orig, int x, int y) + public static IEntitySource GetItemSource(Terraria.On_Wiring.orig_GetItemSource orig, int x, int y) { return WiringWrapper.GetItemSource(x, y); } - public static void ToggleHolidayLight(On.Terraria.Wiring.orig_ToggleHolidayLight orig, int i, int j, Tile tileCache, bool? f) + public static void ToggleHolidayLight(Terraria.On_Wiring.orig_ToggleHolidayLight orig, int i, int j, Tile tileCache, bool? f) { WiringWrapper.ToggleHolidayLight(i, j, tileCache, f); } - public static void ToggleHangingLantern(On.Terraria.Wiring.orig_ToggleHangingLantern orig, int i, int j, Tile tileCache, bool? f, bool d) + public static void ToggleHangingLantern(Terraria.On_Wiring.orig_ToggleHangingLantern orig, int i, int j, Tile tileCache, bool? f, bool d) { WiringWrapper.ToggleHangingLantern(i, j, tileCache, f, d); } - public static void Toggle2x2Light(On.Terraria.Wiring.orig_Toggle2x2Light orig, int i, int j, Tile tileCache, bool? f, bool d) + public static void Toggle2x2Light(Terraria.On_Wiring.orig_Toggle2x2Light orig, int i, int j, Tile tileCache, bool? f, bool d) { WiringWrapper.Toggle2x2Light(i, j, tileCache, f, d); } - public static void ToggleLampPost(On.Terraria.Wiring.orig_ToggleLampPost orig, int i, int j, Tile tileCache, bool? f, bool d) + public static void ToggleLampPost(Terraria.On_Wiring.orig_ToggleLampPost orig, int i, int j, Tile tileCache, bool? f, bool d) { WiringWrapper.ToggleLampPost(i, j, tileCache, f, d); } - public static void ToggleTorch(On.Terraria.Wiring.orig_ToggleTorch orig, int i, int j, Tile tileCache, bool? f) + public static void ToggleTorch(Terraria.On_Wiring.orig_ToggleTorch orig, int i, int j, Tile tileCache, bool? f) { WiringWrapper.ToggleTorch(i, j, tileCache, f); } - public static void ToggleLamp(On.Terraria.Wiring.orig_ToggleLamp orig, int i, int j, Tile tileCache, bool? f, bool d) + public static void ToggleLamp(Terraria.On_Wiring.orig_ToggleLamp orig, int i, int j, Tile tileCache, bool? f, bool d) { WiringWrapper.ToggleLamp(i, j, tileCache, f, d); } - public static void ToggleChandelier(On.Terraria.Wiring.orig_ToggleChandelier orig, int i, int j, Tile tileCache, bool? f, bool d) + public static void ToggleChandelier(Terraria.On_Wiring.orig_ToggleChandelier orig, int i, int j, Tile tileCache, bool? f, bool d) { WiringWrapper.ToggleChandelier(i, j, tileCache, f, d); } - public static void ToggleCampFire(On.Terraria.Wiring.orig_ToggleCampFire orig, int i, int j, Tile tileCache, bool? f, bool d) + public static void ToggleCampFire(Terraria.On_Wiring.orig_ToggleCampFire orig, int i, int j, Tile tileCache, bool? f, bool d) { WiringWrapper.ToggleCampFire(i, j, tileCache, f, d); } - public static void ToggleFirePlace(On.Terraria.Wiring.orig_ToggleFirePlace orig, int i, int j, Tile tileCache, bool? f, bool d) + public static void ToggleFirePlace(Terraria.On_Wiring.orig_ToggleFirePlace orig, int i, int j, Tile tileCache, bool? f, bool d) { WiringWrapper.ToggleFirePlace(i, j, tileCache, f, d); } @@ -256,18 +259,56 @@ public override void PreSaveAndQuit() { if (!WireHead.vanillaWiring) Accelerator.BringInSync(); + TerraCC.disable(); base.PreSaveAndQuit(); } // This used to be PostUpdateWorld, have no idea why it randomly broke - public override void PostUpdatePlayers() + public override void PostUpdateEverything() { foreach (var action in WireHead.toExec) { action(); } - WireHead.toExec.Clear(); + + if(WireHead.useTerracc){ + if(Accelerator.numToHit > 0){ + /* Console.WriteLine($"Triggering r:{Accelerator.toHit[0,0]}, b: {Accelerator.toHit[0,1]}, g: {Accelerator.toHit[0,1]}, y: {Accelerator.toHit[0,3]}"); */ + } + TerraCC.trigger(Accelerator.toHit, Accelerator.numToHit); + Accelerator.numToHit = 0; + Accelerator.SyncPb(); + int cc = TerraCC.read_clock(); + // Only do even number of times, bringinsync handles last one if odd + /* Console.WriteLine($"Diff: {(cc-Accelerator.clockCount)}, to trigger: {2*((cc-Accelerator.clockCount)/2)}, clockGroup: {Accelerator.clockGroup}, size: {Accelerator.triggerable[Accelerator.clockGroup].Length}"); */ + for(int i = 0; i < 2*((cc-Accelerator.clockCount)/2); ++i){ + WiringWrapper._teleport[0].X = -1f; + WiringWrapper._teleport[0].Y = -1f; + WiringWrapper._teleport[1].X = -1f; + WiringWrapper._teleport[1].Y = -1f; + foreach(uint tile in Accelerator.triggerable[Accelerator.clockGroup]){ + Point16 p = Accelerator.uint2Point(tile); + if(Accelerator.standardLamps[p.X,p.Y]) continue; + Accelerator.HitWireSingle(tile); + /* Console.WriteLine($"Triggering, x:{p.X}, y:{p.Y}"); */ + } + if (WiringWrapper._teleport[0].X >= 0f && WiringWrapper._teleport[1].X >= 0f) + WiringWrapper.Teleport(); + } + Accelerator.clockCount = cc; + if(Accelerator.clockCount > Accelerator.clockMax){ + Console.WriteLine("Clock finished"); + Accelerator.clockCount = 0; + TerraCC.set_clock(-1); + } + } + + // Only fast refresh in single player to minimize network stuff + if (Main.netMode == NetmodeID.SinglePlayer){ + Accelerator.BringInSync(false); + } + base.PostUpdateWorld(); } } diff --git a/WireHead.csproj b/WireHead.csproj index 6396c49..dd87b30 100644 --- a/WireHead.csproj +++ b/WireHead.csproj @@ -10,4 +10,4 @@ - \ No newline at end of file + diff --git a/WiringWrapper.cs b/WiringWrapper.cs index 7e85d29..d657b79 100644 --- a/WiringWrapper.cs +++ b/WiringWrapper.cs @@ -27,7 +27,8 @@ namespace Terraria { public static class WiringWrapper { - public static bool blockPlayerTeleportationForOneIteration; + // Use Wiring.blockPlayerTeleportationForOneIteration since other files check it + /* public static bool blockPlayerTeleportationForOneIteration; */ public static bool running; public static Dictionary _wireSkip; public static DoubleStack _wireList; @@ -429,11 +430,30 @@ private static void XferWater() } } - public static void TripWire(int left, int top, int width, int height) + public static void TripWire(int left, int top, int width, int height) { if (Main.netMode == 1) return; + // Modified to accomodate terracc + if(WireHead.WireHead.useTerracc){ + for(int c = 0; c < Accelerator.colors; ++c){ + Accelerator.toHit[Accelerator.numToHit,c] = -1; + for(int x = left; x < left+width; ++x){ + for(int y = top; y < top+height; ++y){ + int g = Accelerator.wireGroup[x, y, c]; + if(g == -1) continue; + /* if(Accelerator.triggerable[g].Length == Accelerator.groupStandardLamps[g].Count){ */ + + Accelerator.toHit[Accelerator.numToHit, c] = g; + /* Console.WriteLine($"Trigger c:{c}, group: {g}"); */ + /* } */ + } + } + } + ++Accelerator.numToHit; + } + running = true; if (_wireList.Count != 0) _wireList.Clear(quickClear: true); @@ -625,10 +645,9 @@ private static void LogicGatePass() } _GatesDone.Clear(); - if (blockPlayerTeleportationForOneIteration) + if (Wiring.blockPlayerTeleportationForOneIteration) { // Other files check this so we need to make sure it stays in sync - blockPlayerTeleportationForOneIteration = false; Wiring.blockPlayerTeleportationForOneIteration = false; } } @@ -2521,7 +2540,7 @@ private static void GeyserTrap(int i, int j) } } - private static void Teleport() + public static void Teleport() { if (_teleport[0].X < _teleport[1].X + 3f && _teleport[0].X > _teleport[1].X - 3f && _teleport[0].Y > _teleport[1].Y - 3f && _teleport[0].Y < _teleport[1].Y) return; @@ -2541,7 +2560,7 @@ private static void Teleport() if (i == 1) value = new Vector2(array[0].X - array[1].X, array[0].Y - array[1].Y); - if (!blockPlayerTeleportationForOneIteration) + if (!Wiring.blockPlayerTeleportationForOneIteration) { for (int j = 0; j < 255; j++) {