├── .editorconfig
├── .gitattributes
├── .gitattributes.txt
├── .gitignore
├── API
├── Generator.cs
├── Legacy
│ ├── LegacyGenerator.cs
│ └── LegacySaver.cs
├── MultiStructureGenerator.cs
└── Saver.cs
├── AssGenConfig.txt
├── Assets
├── GUI
│ ├── Box.png
│ ├── Cross.png
│ ├── Down.png
│ ├── DownLarge.png
│ ├── Eye.png
│ ├── EyeClosed.png
│ ├── Gradient.png
│ ├── Null.png
│ ├── PlusB.png
│ ├── PlusG.png
│ ├── PlusP.png
│ ├── PlusR.png
│ ├── Refresh.png
│ ├── Up.png
│ └── UpLarge.png
├── Items
│ ├── ChestWand.png
│ ├── MultistructureWand.png
│ ├── PortingWand.png
│ ├── StructureWand.png
│ └── TestWand.png
├── Tiles
│ ├── NullBlock.png
│ ├── NullBlockItem.png
│ ├── NullTileAndWallPlacer.png
│ ├── NullWall.png
│ └── NullWallItem.png
├── box.png
└── corner.png
├── ChestHelper
├── ChestEntity.cs
├── ChestRule.cs
├── ChestRuleChance.cs
├── ChestRuleGuaranteed.cs
├── ChestRulePool.cs
└── ChestRulePoolChance.cs
├── Content
├── GUI
│ ├── BoolEditor.cs
│ ├── ChestGUI
│ │ ├── ChestCustomizerState.cs
│ │ ├── ChestRuleElement.cs
│ │ ├── LootElement.cs
│ │ ├── NumberSetter.cs
│ │ └── SpecificChestRuleElements.cs
│ ├── FieldEditor.cs
│ ├── ManualGeneratorMenu.cs
│ ├── NameConfirmPopup.cs
│ ├── TextField.cs
│ ├── Tooltip.cs
│ └── UIRenderer.cs
├── Items
│ ├── ChestWand.cs
│ ├── MultistructureWand.cs
│ ├── PortingWand.cs
│ ├── StructureWand.cs
│ └── TestWand.cs
└── Tiles
│ └── NullBlock.cs
├── Core
└── Loaders
│ └── UILoading
│ ├── SmartUIElement.cs
│ ├── SmartUIState.cs
│ └── UILoader.cs
├── Generator.cs
├── Helper.cs
├── Helpers
├── ErrorHelper.cs
├── GUIHelper.cs
└── LocalizationHelper.cs
├── Localization
└── en-US.hjson
├── LocalizationRoundabout.cs
├── Models
├── GenFlags.cs
├── Legacy
│ └── TileSaveData.cs
├── MultiStructureData.cs
├── NbtEntries
│ ├── SignNBTEntry.cs
│ └── TileEntityNBTEntry.cs
├── StructureData.cs
├── StructureNbtEntry.cs
└── TileDataEntry.cs
├── Porting
└── Porter.cs
├── Properties
└── launchSettings.json
├── SampleChest
├── StructureHelper.cs
├── StructureHelper.csproj
├── StructureHelper.sln
├── Util
├── LegacyStructurePreview.cs
└── StructurePreview.cs
├── WikiPics
├── Buttons.png
├── Weight.png
└── noWeight.png
├── build.txt
├── description.txt
├── description_workshop.txt
├── icon.png
├── icon_workshop.png
└── workshop.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitattributes.txt:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | # we don't want to remove this *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | *.sbr
86 | *.tlb
87 | *.tli
88 | *.tlh
89 | *.tmp
90 | *.tmp_proj
91 | *_wpftmp.csproj
92 | *.log
93 | *.vspscc
94 | *.vssscc
95 | .builds
96 | *.pidb
97 | *.svclog
98 | *.scc
99 |
100 | # Chutzpah Test files
101 | _Chutzpah*
102 |
103 | # Visual C++ cache files
104 | ipch/
105 | *.aps
106 | *.ncb
107 | *.opendb
108 | *.opensdf
109 | *.sdf
110 | *.cachefile
111 | *.VC.db
112 | *.VC.VC.opendb
113 |
114 | # Visual Studio profiler
115 | *.psess
116 | *.vsp
117 | *.vspx
118 | *.sap
119 |
120 | # Visual Studio Trace Files
121 | *.e2e
122 |
123 | # TFS 2012 Local Workspace
124 | $tf/
125 |
126 | # Guidance Automation Toolkit
127 | *.gpState
128 |
129 | # ReSharper is a .NET coding add-in
130 | _ReSharper*/
131 | *.[Rr]e[Ss]harper
132 | *.DotSettings.user
133 |
134 | # TeamCity is a build add-in
135 | _TeamCity*
136 |
137 | # DotCover is a Code Coverage Tool
138 | *.dotCover
139 |
140 | # AxoCover is a Code Coverage Tool
141 | .axoCover/*
142 | !.axoCover/settings.json
143 |
144 | # Coverlet is a free, cross platform Code Coverage Tool
145 | coverage*.json
146 | coverage*.xml
147 | coverage*.info
148 |
149 | # Visual Studio code coverage results
150 | *.coverage
151 | *.coveragexml
152 |
153 | # NCrunch
154 | _NCrunch_*
155 | .*crunch*.local.xml
156 | nCrunchTemp_*
157 |
158 | # MightyMoose
159 | *.mm.*
160 | AutoTest.Net/
161 |
162 | # Web workbench (sass)
163 | .sass-cache/
164 |
165 | # Installshield output folder
166 | [Ee]xpress/
167 |
168 | # DocProject is a documentation generator add-in
169 | DocProject/buildhelp/
170 | DocProject/Help/*.HxT
171 | DocProject/Help/*.HxC
172 | DocProject/Help/*.hhc
173 | DocProject/Help/*.hhk
174 | DocProject/Help/*.hhp
175 | DocProject/Help/Html2
176 | DocProject/Help/html
177 |
178 | # Click-Once directory
179 | publish/
180 |
181 | # Publish Web Output
182 | *.[Pp]ublish.xml
183 | *.azurePubxml
184 | # Note: Comment the next line if you want to checkin your web deploy settings,
185 | # but database connection strings (with potential passwords) will be unencrypted
186 | *.pubxml
187 | *.publishproj
188 |
189 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
190 | # checkin your Azure Web App publish settings, but sensitive information contained
191 | # in these scripts will be unencrypted
192 | PublishScripts/
193 |
194 | # NuGet Packages
195 | *.nupkg
196 | # NuGet Symbol Packages
197 | *.snupkg
198 | # The packages folder can be ignored because of Package Restore
199 | **/[Pp]ackages/*
200 | # except build/, which is used as an MSBuild target.
201 | !**/[Pp]ackages/build/
202 | # Uncomment if necessary however generally it will be regenerated when needed
203 | #!**/[Pp]ackages/repositories.config
204 | # NuGet v3's project.json files produces more ignorable files
205 | *.nuget.props
206 | *.nuget.targets
207 |
208 | # Microsoft Azure Build Output
209 | csx/
210 | *.build.csdef
211 |
212 | # Microsoft Azure Emulator
213 | ecf/
214 | rcf/
215 |
216 | # Windows Store app package directories and files
217 | AppPackages/
218 | BundleArtifacts/
219 | Package.StoreAssociation.xml
220 | _pkginfo.txt
221 | *.appx
222 | *.appxbundle
223 | *.appxupload
224 |
225 | # Visual Studio cache files
226 | # files ending in .cache can be ignored
227 | *.[Cc]ache
228 | # but keep track of directories ending in .cache
229 | !?*.[Cc]ache/
230 |
231 | # Others
232 | ClientBin/
233 | ~$*
234 | *~
235 | *.dbmdl
236 | *.dbproj.schemaview
237 | *.jfm
238 | *.pfx
239 | *.publishsettings
240 | orleans.codegen.cs
241 |
242 | # Including strong name files can present a security risk
243 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
244 | #*.snk
245 |
246 | # Since there are multiple workflows, uncomment next line to ignore bower_components
247 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
248 | #bower_components/
249 |
250 | # RIA/Silverlight projects
251 | Generated_Code/
252 |
253 | # Backup & report files from converting an old project file
254 | # to a newer Visual Studio version. Backup files are not needed,
255 | # because we have git ;-)
256 | _UpgradeReport_Files/
257 | Backup*/
258 | UpgradeLog*.XML
259 | UpgradeLog*.htm
260 | ServiceFabricBackup/
261 | *.rptproj.bak
262 |
263 | # SQL Server files
264 | *.mdf
265 | *.ldf
266 | *.ndf
267 |
268 | # Business Intelligence projects
269 | *.rdl.data
270 | *.bim.layout
271 | *.bim_*.settings
272 | *.rptproj.rsuser
273 | *- [Bb]ackup.rdl
274 | *- [Bb]ackup ([0-9]).rdl
275 | *- [Bb]ackup ([0-9][0-9]).rdl
276 |
277 | # Microsoft Fakes
278 | FakesAssemblies/
279 |
280 | # GhostDoc plugin setting file
281 | *.GhostDoc.xml
282 |
283 | # Node.js Tools for Visual Studio
284 | .ntvs_analysis.dat
285 | node_modules/
286 |
287 | # Visual Studio 6 build log
288 | *.plg
289 |
290 | # Visual Studio 6 workspace options file
291 | *.opt
292 |
293 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
294 | *.vbw
295 |
296 | # Visual Studio LightSwitch build output
297 | **/*.HTMLClient/GeneratedArtifacts
298 | **/*.DesktopClient/GeneratedArtifacts
299 | **/*.DesktopClient/ModelManifest.xml
300 | **/*.Server/GeneratedArtifacts
301 | **/*.Server/ModelManifest.xml
302 | _Pvt_Extensions
303 |
304 | # Paket dependency manager
305 | .paket/paket.exe
306 | paket-files/
307 |
308 | # FAKE - F# Make
309 | .fake/
310 |
311 | # CodeRush personal settings
312 | .cr/personal
313 |
314 | # Python Tools for Visual Studio (PTVS)
315 | __pycache__/
316 | *.pyc
317 |
318 | # Cake - Uncomment if you are using it
319 | # tools/**
320 | # !tools/packages.config
321 |
322 | # Tabs Studio
323 | *.tss
324 |
325 | # Telerik's JustMock configuration file
326 | *.jmconfig
327 |
328 | # BizTalk build output
329 | *.btp.cs
330 | *.btm.cs
331 | *.odx.cs
332 | *.xsd.cs
333 |
334 | # OpenCover UI analysis results
335 | OpenCover/
336 |
337 | # Azure Stream Analytics local run output
338 | ASALocalRun/
339 |
340 | # MSBuild Binary and Structured Log
341 | *.binlog
342 |
343 | # NVidia Nsight GPU debugger configuration file
344 | *.nvuser
345 |
346 | # MFractors (Xamarin productivity tool) working folder
347 | .mfractor/
348 |
349 | # Local History for Visual Studio
350 | .localhistory/
351 |
352 | # BeatPulse healthcheck temp database
353 | healthchecksdb
354 |
355 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
356 | MigrationBackup/
357 |
358 | # Ionide (cross platform F# VS Code tools) working folder
359 | .ionide/
360 |
361 | # Fody - auto-generated XML schema
362 | FodyWeavers.xsd
363 |
364 | # Custom
365 | *.idx
366 | FETCH_HEAD
367 | *.pack
368 | refs/remotes/origin/ArmorEnchantAndBags
369 | /Localization/en-US.hjson
370 |
--------------------------------------------------------------------------------
/API/Generator.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.Helpers;
2 | using StructureHelper.Models;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.IO.Compression;
7 | using Terraria.DataStructures;
8 | using Terraria.ID;
9 |
10 | namespace StructureHelper.API
11 | {
12 | ///
13 | /// In this class you will find various utilities related to generating structures and getting important
14 | /// information from them such as their dimensions.
15 | ///
16 | public class Generator
17 | {
18 | internal static readonly Dictionary StructureCache = [];
19 |
20 | ///
21 | /// Helper to check bounds on a generation call. You can also use this to check the bounds
22 | /// of your own structure
23 | ///
24 | /// The StructureData to check
25 | /// The position to check from, this would be the top-left of the structure.
26 | /// If the structure is in bounds or not
27 | public static bool IsInBounds(StructureData data, Point16 pos)
28 | {
29 | if (pos.X < 0 || pos.X + data.width >= Main.maxTilesX || pos.Y < 0 || pos.Y + data.height >= Main.maxTilesY)
30 | return false;
31 |
32 | return true;
33 | }
34 |
35 | ///
36 | /// Helper to check bounds on a generation call. You can also use this to check the bounds
37 | /// of your own structure
38 | ///
39 | /// The path to search for the structure file
40 | /// The mod to search for the structure file in
41 | /// The position to check from, this would be the top-left of the structure.
42 | /// If the structure is in bounds or not
43 | public static bool IsInBounds(string path, Mod mod, Point16 pos, bool fullPath = false)
44 | {
45 | StructureData data = GetStructureData(path, mod, fullPath);
46 |
47 | if (pos.X < 0 || pos.X + data.width >= Main.maxTilesX || pos.Y < 0 || pos.Y + data.height >= Main.maxTilesY)
48 | return false;
49 |
50 | return true;
51 | }
52 |
53 | ///
54 | /// Gets the dimensions (width and height) of a structure.
55 | ///
56 | /// The path to search for the structure file. If it is in a mod, it should not include the mods name (for example, it should be "structures/coolHouse", not "CoolHouseMod/structures/coolHouse")
57 | /// The mod to search for the structure file in
58 | /// If the search path starts at the root of your file system(true) or the provided mod(false). This should usually be false.
59 | ///
60 | public static Point16 GetStructureDimensions(string path, Mod mod, bool fullPath = false)
61 | {
62 | StructureData data = GetStructureData(path, mod, fullPath);
63 |
64 | return new Point16(data.width, data.height);
65 | }
66 |
67 | ///
68 | /// Gets the StructureData for a given path in a given mod (or absolute path if fullPath is true).
69 | /// Will attempt to retrieve from the StructureData cache first if possible before doing I/O
70 | ///
71 | /// The path to search for the structure file. If it is in a mod, it should not include the mods name (for example, it should be "structures/coolHouse", not "CoolHouseMod/structures/coolHouse")
72 | /// The mod to search for the structure file in
73 | /// If the search path starts at the root of your file system(true) or the provided mod(false). This should usually be false.
74 | /// The StructureData associated with the desired file
75 | ///
76 | public static StructureData GetStructureData(string path, Mod mod, bool fullPath = false)
77 | {
78 | if (!path.EndsWith(".shstruct"))
79 | path += ".shstruct";
80 |
81 | string key = Path.Combine(mod.Name, path);
82 |
83 | if (StructureCache.ContainsKey(key))
84 | return StructureCache[key];
85 |
86 | if (fullPath && !File.Exists(path))
87 | throw new FileNotFoundException(ErrorHelper.GenerateErrorMessage($"A file at the path {path} does not exist! (did you mean to not pass true to fullPath? You should pass false if the file is in your mod!)", mod));
88 |
89 | if (!fullPath && !mod.FileExists(path))
90 | throw new FileNotFoundException(ErrorHelper.GenerateErrorMessage($"A file at the path {path} in mod {mod.DisplayName}({mod.Name}) does not exist! Did you accidentally include your mods name in the path? (for example, you should not pass 'MyMod/Structures/House', but rather 'Structures/House')", mod));
91 |
92 | using Stream stream = fullPath ? File.OpenRead(path) : mod.GetFileStream(path);
93 | using var compressionStream = new GZipStream(stream, CompressionMode.Decompress);
94 | using BinaryReader reader = new(compressionStream);
95 | StructureCache.Add(key, StructureData.FromStream(reader));
96 | return StructureCache[key];
97 | }
98 |
99 | ///
100 | /// This method generates a structure from a structure file.
101 | ///
102 | /// The path to search for the structure file. If it is in a mod, it should not include the mods name (for example, it should be "structures/coolHouse", not "CoolHouseMod/structures/coolHouse")
103 | /// The position in the world to place the top-left of the structure, in tile coordinates.
104 | /// The mod to search for the structure file in
105 | /// If the search path starts at the root of your file system(true) or the provided mod(false). This should usually be false.
106 | /// If the structure should repsect the normal behavior of null tiles or not. This should never be true if you're using the mod as a dll reference.
107 | /// Allows you to pass flags for special generation behavior. See
108 | public static void GenerateStructure(string path, Point16 pos, Mod mod, bool fullPath = false, bool ignoreNull = false, GenFlags flags = GenFlags.None)
109 | {
110 | StructureData data = GetStructureData(path, mod, fullPath);
111 |
112 | if (!IsInBounds(data, pos))
113 | throw new ArgumentException(ErrorHelper.GenerateErrorMessage($"Attempted to generate a structure out of bounds! {pos} is not a valid position for the structure at {path}. Mods are responsible for bounds-checking their own structures. You can fetch dimension data using GetDimensions or GetMultistructureDimensions.", mod));
114 |
115 | GenerateFromData(data, pos, ignoreNull, flags);
116 | }
117 |
118 | ///
119 | /// Directly generates a given StructureData into the world. Use this directly if you have
120 | /// a StructureData object in memory you want to generate from.
121 | ///
122 | /// The StructureData to generate
123 | /// The position in the world to place the top-left of the structure, in tile coordinates.
124 | /// If the structure should repsect the normal behavior of null tiles or not. This should never be true if you're using the mod as a dll reference.
125 | /// Allows you to pass flags for special generation behavior. See
126 | public static void GenerateFromData(StructureData data, Point16 pos, bool ignoreNull = false, GenFlags flags = GenFlags.None)
127 | {
128 | if (!IsInBounds(data, pos))
129 | throw new ArgumentException(ErrorHelper.GenerateErrorMessage($"Attempted to generate a structure out of bounds! {pos} is not a valid position for the structure. Mods are responsible for bounds-checking their own structures. You can fetch dimension data using GetDimensions or GetMultistructureDimensions.", null));
130 |
131 | for (int k = 0; k < data.width; k++)
132 | {
133 | if (!data.slowColumns[k] || ignoreNull)
134 | {
135 | data.ExportDataColumn(pos.X + k, pos.Y, k, null);
136 | data.ExportDataColumn(pos.X + k, pos.Y, k, null);
137 | data.ExportDataColumn(pos.X + k, pos.Y, k, null);
138 | data.ExportDataColumn(pos.X + k, pos.Y, k, null);
139 | data.ExportDataColumn(pos.X + k, pos.Y, k, null);
140 | }
141 | else
142 | {
143 | data.ExportDataColumnSlow(pos.X + k, pos.Y, k, null);
144 | data.ExportDataColumnSlow(pos.X + k, pos.Y, k, null);
145 | data.ExportDataColumnSlow(pos.X + k, pos.Y, k, null);
146 | data.ExportDataColumnSlow(pos.X + k, pos.Y, k, null);
147 | data.ExportDataColumnSlow(pos.X + k, pos.Y, k, null);
148 | }
149 | }
150 |
151 | // replace nulls from the placeholder null value to the current null ID
152 | if (ignoreNull)
153 | {
154 | for(int x = 0; x < data.width; x++)
155 | {
156 | for(int y = 0; y < data.height; y++)
157 | {
158 | Tile tile = Main.tile[pos.X + x, pos.Y + y];
159 |
160 | if (tile.TileType == StructureHelper.NULL_IDENTIFIER)
161 | tile.TileType = StructureHelper.NullTileID;
162 |
163 | if (tile.WallType == StructureHelper.NULL_IDENTIFIER)
164 | tile.WallType = StructureHelper.NullWallID;
165 | }
166 | }
167 | }
168 |
169 | // Handle the NBT data if this structure is marked as having any
170 | if (data.containsNbt)
171 | {
172 | for (int k = 0; k < data.nbtData.Count; k++)
173 | {
174 | StructureNBTEntry thisNbt = data.nbtData[k];
175 | thisNbt.OnGenerate(pos + new Point16(thisNbt.x, thisNbt.y), ignoreNull, flags);
176 | }
177 | }
178 |
179 | // If we're not in worldgen, we should call frame tile to clean up the edges
180 | // and then sync if we're in multiplayer
181 | if (!WorldGen.generatingWorld)
182 | {
183 | for (int x = 0; x < data.width; x++)
184 | {
185 | WorldGen.TileFrame(pos.X + x, pos.Y);
186 | WorldGen.TileFrame(pos.X + x, pos.Y + data.height);
187 |
188 | WorldGen.SquareWallFrame(pos.X + x, pos.Y);
189 | WorldGen.SquareWallFrame(pos.X + x, pos.Y + data.height);
190 | }
191 |
192 | for (int y = 0; y < data.height; y++)
193 | {
194 | WorldGen.TileFrame(pos.X, pos.Y + y);
195 | WorldGen.TileFrame(pos.X + data.width, pos.Y + y);
196 |
197 | WorldGen.SquareWallFrame(pos.X, pos.Y + y);
198 | WorldGen.SquareWallFrame(pos.X + data.width, pos.Y + y);
199 | }
200 |
201 | if (Main.netMode != NetmodeID.SinglePlayer)
202 | NetMessage.SendTileSquare(-1, pos.X, pos.Y, data.width, data.height);
203 | }
204 | }
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/API/Legacy/LegacySaver.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using Terraria.DataStructures;
4 | using Terraria.ID;
5 | using Terraria.ModLoader.IO;
6 |
7 | namespace StructureHelper.API.Legacy
8 | {
9 | ///
10 | /// A static class providing utilities for saving structures.
11 | ///
12 | public static class LegacySaver
13 | {
14 | ///
15 | /// Saves a given region of the world as a structure file
16 | ///
17 | /// The region of the world to save, in tile coordinates
18 | /// The name of the file to save. Automatically defaults to a file named after the date in the SavedStructures folder
19 | public static void SaveToFile(Rectangle target, string targetPath = null, string name = "unnamed structure")
20 | {
21 | if (name == "")
22 | name = "unnamed structure";
23 |
24 | string path = ModLoader.ModPath.Replace("Mods", "SavedStructures");
25 |
26 | if (!Directory.Exists(path))
27 | Directory.CreateDirectory(path);
28 |
29 | string thisPath = targetPath ?? Path.Combine(path, name);
30 |
31 | int counter = 2;
32 | while (File.Exists(thisPath))
33 | {
34 | thisPath = targetPath ?? Path.Combine(path, name) + $"({counter})";
35 | counter++;
36 | }
37 |
38 | Main.NewText("Structure saved as " + thisPath, Color.Yellow);
39 | FileStream stream = File.Create(thisPath);
40 | stream.Close();
41 |
42 | TagCompound tag = SaveStructure(target);
43 | TagIO.ToFile(tag, thisPath);
44 | }
45 |
46 | ///
47 | /// Saves a given list of TagCompounds together as a multistructure file
48 | ///
49 | /// The tags to save
50 | /// The name of the file to save. Automatically defaults to a file named after the date in the SavedStructures folder
51 | public static void SaveMultistructureToFile(ref List toSave, string targetPath = null, string name = "unnamed multistructure")
52 | {
53 | string path = ModLoader.ModPath.Replace("Mods", "SavedStructures");
54 |
55 | if (!Directory.Exists(path))
56 | Directory.CreateDirectory(path);
57 |
58 | string thisPath = targetPath ?? Path.Combine(path, name);
59 |
60 | int counter = 2;
61 | while (File.Exists(thisPath))
62 | {
63 | thisPath = targetPath ?? Path.Combine(path, name) + $"({counter})";
64 | counter++;
65 | }
66 |
67 | Main.NewText("Structure saved as " + thisPath, Color.Yellow);
68 | FileStream stream = File.Create(thisPath);
69 | stream.Close();
70 |
71 | var tag = new TagCompound
72 | {
73 | { "Structures", toSave },
74 | { "Version", StructureHelper.Instance.Version.ToString() }
75 | };
76 |
77 | TagIO.ToFile(tag, thisPath);
78 |
79 | toSave.Clear();
80 | }
81 |
82 | ///
83 | /// Transforms a region of the world into a structure TagCompound. Must be called in an unsafe context!
84 | ///
85 | /// The region to transform
86 | /// The TagCompound that can be saved to a structure file
87 | public unsafe static TagCompound SaveStructure(Rectangle target)
88 | {
89 | StructureHelper instance = StructureHelper.Instance;
90 |
91 | var tag = new TagCompound
92 | {
93 | { "Version", instance?.Version.ToString() ?? "Unknown" },
94 | { "Width", target.Width },
95 | { "Height", target.Height }
96 | };
97 |
98 | var data = new List();
99 | for (int x = target.X; x <= target.X + target.Width; x++)
100 | {
101 | for (int y = target.Y; y <= target.Y + target.Height; y++)
102 | {
103 | Tile tile = Framing.GetTileSafely(x, y);
104 | string tileName;
105 | string wallName;
106 | string teName;
107 |
108 | if (tile.TileType >= TileID.Count)
109 | tileName = ModContent.GetModTile(tile.TileType).Mod.Name + " " + ModContent.GetModTile(tile.TileType).Name;
110 | else
111 | tileName = tile.TileType.ToString();
112 |
113 | if (tile.WallType >= WallID.Count)
114 | wallName = ModContent.GetModWall(tile.WallType).Mod.Name + " " + ModContent.GetModWall(tile.WallType).Name;
115 | else
116 | wallName = tile.WallType.ToString();
117 |
118 | TileEntity teTarget = null; //grabbing TE data
119 | var entityTag = new TagCompound();
120 |
121 | if (TileEntity.ByPosition.ContainsKey(new Point16(x, y)))
122 | teTarget = TileEntity.ByPosition[new Point16(x, y)];
123 |
124 | if (teTarget != null)
125 | {
126 | var modTileEntity = teTarget as ModTileEntity;
127 |
128 | if (modTileEntity != null)
129 | teName = modTileEntity.Mod.Name + " " + modTileEntity.Name;
130 | else
131 | {
132 | teName = teTarget.type.ToString();
133 | }
134 |
135 | teTarget.SaveData(entityTag);
136 | }
137 | else
138 | {
139 | teName = "";
140 | }
141 |
142 | int wallWireData;
143 | short packedLiquidData;
144 | byte brightInvisibleData;
145 |
146 | fixed (void* ptr = &tile.Get())
147 | {
148 | int* intPtr = (int*)ptr;
149 | intPtr++;
150 |
151 | wallWireData = *intPtr;
152 | }
153 |
154 | fixed (void* ptr = &tile.Get())
155 | {
156 | short* shortPtr = (short*)ptr;
157 |
158 | packedLiquidData = *shortPtr;
159 | }
160 |
161 | fixed (void* ptr = &tile.Get())
162 | {
163 | byte* bytePtr = (byte*)ptr;
164 |
165 | brightInvisibleData = *bytePtr;
166 | }
167 |
168 | data.Add(
169 | new TileSaveData(
170 | tileName,
171 | wallName,
172 | tile.TileFrameX,
173 | tile.TileFrameY,
174 | wallWireData,
175 | packedLiquidData,
176 | brightInvisibleData,
177 | teName,
178 | entityTag
179 | ));
180 | }
181 | }
182 |
183 | tag.Add("TileData", data);
184 | return tag;
185 | }
186 | }
187 | }
--------------------------------------------------------------------------------
/API/MultiStructureGenerator.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.Helpers;
2 | using StructureHelper.Models;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.IO.Compression;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 | using Terraria.DataStructures;
11 | using Terraria.ModLoader;
12 |
13 | namespace StructureHelper.API
14 | {
15 | ///
16 | /// In this class you will find various utilities related to generating multi structures and getting important
17 | /// information from them such as their dimensions or structure count.
18 | ///
19 | public class MultiStructureGenerator
20 | {
21 | internal static readonly Dictionary MultiStructureCache = [];
22 |
23 | ///
24 | /// Helper to check bounds on a generation call. You can also use this to check the bounds
25 | /// of your own structure
26 | ///
27 | /// The MultiStructureData to check
28 | /// The position to check from, this would be the top-left of the structure.
29 | /// The index in the multistructure to check the dimensions of.
30 | /// If the structure is in bounds or not
31 | public static bool IsInBounds(MultiStructureData data, int index, Point16 pos)
32 | {
33 | if (index < 0 || index >= data.count)
34 | throw new IndexOutOfRangeException(ErrorHelper.GenerateErrorMessage($"Index {index} is out of bounds for a multistructure! Max index is {data.count - 1}. You can use GetStructureCount to check the max index if needed.", null));
35 |
36 | StructureData sData = data.structures[index];
37 |
38 | if (pos.X < 0 || pos.X + sData.width >= Main.maxTilesX || pos.Y < 0 || pos.Y + sData.height >= Main.maxTilesY)
39 | return false;
40 |
41 | return true;
42 | }
43 |
44 | ///
45 | /// Helper to check bounds on a generation call. You can also use this to check the bounds
46 | /// of your own structure
47 | ///
48 | /// The path to search for the multi structure file
49 | /// The mod to search for the multi structure file in
50 | /// The position to check from, this would be the top-left of the structure.
51 | /// The index in the multistructure to check the dimensions of.
52 | /// If the search path starts at the root of your file system(true) or the provided mod(false). This should usually be false.
53 | /// If the structure is in bounds or not
54 | public static bool IsInBounds(string path, Mod mod, int index, Point16 pos, bool fullPath = false)
55 | {
56 | MultiStructureData data = GetMultiStructureData(path, mod, fullPath);
57 |
58 | if (index < 0 || index >= data.count)
59 | throw new IndexOutOfRangeException(ErrorHelper.GenerateErrorMessage($"Index {index} is out of bounds for the multistructure at {path}! Max index is {data.count - 1}. You can use GetStructureCount to check the max index if needed.", mod));
60 |
61 | StructureData sData = data.structures[index];
62 |
63 | if (pos.X < 0 || pos.X + sData.width >= Main.maxTilesX || pos.Y < 0 || pos.Y + sData.height >= Main.maxTilesY)
64 | return false;
65 |
66 | return true;
67 | }
68 |
69 | ///
70 | /// Gets the dimensions (width and height) of a structure in a multistructure.
71 | ///
72 | /// The path to search for the structure file. If it is in a mod, it should not include the mods name (for example, it should be "structures/coolHouse", not "CoolHouseMod/structures/coolHouse")
73 | /// The mod to search for the structure file in
74 | /// The index in the multistructure to check the dimensions of.
75 | /// If the search path starts at the root of your file system(true) or the provided mod(false). This should usually be false.
76 | ///
77 | public static Point16 GetStructureDimensions(string path, Mod mod, int index, bool fullPath = false)
78 | {
79 | MultiStructureData data = GetMultiStructureData(path, mod, fullPath);
80 |
81 | if (index < 0 || index >= data.count)
82 | throw new IndexOutOfRangeException(ErrorHelper.GenerateErrorMessage($"Index {index} is out of bounds for the multistructure at {path}! Max index is {data.count - 1}. You can use GetStructureCount to check the max index if needed.", mod));
83 |
84 | StructureData sData = data.structures[index];
85 |
86 | return new Point16(sData.width, sData.height);
87 | }
88 |
89 | ///
90 | /// Gets the amount of structures inside of a multi structure file
91 | ///
92 | /// The path to search for the structure file. If it is in a mod, it should not include the mods name (for example, it should be "structures/coolHouse", not "CoolHouseMod/structures/coolHouse")
93 | /// The mod to search for the structure file in
94 | /// If the search path starts at the root of your file system(true) or the provided mod(false). This should usually be false.
95 | ///
96 | public static int GetStructureCount(string path, Mod mod, bool fullPath = false)
97 | {
98 | MultiStructureData data = GetMultiStructureData(path, mod, fullPath);
99 | return data.count;
100 | }
101 |
102 | ///
103 | /// Gets the MultiStructureData for a given path in a given mod (or absolute path if fullPath is true).
104 | /// Will attempt to retrieve from the MultiStructureData cache first if possible before doing I/O
105 | ///
106 | /// The path to search for the multi structure file. If it is in a mod, it should not include the mods name (for example, it should be "structures/coolHouse", not "CoolHouseMod/structures/coolHouse")
107 | /// The mod to search for the multi structure file in
108 | /// If the search path starts at the root of your file system(true) or the provided mod(false). This should usually be false.
109 | /// The MultiStructureData associated with the desired file
110 | ///
111 | public static MultiStructureData GetMultiStructureData(string path, Mod mod, bool fullPath = false)
112 | {
113 | if (!path.EndsWith(".shmstruct"))
114 | path += ".shmstruct";
115 |
116 | string key = Path.Combine(mod.Name, path);
117 |
118 | if (MultiStructureCache.ContainsKey(key))
119 | return MultiStructureCache[key];
120 |
121 | if (fullPath && !File.Exists(path))
122 | throw new FileNotFoundException(ErrorHelper.GenerateErrorMessage($"A file at the path {path} does not exist! (did you mean to not pass true to fullPath? You should pass false if the file is in your mod!)", mod));
123 |
124 | if (!fullPath && !mod.FileExists(path))
125 | throw new FileNotFoundException(ErrorHelper.GenerateErrorMessage($"A file at the path {path} in mod {mod.DisplayName}({mod.Name}) does not exist! Did you accidentally include your mods name in the path? (for example, you should not pass 'MyMod/Structures/House', but rather 'Structures/House')", mod));
126 |
127 | using Stream stream = fullPath ? File.OpenRead(path) : mod.GetFileStream(path);
128 | using var compressionStream = new GZipStream(stream, CompressionMode.Decompress);
129 | using BinaryReader reader = new(compressionStream);
130 | MultiStructureCache.Add(key, MultiStructureData.FromStream(reader));
131 | return MultiStructureCache[key];
132 | }
133 |
134 | ///
135 | /// This method generates a random structure from a multi structure file.
136 | ///
137 | /// The path to search for the structure file. If it is in a mod, it should not include the mods name (for example, it should be "structures/coolHouse", not "CoolHouseMod/structures/coolHouse")
138 | /// The position in the world to place the top-left of the structure, in tile coordinates.
139 | /// The mod to search for the structure file in
140 | /// If the search path starts at the root of your file system(true) or the provided mod(false). This should usually be false.
141 | /// If the structure should repsect the normal behavior of null tiles or not. This should never be true if you're using the mod as a dll reference.
142 | /// Allows you to pass flags for special generation behavior. See
143 | public static void GenerateMultistructureRandom(string path, Point16 pos, Mod mod, bool fullPath = false, bool ignoreNull = false, GenFlags flags = GenFlags.None)
144 | {
145 | MultiStructureData data = GetMultiStructureData(path, mod, fullPath);
146 |
147 | int index;
148 |
149 | if (WorldGen.generatingWorld)
150 | index = WorldGen.genRand.Next(data.count);
151 | else
152 | index = Main.rand.Next(data.count);
153 |
154 | Generator.GenerateFromData(data.structures[index], pos, ignoreNull, flags);
155 | }
156 |
157 | ///
158 | /// Generates a specific structure from a multi structure file
159 | ///
160 | /// The path to search for the structure file. If it is in a mod, it should not include the mods name (for example, it should be "structures/coolHouse", not "CoolHouseMod/structures/coolHouse")
161 | /// The index of the structure to generate out of the multistructure
162 | /// The position in the world to place the top-left of the structure, in tile coordinates.
163 | /// The mod to search for the structure file in
164 | /// If the search path starts at the root of your file system(true) or the provided mod(false). This should usually be false.
165 | /// If the structure should repsect the normal behavior of null tiles or not. This should never be true if you're using the mod as a dll reference.
166 | /// Allows you to pass flags for special generation behavior. See
167 | ///
168 | public static void GenerateMultistructureSpecific(string path, int index, Point16 pos, Mod mod, bool fullPath = false, bool ignoreNull = false, GenFlags flags = GenFlags.None)
169 | {
170 | MultiStructureData data = GetMultiStructureData(path, mod, fullPath);
171 |
172 | if (index < 0 || index >= data.count)
173 | throw new IndexOutOfRangeException(ErrorHelper.GenerateErrorMessage($"Index {index} is out of bounds for the multistructure at {path}! Max index is {data.count - 1}. You can use GetStructureCount to check the max index if needed.", mod));
174 |
175 | Generator.GenerateFromData(data.structures[index], pos, ignoreNull, flags);
176 | }
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/API/Saver.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.Helpers;
2 | using StructureHelper.Models;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.IO.Compression;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace StructureHelper.API
12 | {
13 | ///
14 | /// In this class you will find utilities for saving regions of the world into structure data,
15 | /// and then saving that data into files if needed.
16 | ///
17 | public class Saver
18 | {
19 | ///
20 | /// Saves a region of the world into a StructureData object.
21 | ///
22 | /// The leftmost point of the region
23 | /// The topmost point of the region
24 | /// The width of the region
25 | /// The height of the region
26 | /// a StructureData object representing that region of the world
27 | /// If any part of the region is outside of the world
28 | public static StructureData SaveToStructureData(int x, int y, int width, int height)
29 | {
30 | if (x < 0 || x + width > Main.maxTilesX || y < 0 || y + height > Main.maxTilesY)
31 | throw new ArgumentException(ErrorHelper.GenerateErrorMessage("Attempted to save structure data for a region outside of the world.", null));
32 |
33 | return StructureData.FromWorld(x, y, width, height);
34 | }
35 |
36 | ///
37 | /// Saves a given StructureData to a file, the location is by the target path, and with the given name.
38 | ///
39 | /// The StructureData to save
40 | /// The path to the directory save the file into
41 | /// The name of the file, NOT including the extension
42 | public static void SaveToFile(StructureData data, string targetPath = null, string name = "unnamed structure")
43 | {
44 | if (string.IsNullOrEmpty(name))
45 | name = "unnamed structure";
46 |
47 | string path = ModLoader.ModPath.Replace("Mods", "SavedStructures");
48 |
49 | if (!Directory.Exists(path))
50 | Directory.CreateDirectory(path);
51 |
52 | string thisPath = targetPath ?? Path.Combine(path, name);
53 |
54 | int counter = 2;
55 | while (File.Exists(thisPath + ".shstruct"))
56 | {
57 | thisPath = (targetPath ?? Path.Combine(path, name)) + $"({counter})";
58 | counter++;
59 | }
60 |
61 | thisPath += ".shstruct";
62 |
63 | using FileStream fileStream = File.Create(thisPath);
64 | using var compressionStream = new GZipStream(fileStream, CompressionLevel.Optimal);
65 | using var writer = new BinaryWriter(compressionStream);
66 | data.Serialize(writer);
67 |
68 | Main.NewText("Structure saved as " + thisPath, Color.Yellow);
69 | }
70 |
71 | ///
72 | /// Saves a given MultiStructureData to a file, the location is by the target path, and with the given name.
73 | ///
74 | /// The MultiStructureData to save
75 | /// The path to the directory save the file into
76 | /// The name of the file, NOT including the extension
77 | public static void SaveMultistructureToFile(MultiStructureData data, string targetPath = null, string name = "unnamed multistructure")
78 | {
79 | if (string.IsNullOrEmpty(name))
80 | name = "unnamed structure";
81 |
82 | string path = ModLoader.ModPath.Replace("Mods", "SavedStructures");
83 |
84 | if (!Directory.Exists(path))
85 | Directory.CreateDirectory(path);
86 |
87 | string thisPath = targetPath ?? Path.Combine(path, name);
88 |
89 | int counter = 2;
90 | while (File.Exists(thisPath + ".shmstruct"))
91 | {
92 | thisPath = (targetPath ?? Path.Combine(path, name)) + $"({counter})";
93 | counter++;
94 | }
95 |
96 | thisPath += ".shmstruct";
97 |
98 | using FileStream fileStream = File.Create(thisPath);
99 | using var compressionStream = new GZipStream(fileStream, CompressionLevel.Optimal);
100 | using var writer = new BinaryWriter(compressionStream);
101 | data.Serialize(writer);
102 |
103 | Main.NewText("MultiStructure saved as " + thisPath, Color.Yellow);
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/AssGenConfig.txt:
--------------------------------------------------------------------------------
1 | AssetRoot:StructureHelper\Assets
--------------------------------------------------------------------------------
/Assets/GUI/Box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/GUI/Box.png
--------------------------------------------------------------------------------
/Assets/GUI/Cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/GUI/Cross.png
--------------------------------------------------------------------------------
/Assets/GUI/Down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/GUI/Down.png
--------------------------------------------------------------------------------
/Assets/GUI/DownLarge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/GUI/DownLarge.png
--------------------------------------------------------------------------------
/Assets/GUI/Eye.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/GUI/Eye.png
--------------------------------------------------------------------------------
/Assets/GUI/EyeClosed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/GUI/EyeClosed.png
--------------------------------------------------------------------------------
/Assets/GUI/Gradient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/GUI/Gradient.png
--------------------------------------------------------------------------------
/Assets/GUI/Null.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/GUI/Null.png
--------------------------------------------------------------------------------
/Assets/GUI/PlusB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/GUI/PlusB.png
--------------------------------------------------------------------------------
/Assets/GUI/PlusG.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/GUI/PlusG.png
--------------------------------------------------------------------------------
/Assets/GUI/PlusP.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/GUI/PlusP.png
--------------------------------------------------------------------------------
/Assets/GUI/PlusR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/GUI/PlusR.png
--------------------------------------------------------------------------------
/Assets/GUI/Refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/GUI/Refresh.png
--------------------------------------------------------------------------------
/Assets/GUI/Up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/GUI/Up.png
--------------------------------------------------------------------------------
/Assets/GUI/UpLarge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/GUI/UpLarge.png
--------------------------------------------------------------------------------
/Assets/Items/ChestWand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/Items/ChestWand.png
--------------------------------------------------------------------------------
/Assets/Items/MultistructureWand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/Items/MultistructureWand.png
--------------------------------------------------------------------------------
/Assets/Items/PortingWand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/Items/PortingWand.png
--------------------------------------------------------------------------------
/Assets/Items/StructureWand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/Items/StructureWand.png
--------------------------------------------------------------------------------
/Assets/Items/TestWand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/Items/TestWand.png
--------------------------------------------------------------------------------
/Assets/Tiles/NullBlock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/Tiles/NullBlock.png
--------------------------------------------------------------------------------
/Assets/Tiles/NullBlockItem.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/Tiles/NullBlockItem.png
--------------------------------------------------------------------------------
/Assets/Tiles/NullTileAndWallPlacer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/Tiles/NullTileAndWallPlacer.png
--------------------------------------------------------------------------------
/Assets/Tiles/NullWall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/Tiles/NullWall.png
--------------------------------------------------------------------------------
/Assets/Tiles/NullWallItem.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/Tiles/NullWallItem.png
--------------------------------------------------------------------------------
/Assets/box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/box.png
--------------------------------------------------------------------------------
/Assets/corner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/Assets/corner.png
--------------------------------------------------------------------------------
/ChestHelper/ChestEntity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using Terraria.ID;
5 | using Terraria.ModLoader.IO;
6 |
7 | namespace StructureHelper.ChestHelper
8 | {
9 | class ChestEntity : ModTileEntity
10 | {
11 | public List rules = [];
12 |
13 | public void SaveChestRulesFile()
14 | {
15 | string path = ModLoader.ModPath.Replace("Mods", "SavedStructures");
16 |
17 | if (!Directory.Exists(path))
18 | Directory.CreateDirectory(path);
19 |
20 | string thisPath = Path.Combine(path, "SavedChest_" + DateTime.Now.ToString("d-M-y----H-m-s-f"));
21 | Main.NewText("Chest data saved as " + thisPath, Color.Yellow);
22 | FileStream stream = File.Create(thisPath);
23 | stream.Close();
24 |
25 | TagCompound tag = SaveChestRules();
26 | TagIO.ToFile(tag, thisPath);
27 | }
28 |
29 | public TagCompound SaveChestRules()
30 | {
31 | var tag = new TagCompound
32 | {
33 | { "Count", rules.Count }
34 | };
35 |
36 | for (int k = 0; k < rules.Count; k++)
37 | {
38 | tag.Add("Rule" + k, rules[k].Serizlize());
39 | }
40 |
41 | return tag;
42 | }
43 |
44 | public static List LoadChestRules(TagCompound tag)
45 | {
46 | var rules = new List();
47 | int count = tag.GetInt("Count");
48 |
49 | for (int k = 0; k < count; k++)
50 | {
51 | rules.Add(ChestRule.Deserialize(tag.GetCompound("Rule" + k)));
52 | }
53 |
54 | return rules;
55 | }
56 |
57 | public static void SetChest(Chest chest, List rules)
58 | {
59 | int index = 0;
60 | rules.ForEach(n => n.PlaceItems(chest, ref index));
61 | }
62 |
63 | public override void Update()
64 | {
65 | Dust.NewDustPerfect(Position.ToVector2() * 16 + Vector2.UnitY * 8 + Vector2.One.RotatedByRandom(6.28f) * 6, 111, Vector2.Zero, 0, default, 0.5f);
66 | }
67 |
68 | public override void SaveData(TagCompound tag)
69 | {
70 | tag.Add("Count", rules.Count);
71 |
72 | for (int k = 0; k < rules.Count; k++)
73 | {
74 | tag.Add("Rule" + k, rules[k].Serizlize());
75 | }
76 | }
77 |
78 | public override void LoadData(TagCompound tag)
79 | {
80 | rules = LoadChestRules(tag);
81 | }
82 |
83 | public override bool IsTileValidForEntity(int i, int j)
84 | {
85 | Tile tile = Framing.GetTileSafely(i, j);
86 | return tile.TileType == TileID.Containers || TileID.Sets.BasicChest[tile.TileType];
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/ChestHelper/ChestRule.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Terraria.ModLoader.IO;
3 |
4 | namespace StructureHelper.ChestHelper
5 | {
6 | class ChestRule
7 | {
8 | public List pool = [];
9 |
10 | public virtual bool UsesWeight => false;
11 |
12 | public virtual string Name => "Unknown Rule";
13 |
14 | public virtual string Tooltip => "Probably a bug! Report me!";
15 |
16 | public Loot AddItem(Item item)
17 | {
18 | var loot = new Loot(item.Clone(), 1);
19 | pool.Add(loot);
20 |
21 | return loot;
22 | }
23 |
24 | public void RemoveItem(Loot loot)
25 | {
26 | pool.Remove(loot);
27 | }
28 |
29 | public virtual void PlaceItems(Chest chest, ref int nextIndex) { }
30 |
31 | public virtual TagCompound Serizlize()
32 | {
33 | return null;
34 | }
35 |
36 | public static ChestRule Deserialize(TagCompound tag)
37 | {
38 | string str = tag.GetString("Type");
39 |
40 | ChestRule rule = str switch
41 | {
42 | "Guaranteed" => ChestRuleGuaranteed.Deserialize(tag),
43 | "Chance" => ChestRuleChance.Deserialize(tag),
44 | "Pool" => ChestRulePool.Deserialize(tag),
45 | "PoolChance" => ChestRulePoolChance.Deserialize(tag),
46 | _ => null,
47 | };
48 |
49 | return rule;
50 | }
51 |
52 | public TagCompound SerializePool()
53 | {
54 | var tag = new TagCompound
55 | {
56 | { "Count", pool.Count }
57 | };
58 |
59 | for (int k = 0; k < pool.Count; k++)
60 | {
61 | tag.Add("Pool" + k, pool[k].Serialize());
62 | }
63 |
64 | return tag;
65 | }
66 |
67 | public static List DeserializePool(TagCompound tag)
68 | {
69 | var loot = new List();
70 | int count = tag.GetInt("Count");
71 |
72 | for (int k = 0; k < count; k++)
73 | {
74 | loot.Add(Loot.Deserialze(tag.GetCompound("Pool" + k)));
75 | }
76 |
77 | return loot;
78 | }
79 |
80 | public virtual ChestRule Clone()
81 | {
82 | var clone = new ChestRule();
83 |
84 | for (int k = 0; k < pool.Count; k++)
85 | {
86 | clone.pool.Add(pool[k].Clone());
87 | }
88 |
89 | return clone;
90 | }
91 | }
92 |
93 | class Loot
94 | {
95 | public Item givenItem;
96 | public int min;
97 | public int max;
98 | public int weight;
99 |
100 | public Loot(Item item, int min, int max = 0, int weight = 1)
101 | {
102 | this.min = min;
103 | this.max = max == 0 ? min : max;
104 | this.weight = weight;
105 |
106 | Item newItem = item.Clone();
107 | newItem.stack = 1;
108 | givenItem = newItem;
109 |
110 | }
111 |
112 | public Item GetLoot()
113 | {
114 | Item item = givenItem.Clone();
115 | item.stack = WorldGen.genRand.Next(min, max);
116 | return item;
117 | }
118 |
119 | public TagCompound Serialize()
120 | {
121 | var tag = new TagCompound
122 | {
123 | { "Item", givenItem },
124 | { "Min", min },
125 | { "Max", max },
126 | { "Weight", weight}
127 | };
128 | return tag;
129 | }
130 |
131 | public static Loot Deserialze(TagCompound tag)
132 | {
133 | return new Loot(
134 | tag.Get- ("Item"),
135 | tag.GetInt("Min"),
136 | tag.GetInt("Max"),
137 | tag.GetInt("Weight"));
138 | }
139 |
140 | public Loot Clone()
141 | {
142 | return new Loot(givenItem.Clone(), min, max, weight);
143 | }
144 | }
145 | }
--------------------------------------------------------------------------------
/ChestHelper/ChestRuleChance.cs:
--------------------------------------------------------------------------------
1 | using Terraria.ModLoader.IO;
2 |
3 | namespace StructureHelper.ChestHelper
4 | {
5 | class ChestRuleChance : ChestRule
6 | {
7 | ///
8 | /// the chance for any individual item from this pool to generate, from 0 to 1. (0 = 0%, 1 = 100%) If you want to generate X items from the pool, use ChestRulePool instead. If you want multiple different chances, add another rule of this type.
9 | ///
10 | public float chance = 0;
11 |
12 | public override string Name => "Chance Rule";
13 |
14 | public override string Tooltip => "Attempts to generate all items in the rule, \nwith a configurable chance to generate each.\nItems are attempted in the order they appear here";
15 |
16 | public override void PlaceItems(Chest chest, ref int nextIndex)
17 | {
18 | if (nextIndex >= 40)
19 | return;
20 |
21 | for (int k = 0; k < pool.Count; k++)
22 | {
23 | if (WorldGen.genRand.NextFloat(1) <= chance)
24 | {
25 | chest.item[nextIndex] = pool[k].GetLoot();
26 | nextIndex++;
27 | }
28 | }
29 | }
30 |
31 | public override TagCompound Serizlize()
32 | {
33 | var tag = new TagCompound()
34 | {
35 | {"Type", "Chance"},
36 | {"Chance", chance},
37 | {"Pool", SerializePool()}
38 | };
39 |
40 | return tag;
41 | }
42 |
43 | public static ChestRule Deserialize(TagCompound tag)
44 | {
45 | var rule = new ChestRuleChance
46 | {
47 | chance = tag.GetFloat("Chance"),
48 | pool = DeserializePool(tag.GetCompound("Pool"))
49 | };
50 |
51 | return rule;
52 | }
53 |
54 | public override ChestRule Clone()
55 | {
56 | var clone = new ChestRuleChance();
57 |
58 | for (int k = 0; k < pool.Count; k++)
59 | {
60 | clone.pool.Add(pool[k].Clone());
61 | }
62 |
63 | clone.chance = chance;
64 |
65 | return clone;
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/ChestHelper/ChestRuleGuaranteed.cs:
--------------------------------------------------------------------------------
1 | using Terraria.ModLoader.IO;
2 |
3 | namespace StructureHelper.ChestHelper
4 | {
5 | class ChestRuleGuaranteed : ChestRule
6 | {
7 | public override string Name => "Guaranteed Rule";
8 |
9 | public override string Tooltip => "Always generates every item in the rule\nItems are generated in the order they appear here";
10 |
11 | public override void PlaceItems(Chest chest, ref int nextIndex)
12 | {
13 | if (nextIndex >= 40)
14 | return;
15 |
16 | for (int k = 0; k < pool.Count; k++)
17 | {
18 | if (nextIndex >= 40)
19 | return;
20 |
21 | chest.item[nextIndex] = pool[k].GetLoot();
22 | nextIndex++;
23 | }
24 | }
25 |
26 | public override TagCompound Serizlize()
27 | {
28 | var tag = new TagCompound()
29 | {
30 | {"Type", "Guaranteed"},
31 | {"Pool", SerializePool()}
32 | };
33 |
34 | return tag;
35 | }
36 |
37 | public static ChestRule Deserialize(TagCompound tag)
38 | {
39 | var rule = new ChestRuleGuaranteed
40 | {
41 | pool = DeserializePool(tag.GetCompound("Pool"))
42 | };
43 |
44 | return rule;
45 | }
46 |
47 | public override ChestRule Clone()
48 | {
49 | var clone = new ChestRuleGuaranteed();
50 |
51 | for (int k = 0; k < pool.Count; k++)
52 | {
53 | clone.pool.Add(pool[k].Clone());
54 | }
55 |
56 | return clone;
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/ChestHelper/ChestRulePool.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Terraria.ModLoader.IO;
3 |
4 | namespace StructureHelper.ChestHelper
5 | {
6 | class ChestRulePool : ChestRule
7 | {
8 | ///
9 | /// How many items from the pool, picked at random, should be placed in the chest.
10 | ///
11 | public int itemsToGenerate;
12 |
13 | public override bool UsesWeight => true;
14 |
15 | public override string Name => "Pool Rule";
16 |
17 | public override string Tooltip => "Generates a configurable amount of items \nrandomly selected from the rule.\nCan make use of weight.";
18 |
19 | public override void PlaceItems(Chest chest, ref int nextIndex)
20 | {
21 | if (nextIndex >= 40)
22 | return;
23 |
24 | List toLoot = pool;
25 |
26 | for (int k = 0; k < itemsToGenerate; k++)
27 | {
28 | if (nextIndex >= 40)
29 | return;
30 |
31 | int maxWeight = 1;
32 |
33 | foreach (Loot loot in toLoot)
34 | maxWeight += loot.weight;
35 |
36 | int selection = Main.rand.Next(maxWeight);
37 | int weightTotal = 0;
38 | Loot selectedLoot = null;
39 |
40 | for (int i = 0; i < toLoot.Count; i++)
41 | {
42 | weightTotal += toLoot[i].weight;
43 |
44 | if (selection < weightTotal + 1)
45 | {
46 | selectedLoot = toLoot[i];
47 | toLoot.Remove(selectedLoot);
48 | break;
49 | }
50 | }
51 |
52 | chest.item[nextIndex] = selectedLoot?.GetLoot();
53 | nextIndex++;
54 | }
55 | }
56 |
57 | public override TagCompound Serizlize()
58 | {
59 | var tag = new TagCompound()
60 | {
61 | {"Type", "Pool"},
62 | {"ToGenerate", itemsToGenerate},
63 | {"Pool", SerializePool()}
64 | };
65 |
66 | return tag;
67 | }
68 |
69 | public static ChestRule Deserialize(TagCompound tag)
70 | {
71 | var rule = new ChestRulePool
72 | {
73 | itemsToGenerate = tag.GetInt("ToGenerate"),
74 | pool = DeserializePool(tag.GetCompound("Pool"))
75 | };
76 |
77 | return rule;
78 | }
79 |
80 | public override ChestRule Clone()
81 | {
82 | var clone = new ChestRulePool();
83 |
84 | for (int k = 0; k < pool.Count; k++)
85 | {
86 | clone.pool.Add(pool[k].Clone());
87 | }
88 |
89 | clone.itemsToGenerate = itemsToGenerate;
90 |
91 | return clone;
92 | }
93 | }
94 | }
--------------------------------------------------------------------------------
/ChestHelper/ChestRulePoolChance.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Terraria.ModLoader.IO;
3 |
4 | namespace StructureHelper.ChestHelper
5 | {
6 | class ChestRulePoolChance : ChestRule
7 | {
8 | ///
9 | /// How many items from the pool, picked at random, should be placed in the chest.
10 | ///
11 | public int itemsToGenerate;
12 |
13 | ///
14 | /// the chance for this item pool to generate at all.
15 | ///
16 | public float chance;
17 |
18 | public override bool UsesWeight => true;
19 |
20 | public override string Name => "Chance + Pool Rule";
21 |
22 | public override string Tooltip => "Has a configurable chance to generate a \nconfigurable amount of items randomly \nselected from the rule. \nCan make use of weight.";
23 |
24 | public override void PlaceItems(Chest chest, ref int nextIndex)
25 | {
26 | if (nextIndex >= 40)
27 | return;
28 |
29 | if (WorldGen.genRand.NextFloat() <= chance)
30 | {
31 | List toLoot = pool;
32 |
33 | for (int k = 0; k < itemsToGenerate; k++)
34 | {
35 | if (nextIndex >= 40)
36 | return;
37 |
38 | int maxWeight = 1;
39 |
40 | foreach (Loot loot in toLoot)
41 | maxWeight += loot.weight;
42 |
43 | int selection = Main.rand.Next(maxWeight);
44 | int weightTotal = 0;
45 | Loot selectedLoot = null;
46 |
47 | for (int i = 0; i < toLoot.Count; i++)
48 | {
49 | weightTotal += toLoot[i].weight;
50 |
51 | if (selection < weightTotal + 1)
52 | {
53 | selectedLoot = toLoot[i];
54 | toLoot.Remove(selectedLoot);
55 | break;
56 | }
57 | }
58 |
59 | chest.item[nextIndex] = selectedLoot?.GetLoot();
60 | nextIndex++;
61 | }
62 | }
63 | }
64 |
65 | public override TagCompound Serizlize()
66 | {
67 | var tag = new TagCompound()
68 | {
69 | {"Type", "PoolChance"},
70 | {"Chance", chance},
71 | {"ToGenerate", itemsToGenerate},
72 | {"Pool", SerializePool()}
73 | };
74 |
75 | return tag;
76 | }
77 |
78 | public static ChestRule Deserialize(TagCompound tag)
79 | {
80 | var rule = new ChestRulePoolChance
81 | {
82 | itemsToGenerate = tag.GetInt("ToGenerate"),
83 | chance = tag.GetFloat("Chance"),
84 | pool = DeserializePool(tag.GetCompound("Pool"))
85 | };
86 |
87 | return rule;
88 | }
89 |
90 | public override ChestRule Clone()
91 | {
92 | var clone = new ChestRulePoolChance();
93 |
94 | for (int k = 0; k < pool.Count; k++)
95 | clone.pool.Add(pool[k].Clone());
96 |
97 | clone.itemsToGenerate = itemsToGenerate;
98 | clone.chance = chance;
99 |
100 | return clone;
101 | }
102 | }
103 | }
--------------------------------------------------------------------------------
/Content/GUI/BoolEditor.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.Helpers;
2 | using System;
3 | using Terraria.UI;
4 |
5 | namespace StructureHelper.Content.GUI
6 | {
7 | internal class BoolEditor : FieldEditor
8 | {
9 | public BoolEditor(string name, Action onValueChanged, bool initialValue, Func listenForUpdate = null, string description = "") : base(70, name, onValueChanged, listenForUpdate, initialValue, description) { }
10 |
11 | public override void SafeClick(UIMouseEvent evt)
12 | {
13 | value = !value;
14 | onValueChanged(value);
15 |
16 | Main.isMouseLeftConsumedByUI = true;
17 | }
18 |
19 | public override void SafeDraw(SpriteBatch sprite)
20 | {
21 | Utils.DrawBorderString(sprite, $"{value}", GetDimensions().Position() + new Vector2(12, 38), Color.White, 0.8f);
22 |
23 | var box = GetDimensions().ToRectangle();
24 | box.Width = 40;
25 | box.Height = 15;
26 | box.Offset(new Point(95, 40));
27 | GUIHelper.DrawBox(sprite, box);
28 |
29 | if (value)
30 | {
31 | box.Width = 15;
32 | box.Offset(new Point(25, 0));
33 | GUIHelper.DrawBox(sprite, box, Color.Yellow);
34 | }
35 | else
36 | {
37 | box.Width = 15;
38 | GUIHelper.DrawBox(sprite, box);
39 | }
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/Content/GUI/ChestGUI/ChestCustomizerState.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.ChestHelper;
2 | using StructureHelper.Core.Loaders.UILoading;
3 | using System.Collections.Generic;
4 | using Terraria.GameContent.UI.Elements;
5 | using Terraria.UI;
6 |
7 | namespace StructureHelper.Content.GUI.ChestGUI
8 | {
9 | class ChestCustomizerState : SmartUIState
10 | {
11 | internal UIList ruleElements = [];
12 | internal UIScrollbar scrollBar = new();
13 |
14 | readonly UIImageButton NewGuaranteed = new(Assets.GUI.PlusR);
15 | readonly UIImageButton NewChance = new(Assets.GUI.PlusG);
16 | readonly UIImageButton NewPool = new(Assets.GUI.PlusP);
17 | readonly UIImageButton NewPoolChance = new(Assets.GUI.PlusB);
18 |
19 | public static UIImageButton closeButton = new(Assets.GUI.Cross);
20 |
21 | public override int InsertionIndex(List layers)
22 | {
23 | return layers.FindIndex(layer => layer.Name.Equals("Vanilla: Mouse Text"));
24 | }
25 |
26 | public override void OnInitialize()
27 | {
28 | ManualGeneratorMenu.SetDims(ruleElements, -200, 0.5f, 0, 0.1f, 400, 0, 0, 0.8f);
29 | ManualGeneratorMenu.SetDims(scrollBar, 232, 0.5f, 0, 0.1f, 32, 0, 0, 0.8f);
30 | ruleElements.SetScrollbar(scrollBar);
31 | ruleElements.ListPadding = 10f;
32 | Append(ruleElements);
33 | Append(scrollBar);
34 |
35 | ManualGeneratorMenu.SetDims(NewGuaranteed, -200, 0.5f, -50, 0.1f, 32, 0, 32, 0);
36 | NewGuaranteed.OnLeftClick += (n, m) => ruleElements.Add(new GuaranteedRuleElement());
37 | Append(NewGuaranteed);
38 |
39 | ManualGeneratorMenu.SetDims(NewChance, -160, 0.5f, -50, 0.1f, 32, 0, 32, 0);
40 | NewChance.OnLeftClick += (n, m) => ruleElements.Add(new ChanceRuleElement());
41 | Append(NewChance);
42 |
43 | ManualGeneratorMenu.SetDims(NewPool, -120, 0.5f, -50, 0.1f, 32, 0, 32, 0);
44 | NewPool.OnLeftClick += (n, m) => ruleElements.Add(new PoolRuleElement());
45 | Append(NewPool);
46 |
47 | ManualGeneratorMenu.SetDims(NewPoolChance, -80, 0.5f, -50, 0.1f, 32, 0, 32, 0);
48 | NewPoolChance.OnLeftClick += (n, m) => ruleElements.Add(new PoolChanceRuleElement());
49 | Append(NewPoolChance);
50 |
51 | ManualGeneratorMenu.SetDims(closeButton, 200 - 32, 0.5f, -50, 0.1f, 32, 0, 32, 0);
52 | closeButton.OnLeftClick += (n, m) => Visible = false;
53 | Append(closeButton);
54 | }
55 |
56 | public bool SetData(ChestEntity entity)
57 | {
58 | entity.rules.Clear();
59 |
60 | if (ruleElements.Count == 0)
61 | {
62 | entity.Kill(entity.Position.X, entity.Position.Y);
63 | return false;
64 | }
65 |
66 | for (int k = 0; k < ruleElements.Count; k++)
67 | {
68 | entity.rules.Add((ruleElements._items[k] as ChestRuleElement).rule.Clone());
69 | }
70 |
71 | return true;
72 | }
73 |
74 | public override void Draw(SpriteBatch spriteBatch)
75 | {
76 | Recalculate();
77 |
78 | var color = new Color(49, 84, 141);
79 |
80 | Helpers.GUIHelper.DrawBox(spriteBatch, NewGuaranteed.GetDimensions().ToRectangle(), NewGuaranteed.IsMouseHovering ? color : color * 0.8f);
81 | Helpers.GUIHelper.DrawBox(spriteBatch, NewChance.GetDimensions().ToRectangle(), NewChance.IsMouseHovering ? color : color * 0.8f);
82 | Helpers.GUIHelper.DrawBox(spriteBatch, NewPool.GetDimensions().ToRectangle(), NewPool.IsMouseHovering ? color : color * 0.8f);
83 | Helpers.GUIHelper.DrawBox(spriteBatch, NewPoolChance.GetDimensions().ToRectangle(), NewPoolChance.IsMouseHovering ? color : color * 0.8f);
84 |
85 | Helpers.GUIHelper.DrawBox(spriteBatch, closeButton.GetDimensions().ToRectangle(), closeButton.IsMouseHovering ? color : color * 0.8f);
86 |
87 | var rect = ruleElements.GetDimensions().ToRectangle();
88 | rect.Inflate(30, 10);
89 | Helpers.GUIHelper.DrawBox(spriteBatch, rect, new Color(20, 40, 60) * 0.8f);
90 |
91 | if (rect.Contains(Main.MouseScreen.ToPoint()))
92 | Main.LocalPlayer.mouseInterface = true;
93 |
94 | if (NewGuaranteed.IsMouseHovering)
95 | {
96 | Tooltip.SetName("New 'Guaranteed' rule");
97 | Tooltip.SetTooltip("Guaranteed rules will always place every item in them in this chest, in the order they appear in the rule.");
98 | }
99 |
100 | if (NewChance.IsMouseHovering)
101 | {
102 | Tooltip.SetName("New 'Chance' rule");
103 | Tooltip.SetTooltip("Chance rules give all items in the rule a chance to generate. You can customize the value for this chance from 0% to 100%");
104 | }
105 |
106 | if (NewPool.IsMouseHovering)
107 | {
108 | Tooltip.SetName("New 'Pool' rule");
109 | Tooltip.SetTooltip("Pool rules will select a customizable amount of items from them and place them in the chest.");
110 | }
111 |
112 | if (NewPoolChance.IsMouseHovering)
113 | {
114 | Tooltip.SetName("New 'Pool + Chance' rule");
115 | Tooltip.SetTooltip("Pool + Chance rules act as a combination of a chance and pool rule -- they have a customizable chance to occur, and if they do, act as a pool rule.");
116 | }
117 |
118 | if (closeButton.IsMouseHovering)
119 | {
120 | Tooltip.SetName("Close");
121 | Tooltip.SetTooltip("Close this menu");
122 | }
123 |
124 | base.Draw(spriteBatch);
125 | }
126 | }
127 | }
--------------------------------------------------------------------------------
/Content/GUI/ChestGUI/ChestRuleElement.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.ChestHelper;
2 | using Terraria.GameContent.UI.Elements;
3 | using Terraria.UI;
4 |
5 | namespace StructureHelper.Content.GUI.ChestGUI
6 | {
7 | class ChestRuleElement : UIElement
8 | {
9 | internal ChestRule rule;
10 | internal Color color = Color.White;
11 | internal float storedHeight = 0;
12 |
13 | internal UIList lootElements = [];
14 | readonly UIImageButton removeButton = new(Assets.GUI.Cross);
15 |
16 | readonly UIImageButton upButton = new(Assets.GUI.UpLarge);
17 | readonly UIImageButton downButton = new(Assets.GUI.DownLarge);
18 | readonly UIImageButton hideButton = new(Assets.GUI.Eye);
19 |
20 | public ChestRuleElement(ChestRule rule)
21 | {
22 | this.rule = rule;
23 |
24 | Width.Set(400, 0);
25 | Height.Set(50, 0);
26 |
27 | lootElements.Left.Set(0, 0);
28 | lootElements.Top.Set(50, 0);
29 | lootElements.Width.Set(400, 0);
30 | lootElements.Height.Set(0, 0);
31 | lootElements.ListPadding = 2f;
32 | Append(lootElements);
33 |
34 | removeButton.Left.Set(-36, 1);
35 | removeButton.Top.Set(6, 0);
36 | removeButton.Width.Set(32, 0);
37 | removeButton.Height.Set(32, 0);
38 | removeButton.OnLeftClick += Remove;
39 | Append(removeButton);
40 |
41 | upButton.Left.Set(8, 0);
42 | upButton.Top.Set(-3, 0);
43 | upButton.Width.Set(24, 0);
44 | upButton.Height.Set(18, 0);
45 | upButton.SetVisibility(1, 0.8f);
46 | upButton.OnLeftClick += MoveUp;
47 | Append(upButton);
48 |
49 | downButton.Left.Set(8, 0);
50 | downButton.Top.Set(27, 0);
51 | downButton.Width.Set(24, 0);
52 | downButton.Height.Set(18, 0);
53 | downButton.SetVisibility(1, 0.8f);
54 | downButton.OnLeftClick += MoveDown;
55 | Append(downButton);
56 |
57 | hideButton.Left.Set(-56, 1);
58 | hideButton.Top.Set(16, 0);
59 | hideButton.Width.Set(18, 0);
60 | hideButton.Height.Set(12, 0);
61 | hideButton.SetVisibility(1, 0.5f);
62 | hideButton.OnLeftClick += Hide;
63 | Append(hideButton);
64 |
65 | foreach (Loot loot in rule.pool)
66 | AddItem(loot);
67 | }
68 |
69 | private void Hide(UIMouseEvent evt, UIElement listeningElement)
70 | {
71 | if (storedHeight == 0)
72 | {
73 | hideButton.SetImage(Assets.GUI.EyeClosed);
74 | storedHeight = GetDimensions().Height;
75 | Height.Set(50, 0);
76 | }
77 | else
78 | {
79 | hideButton.SetImage(Assets.GUI.Eye);
80 | Height.Set(storedHeight, 0);
81 | storedHeight = 0;
82 | }
83 | }
84 |
85 | private void MoveDown(UIMouseEvent evt, UIElement listeningElement)
86 | {
87 | var list = Parent.Parent as UIList;
88 | int i = list._items.IndexOf(this);
89 |
90 | if (i < list.Count - 1)
91 | {
92 | (list._items[i + 1], list._items[i]) = (list._items[i], list._items[i + 1]);
93 | }
94 | }
95 |
96 | private void MoveUp(UIMouseEvent evt, UIElement listeningElement)
97 | {
98 | var list = Parent.Parent as UIList;
99 | int i = list._items.IndexOf(this);
100 |
101 | if (i >= 1)
102 | {
103 | (list._items[i - 1], list._items[i]) = (list._items[i], list._items[i - 1]);
104 | }
105 | }
106 |
107 | public override void LeftClick(UIMouseEvent evt)
108 | {
109 | if (Main.mouseItem.IsAir || storedHeight > 0)
110 | return;
111 |
112 | AddItem(Main.mouseItem);
113 | }
114 |
115 | public override void Draw(SpriteBatch spriteBatch)
116 | {
117 | Vector2 pos = GetDimensions().ToRectangle().TopLeft();
118 | var target = new Rectangle((int)pos.X, (int)pos.Y, (int)GetDimensions().Width, 46);
119 | Helpers.GUIHelper.DrawBox(spriteBatch, target, color);
120 |
121 | if (target.Contains(Main.MouseScreen.ToPoint()))
122 | {
123 | Tooltip.SetName(rule.Name);
124 | Tooltip.SetTooltip("Left click with an item to add to this rule");
125 | }
126 |
127 | if (removeButton.IsMouseHovering)
128 | {
129 | Tooltip.SetName("Remove rule");
130 | Tooltip.SetTooltip("Removes this rule.");
131 | }
132 |
133 | if (upButton.IsMouseHovering)
134 | {
135 | Tooltip.SetName("Move up");
136 | Tooltip.SetTooltip("Moves this rule earlier in priority");
137 | }
138 |
139 | if (downButton.IsMouseHovering)
140 | {
141 | Tooltip.SetName("Move down");
142 | Tooltip.SetTooltip("Moves this rule later in priority");
143 | }
144 |
145 | if (hideButton.IsMouseHovering)
146 | {
147 | Tooltip.SetName("Collapse");
148 | Tooltip.SetTooltip("Collapse or expand this rule");
149 | }
150 |
151 | Utils.DrawBorderString(spriteBatch, rule.Name, pos + new Vector2(38, 14), Color.White, 0.9f);
152 |
153 | if (storedHeight == 0)
154 | {
155 | base.Draw(spriteBatch);
156 | }
157 | else
158 | {
159 | removeButton.Draw(spriteBatch);
160 | upButton.Draw(spriteBatch);
161 | downButton.Draw(spriteBatch);
162 | hideButton.Draw(spriteBatch);
163 | }
164 | }
165 |
166 | //These handle adding/removing the elements and items from the appropriate lists, as well as re-sizing the element.
167 | public void AddItem(Item item)
168 | {
169 | Loot loot = rule.AddItem(item);
170 |
171 | var element = new LootElement(loot, rule.UsesWeight);
172 | lootElements.Add(element);
173 | lootElements.Height.Set(lootElements.Height.Pixels + element.Height.Pixels + 2, 0);
174 | Height.Set(Height.Pixels + element.Height.Pixels + 2, 0);
175 | }
176 |
177 | public void AddItem(Loot loot)
178 | {
179 | var element = new LootElement(loot, rule.UsesWeight);
180 | lootElements.Add(element);
181 | lootElements.Height.Set(lootElements.Height.Pixels + element.Height.Pixels + 2, 0);
182 | Height.Set(Height.Pixels + element.Height.Pixels + 2, 0);
183 | }
184 |
185 | public void RemoveItem(Loot loot, LootElement element)
186 | {
187 | rule.RemoveItem(loot);
188 | lootElements.Remove(element);
189 | lootElements.Height.Set(lootElements.Height.Pixels - element.Height.Pixels - 2, 0);
190 | Height.Set(Height.Pixels - element.Height.Pixels - 2, 0);
191 | }
192 |
193 | private void Remove(UIMouseEvent evt, UIElement listeningElement)
194 | {
195 | if (!(Parent.Parent.Parent is ChestCustomizerState))
196 | return;
197 |
198 | var parent = Parent.Parent.Parent as ChestCustomizerState;
199 | parent.ruleElements.Remove(this);
200 | }
201 | }
202 | }
--------------------------------------------------------------------------------
/Content/GUI/ChestGUI/LootElement.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.ChestHelper;
2 | using Terraria.GameContent.UI.Elements;
3 | using Terraria.UI;
4 |
5 | namespace StructureHelper.Content.GUI.ChestGUI
6 | {
7 | class LootElement : UIElement
8 | {
9 | readonly Loot loot;
10 | readonly UIImageButton removeButton = new(Assets.GUI.Cross);
11 |
12 | readonly NumberSetter min;
13 | readonly NumberSetter max;
14 | readonly NumberSetter weight;
15 |
16 | readonly UIImageButton upButton = new(Assets.GUI.Up);
17 | readonly UIImageButton downButton = new(Assets.GUI.Down);
18 |
19 | public LootElement(Loot loot, bool hasWeight)
20 | {
21 | this.loot = loot;
22 |
23 | Width.Set(350, 0);
24 | Height.Set(50, 0);
25 | Left.Set(50, 0);
26 |
27 | removeButton.Left.Set(-36, 1);
28 | removeButton.Top.Set(6, 0);
29 | removeButton.Width.Set(32, 0);
30 | removeButton.Height.Set(32, 0);
31 | removeButton.OnLeftClick += Remove;
32 | Append(removeButton);
33 |
34 | min = new NumberSetter(loot.min, "Min", 115);
35 | Append(min);
36 |
37 | max = new NumberSetter(loot.max, "Max", 70);
38 | Append(max);
39 |
40 | if (hasWeight)
41 | {
42 | weight = new NumberSetter(loot.weight, "Weight", 160);
43 | Append(weight);
44 | }
45 | else
46 | {
47 | upButton.Left.Set(8, 0);
48 | upButton.Top.Set(10, 0);
49 | upButton.Width.Set(12, 0);
50 | upButton.Height.Set(8, 0);
51 | upButton.SetVisibility(1, 0.8f);
52 | upButton.OnLeftClick += MoveUp;
53 | Append(upButton);
54 |
55 | downButton.Left.Set(8, 0);
56 | downButton.Top.Set(26, 0);
57 | downButton.Width.Set(12, 0);
58 | downButton.Height.Set(8, 0);
59 | downButton.SetVisibility(1, 0.8f);
60 | downButton.OnLeftClick += MoveDown;
61 | Append(downButton);
62 | }
63 | }
64 |
65 | private void MoveDown(UIMouseEvent evt, UIElement listeningElement)
66 | {
67 | var list = Parent.Parent as UIList;
68 | int i = list._items.IndexOf(this);
69 |
70 | if (i < list.Count - 1)
71 | {
72 | (list._items[i + 1], list._items[i]) = (list._items[i], list._items[i + 1]);
73 | }
74 | }
75 |
76 | private void MoveUp(UIMouseEvent evt, UIElement listeningElement)
77 | {
78 | var list = Parent.Parent as UIList;
79 | int i = list._items.IndexOf(this);
80 |
81 | if (i >= 1)
82 | {
83 | (list._items[i - 1], list._items[i]) = (list._items[i], list._items[i - 1]);
84 | }
85 | }
86 |
87 | public override void Draw(SpriteBatch spriteBatch)
88 | {
89 | Vector2 pos = GetDimensions().ToRectangle().TopLeft();
90 | var target = new Rectangle((int)pos.X, (int)pos.Y, (int)GetDimensions().Width, 46);
91 |
92 | Color color = Color.White;
93 |
94 | if (Parent.Parent.Parent is ChestRuleElement)
95 | {
96 | color = (Parent.Parent.Parent as ChestRuleElement).color;
97 | color = Color.Lerp(color, Color.Black, 0.25f);
98 | }
99 |
100 | if (removeButton.IsMouseHovering)
101 | {
102 | Tooltip.SetName("Remove item");
103 | Tooltip.SetTooltip("Remove this item from the rule");
104 | }
105 |
106 | Helpers.GUIHelper.DrawBox(spriteBatch, target, color);
107 |
108 | int xOff = 0;
109 | if (weight is null)
110 | xOff += 20;
111 |
112 | Main.inventoryScale = 36 / 52f * 46 / 36f;
113 | ItemSlot.Draw(spriteBatch, ref loot.givenItem, 21, pos + Vector2.UnitX * xOff);
114 |
115 | string name = loot.givenItem.Name.Length > 20 ? loot.givenItem.Name[..18] + "..." : loot.givenItem.Name;
116 | Utils.DrawBorderString(spriteBatch, name, pos + new Vector2(46 + xOff, 14), Color.White, 0.8f);
117 |
118 | if (max.Value < min.Value)
119 | max.Value = min.Value;
120 |
121 | loot.min = min.Value;
122 | loot.max = max.Value;
123 |
124 | if (weight != null)
125 | loot.weight = weight.Value;
126 |
127 | base.Draw(spriteBatch);
128 | }
129 |
130 | private void Remove(UIMouseEvent evt, UIElement listeningElement)
131 | {
132 | if (!(Parent.Parent.Parent is ChestRuleElement))
133 | return;
134 |
135 | var parent = Parent.Parent.Parent as ChestRuleElement;
136 | parent.RemoveItem(loot, this);
137 | }
138 | }
139 | }
--------------------------------------------------------------------------------
/Content/GUI/ChestGUI/NumberSetter.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.ChestHelper;
2 | using Terraria.UI;
3 |
4 | namespace StructureHelper.Content.GUI.ChestGUI
5 | {
6 | class NumberSetter : UIElement
7 | {
8 | readonly string Text;
9 | readonly string Suffix;
10 |
11 | readonly TextField editor = new(InputType.integer);
12 |
13 | public int Value
14 | {
15 | get
16 | {
17 | if (int.TryParse(editor.currentValue, out int result))
18 | return result;
19 |
20 | return 0;
21 | }
22 |
23 | set => editor.currentValue = value.ToString();
24 | }
25 |
26 | public NumberSetter(int value, string text, int xOff, string suffix = "")
27 | {
28 | Value = value;
29 | Text = text;
30 | Suffix = suffix;
31 |
32 | Width.Set(32, 0);
33 | Height.Set(50, 0);
34 | Left.Set(-xOff, 1);
35 |
36 | editor.Left.Set(0, 0);
37 | editor.Top.Set(16, 0);
38 | editor.Width.Set(32, 0);
39 | editor.Height.Set(22, 0);
40 | Append(editor);
41 | }
42 |
43 | public override void Draw(SpriteBatch spriteBatch)
44 | {
45 | if (IsMouseHovering)
46 | {
47 | Tooltip.SetName(Text);
48 | Tooltip.SetTooltip("Click to type");
49 | }
50 |
51 | base.Draw(spriteBatch);
52 |
53 | Utils.DrawBorderString(spriteBatch, Text, GetDimensions().Center() + Vector2.UnitY * -22, Color.White, 0.7f, 0.5f, 0f);
54 | Utils.DrawBorderString(spriteBatch, Suffix, GetDimensions().Center() + new Vector2(16, 6), Color.White, 0.7f, 0.5f, 0.5f);
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/Content/GUI/ChestGUI/SpecificChestRuleElements.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.ChestHelper;
2 | using System;
3 |
4 | namespace StructureHelper.Content.GUI.ChestGUI
5 | {
6 | class GuaranteedRuleElement : ChestRuleElement
7 | {
8 | public GuaranteedRuleElement() : base(new ChestRuleGuaranteed())
9 | {
10 | color = new Color(200, 0, 0);
11 | }
12 |
13 | public GuaranteedRuleElement(ChestRule rule) : base(rule)
14 | {
15 | color = new Color(200, 0, 0);
16 | }
17 | }
18 |
19 | class ChanceRuleElement : ChestRuleElement
20 | {
21 | readonly NumberSetter chanceSetter = new(100, "Chance", 100, "%");
22 |
23 | public ChanceRuleElement() : base(new ChestRuleChance())
24 | {
25 | color = Color.Green;
26 | Append(chanceSetter);
27 | }
28 |
29 | public ChanceRuleElement(ChestRule rule) : base(rule)
30 | {
31 | color = Color.Green;
32 | chanceSetter.Value = (int)Math.Round((rule as ChestRuleChance).chance * 100);
33 | Append(chanceSetter);
34 | }
35 |
36 | public override void Update(GameTime gameTime)
37 | {
38 | if (chanceSetter.Value > 100)
39 | chanceSetter.Value = 100;
40 |
41 | (rule as ChestRuleChance).chance = chanceSetter.Value / 100f;
42 | base.Update(gameTime);
43 | }
44 | }
45 |
46 | class PoolRuleElement : ChestRuleElement
47 | {
48 | readonly NumberSetter countSetter = new(1, "Amount", 100);
49 |
50 | public PoolRuleElement() : base(new ChestRulePool())
51 | {
52 | color = Color.Purple;
53 | Append(countSetter);
54 | }
55 |
56 | public PoolRuleElement(ChestRule rule) : base(rule)
57 | {
58 | color = Color.Purple;
59 | countSetter.Value = (rule as ChestRulePool).itemsToGenerate;
60 | Append(countSetter);
61 | }
62 |
63 | public override void Update(GameTime gameTime)
64 | {
65 | if (countSetter.Value > rule.pool.Count)
66 | countSetter.Value = rule.pool.Count;
67 |
68 | (rule as ChestRulePool).itemsToGenerate = countSetter.Value;
69 | base.Update(gameTime);
70 | }
71 | }
72 |
73 | class PoolChanceRuleElement : ChestRuleElement
74 | {
75 | readonly NumberSetter chanceSetter = new(100, "Chance", 100, "%");
76 | readonly NumberSetter countSetter = new(1, "Amount", 150);
77 |
78 | public PoolChanceRuleElement() : base(new ChestRulePoolChance())
79 | {
80 | color = new Color(50, 50, 200);
81 | Append(chanceSetter);
82 | Append(countSetter);
83 | }
84 |
85 | public PoolChanceRuleElement(ChestRule rule) : base(rule)
86 | {
87 | color = new Color(50, 50, 200);
88 | chanceSetter.Value = (int)Math.Round((rule as ChestRulePoolChance).chance * 100);
89 | countSetter.Value = (rule as ChestRulePoolChance).itemsToGenerate;
90 | Append(chanceSetter);
91 | Append(countSetter);
92 | }
93 |
94 | public override void Update(GameTime gameTime)
95 | {
96 | if (countSetter.Value > rule.pool.Count)
97 | countSetter.Value = rule.pool.Count;
98 |
99 | if (chanceSetter.Value > 100)
100 | chanceSetter.Value = 100;
101 |
102 | (rule as ChestRulePoolChance).itemsToGenerate = countSetter.Value;
103 | (rule as ChestRulePoolChance).chance = chanceSetter.Value / 100f;
104 | base.Update(gameTime);
105 | }
106 | }
107 | }
--------------------------------------------------------------------------------
/Content/GUI/FieldEditor.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.Core.Loaders.UILoading;
2 | using StructureHelper.Helpers;
3 | using System;
4 |
5 | namespace StructureHelper.Content.GUI
6 | {
7 | ///
8 | /// A UI element for changing the value of 'something'.
9 | ///
10 | internal abstract class FieldEditor : SmartUIElement
11 | {
12 | ///
13 | /// The name that gets displated above the panel to the user
14 | ///
15 | public string name;
16 |
17 | ///
18 | /// The info sown when hovering over this panel
19 | ///
20 | public string description;
21 |
22 | ///
23 | /// The current value this editor believes the field its tied to to have. This wont update in real time so be careful
24 | ///
25 | public T value;
26 |
27 | ///
28 | /// The callback that should happen when this editor thinks the value its tracking has changed. You'll likely need to cast the object parameter to the correct type.
29 | ///
30 | protected readonly Action onValueChanged;
31 |
32 | ///
33 | /// This function, called every frame while the editor is not being used, is used to update the editor's value to the current value of the tracked value.
34 | ///
35 | protected readonly Func listenForUpdate;
36 |
37 | ///
38 | /// If this editor is currently being used to change a value, and thus shouldn't listen for update
39 | ///
40 | public virtual bool Editing => false;
41 |
42 | ///
43 | ///
44 | ///
45 | /// Height of the panel
46 | /// The name that gets displated above the panel to the user
47 | /// The callback that should happen when this editor thinks the value its tracking has changed. You'll likely need to cast the object parameter to the correct type.
48 | /// A hint for what the initial value of the field tracked by this editor is
49 | public FieldEditor(int height, string name, Action onValueChanged, Func listenForUpdate = null, T initialValue = default, string description = "")
50 | {
51 | Width.Set(150, 0);
52 | Height.Set(height, 0);
53 | this.name = name;
54 | this.onValueChanged = onValueChanged;
55 | this.listenForUpdate = listenForUpdate;
56 | value = initialValue;
57 | this.description = description;
58 | }
59 |
60 | ///
61 | /// Defines what should happen when a new value is recieved from the value update listener. Note that value has not yet been updated when this is called, so you can compare to the old value.
62 | ///
63 | /// The new value that was recieved
64 | public virtual void OnRecieveNewValue(T newValue) { }
65 |
66 | public sealed override void SafeUpdate(GameTime gameTime)
67 | {
68 | if (!Editing && listenForUpdate != null)
69 | {
70 | T newValue = listenForUpdate();
71 | OnRecieveNewValue(newValue);
72 | value = newValue;
73 | }
74 |
75 | EditorUpdate(gameTime);
76 | }
77 |
78 | public virtual void EditorUpdate(GameTime gameTime) { }
79 |
80 | public sealed override void Draw(SpriteBatch spriteBatch)
81 | {
82 | GUIHelper.DrawBox(spriteBatch, GetDimensions().ToRectangle());
83 |
84 | Texture2D back = Assets.GUI.Gradient.Value;
85 | var backTarget = GetDimensions().ToRectangle();
86 | backTarget.Height = 24;
87 | backTarget.Offset(new Point(4, 4));
88 | spriteBatch.Draw(back, backTarget, Color.Black * 0.5f);
89 |
90 | Utils.DrawBorderString(spriteBatch, name, GetDimensions().Position() + new Vector2(8, 4), Color.White, 0.7f);
91 | Utils.DrawBorderString(spriteBatch, typeof(T).Name, GetDimensions().Position() + new Vector2(8, 18), Color.Gray, 0.65f);
92 |
93 | base.Draw(spriteBatch);
94 |
95 | SafeDraw(spriteBatch);
96 |
97 | if (IsMouseHovering)
98 | {
99 | Tooltip.SetName(name);
100 | Tooltip.SetTooltip(description);
101 | Main.LocalPlayer.mouseInterface = true;
102 | }
103 | }
104 |
105 | public virtual void SafeDraw(SpriteBatch sprite) { }
106 | }
107 | }
--------------------------------------------------------------------------------
/Content/GUI/ManualGeneratorMenu.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.API;
2 | using StructureHelper.Content.Items;
3 | using StructureHelper.Core.Loaders.UILoading;
4 | using StructureHelper.Models;
5 | using StructureHelper.Util;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.IO;
9 | using Terraria.GameContent.UI.Elements;
10 | using Terraria.ModLoader.UI.Elements;
11 | using Terraria.UI;
12 |
13 | namespace StructureHelper.Content.GUI
14 | {
15 | class ManualGeneratorMenu : SmartUIState
16 | {
17 | public static StructureEntry selected;
18 | public static bool ignoreNulls = false;
19 |
20 | public static StructurePreview preview;
21 |
22 | public static bool multiMode = false;
23 | public static int multiIndex;
24 |
25 | public static UIGrid structureElements = new();
26 | public static UIScrollbar scrollBar = new();
27 |
28 | public static UIImageButton refreshButton = new(Assets.GUI.Refresh);
29 | public static UIImageButton ignoreButton = new(Assets.GUI.Null);
30 | public static UIImageButton closeButton = new(Assets.GUI.Cross);
31 |
32 | public static GenFlags flags;
33 | public static BoolEditor slopeNullToggle = new("NullsKeepGivenSlope", (val) => flags = val ? flags | GenFlags.NullsKeepGivenSlope : flags & ~GenFlags.NullsKeepGivenSlope, false, description: "If null tiles will keep their slope when generated");
34 | public static BoolEditor paintNullToggle = new("NullsKeepGivenPaint", (val) => flags = val ? flags | GenFlags.NullsKeepGivenPaint : flags & ~GenFlags.NullsKeepGivenPaint, false, description: "If null tiles/walls will keep their paint when generated");
35 | public static BoolEditor tileEntityToggle = new("IgnoreTileEntityData", (val) => flags = val ? flags | GenFlags.IgnoreTileEnttiyData : flags & ~GenFlags.IgnoreTileEnttiyData, false, description: "If custom tile entity data is ignored or not");
36 |
37 | public override bool Visible => TestWand.UIVisible;
38 |
39 | public override int InsertionIndex(List layers)
40 | {
41 | return layers.FindIndex(layer => layer.Name.Equals("Vanilla: Mouse Text"));
42 | }
43 |
44 | ///
45 | /// Loads all structure files and generates elements to be clicked for them
46 | ///
47 | public static void LoadStructures()
48 | {
49 | structureElements.Clear();
50 | selected = null;
51 |
52 | string folderPath = ModLoader.ModPath.Replace("Mods", "SavedStructures");
53 | Directory.CreateDirectory(folderPath);
54 |
55 | string[] filePaths = Directory.GetFiles(folderPath);
56 |
57 | foreach (string path in filePaths)
58 | {
59 | string name = path.Replace(folderPath + Path.DirectorySeparatorChar, "");
60 | structureElements.Add(new StructureEntry(name, path));
61 | }
62 | }
63 |
64 | public override void OnInitialize()
65 | {
66 | LoadStructures();
67 | SetDims(structureElements, -200, 0.5f, 0, 0.1f, 400, 0, 0, 0.8f);
68 | SetDims(scrollBar, 232, 0.5f, 0, 0.1f, 32, 0, 0, 0.8f);
69 | structureElements.SetScrollbar(scrollBar);
70 | Append(structureElements);
71 | Append(scrollBar);
72 |
73 | SetDims(refreshButton, -200, 0.5f, -50, 0.1f, 32, 0, 32, 0);
74 | refreshButton.OnLeftClick += RefreshButton_OnClick;
75 | Append(refreshButton);
76 |
77 | SetDims(ignoreButton, -150, 0.5f, -50, 0.1f, 32, 0, 32, 0);
78 | ignoreButton.OnLeftClick += IgnoreButton_OnClick;
79 | Append(ignoreButton);
80 |
81 | SetDims(closeButton, 200 - 32, 0.5f, -50, 0.1f, 32, 0, 32, 0);
82 | closeButton.OnLeftClick += CloseButton_OnClick;
83 | Append(closeButton);
84 |
85 | SetDims(slopeNullToggle, -384, 0.5f, 0, 0.1f, 150, 0, 70, 0);
86 | Append(slopeNullToggle);
87 |
88 | SetDims(paintNullToggle, -384, 0.5f, 80, 0.1f, 150, 0, 70, 0);
89 | Append(paintNullToggle);
90 |
91 | SetDims(tileEntityToggle, -384, 0.5f, 160, 0.1f, 150, 0, 70, 0);
92 | Append(tileEntityToggle);
93 | }
94 |
95 | private void CloseButton_OnClick(UIMouseEvent evt, UIElement listeningElement)
96 | {
97 | TestWand.UIVisible = false;
98 | Main.isMouseLeftConsumedByUI = true;
99 | }
100 |
101 | private void IgnoreButton_OnClick(UIMouseEvent evt, UIElement listeningElement)
102 | {
103 | ignoreNulls = !ignoreNulls;
104 | Main.isMouseLeftConsumedByUI = true;
105 | }
106 |
107 | private void RefreshButton_OnClick(UIMouseEvent evt, UIElement listeningElement)
108 | {
109 | LoadStructures();
110 | Main.isMouseLeftConsumedByUI = true;
111 | }
112 |
113 | public override void SafeUpdate(GameTime gameTime)
114 | {
115 | Recalculate();
116 |
117 | if (Main.playerInventory)
118 | TestWand.UIVisible = false;
119 |
120 | if (ignoreButton.IsMouseHovering)
121 | {
122 | Tooltip.SetName($"Place with null tiles: {ignoreNulls}");
123 | Tooltip.SetTooltip("If the structure placed manually should have it's null tiles placed or not. Turn this off to get a realistic generation, or on if you want to edit the structure.");
124 | Main.LocalPlayer.mouseInterface = true;
125 | }
126 |
127 | if (refreshButton.IsMouseHovering)
128 | {
129 | Tooltip.SetName("Reload");
130 | Tooltip.SetTooltip("Reload structures from the folder, use this if you change the folders contents externally and want to see it reflected here.");
131 | Main.LocalPlayer.mouseInterface = true;
132 | }
133 |
134 | if (closeButton.IsMouseHovering)
135 | {
136 | Tooltip.SetName("Close");
137 | Tooltip.SetTooltip("Close this menu");
138 | Main.LocalPlayer.mouseInterface = true;
139 | }
140 | }
141 |
142 | public override void Draw(SpriteBatch spriteBatch)
143 | {
144 | var color = new Color(49, 84, 141);
145 | Helpers.GUIHelper.DrawBox(spriteBatch, ignoreButton.GetDimensions().ToRectangle(), ignoreButton.IsMouseHovering ? color : color * 0.8f);
146 | Helpers.GUIHelper.DrawBox(spriteBatch, refreshButton.GetDimensions().ToRectangle(), refreshButton.IsMouseHovering ? color : color * 0.8f);
147 | Helpers.GUIHelper.DrawBox(spriteBatch, closeButton.GetDimensions().ToRectangle(), closeButton.IsMouseHovering ? color : color * 0.8f);
148 |
149 | var rect = structureElements.GetDimensions().ToRectangle();
150 | rect.Inflate(30, 10);
151 | Helpers.GUIHelper.DrawBox(spriteBatch, rect, new Color(20, 40, 60) * 0.8f);
152 |
153 | base.Draw(spriteBatch);
154 |
155 | if (!ignoreNulls)
156 | {
157 | Texture2D tex = Assets.GUI.Cross.Value;
158 | spriteBatch.Draw(tex, ignoreButton.GetDimensions().ToRectangle(), ignoreButton.IsMouseHovering ? Color.White : Color.White * 0.5f);
159 | }
160 | }
161 |
162 | public static void SetDims(UIElement ele, int x, int y, int w, int h)
163 | {
164 | ele.Left.Set(x, 0);
165 | ele.Top.Set(y, 0);
166 | ele.Width.Set(w, 0);
167 | ele.Height.Set(h, 0);
168 | }
169 |
170 | public static void SetDims(UIElement ele, int x, float xp, int y, float yp, int w, float wp, int h, float hp)
171 | {
172 | ele.Left.Set(x, xp);
173 | ele.Top.Set(y, yp);
174 | ele.Width.Set(w, wp);
175 | ele.Height.Set(h, hp);
176 | }
177 | }
178 |
179 | class StructureEntry : UIElement
180 | {
181 | public string name = "";
182 | public string path;
183 |
184 | bool Active => ManualGeneratorMenu.selected == this;
185 |
186 | public StructureEntry(string name, string path)
187 | {
188 | this.name = name;
189 | this.path = path;
190 |
191 | Width.Set(400, 0);
192 | Height.Set(32, 0);
193 | }
194 |
195 | public override void Draw(SpriteBatch spriteBatch)
196 | {
197 | if (IsMouseHovering)
198 | Main.LocalPlayer.mouseInterface = true;
199 |
200 | Vector2 pos = GetDimensions().ToRectangle().TopLeft();
201 | var mainBox = new Rectangle((int)pos.X, (int)pos.Y, 400, 32);
202 |
203 | Color color = Color.Gray;
204 |
205 | if (IsMouseHovering)
206 | color = Color.White;
207 |
208 | if (Active)
209 | color = Color.Yellow;
210 |
211 | Helpers.GUIHelper.DrawBox(spriteBatch, mainBox, IsMouseHovering || Active ? new Color(49, 84, 141) : new Color(49, 84, 141) * 0.6f);
212 | Utils.DrawBorderString(spriteBatch, name, mainBox.Center() + Vector2.UnitY * 4, color, 0.8f, 0.5f, 0.5f);
213 |
214 | base.Draw(spriteBatch);
215 |
216 | if (!Active)
217 | {
218 | Height.Set(32, 0);
219 | RemoveAllChildren();
220 | }
221 | }
222 |
223 | public override void LeftClick(UIMouseEvent evt)
224 | {
225 | ManualGeneratorMenu.selected = this;
226 | ManualGeneratorMenu.multiIndex = 0;
227 | ManualGeneratorMenu.multiMode = false;
228 |
229 | if (Path.GetExtension(path) == ".shstruct")
230 | {
231 | ManualGeneratorMenu.multiMode = false;
232 |
233 | ManualGeneratorMenu.preview?.Dispose();
234 | ManualGeneratorMenu.preview = new StructurePreview(name, API.Generator.GetStructureData(path, StructureHelper.Instance, true));
235 | }
236 | else if (Path.GetExtension(path) == ".shmstruct")
237 | {
238 | ManualGeneratorMenu.multiMode = true;
239 | MultiStructureData data = MultiStructureGenerator.GetMultiStructureData(path, StructureHelper.Instance, true);
240 |
241 | ManualGeneratorMenu.preview?.Dispose();
242 | ManualGeneratorMenu.preview = new StructurePreview("", data.structures[0]);
243 |
244 | Height.Set(36 + 96 * (int)Math.Ceiling(data.count / 4f), 0);
245 |
246 | var list = new UIGrid();
247 |
248 | for (int k = 0; k < data.count; k++)
249 | {
250 | list.Add(new MultiSelectionEntry(k, data.structures[k]));
251 | }
252 |
253 | list.Width.Set(400, 0);
254 | list.Height.Set(96 * (int)Math.Ceiling(data.count / 4f), 0);
255 | list.Left.Set(0, 0);
256 | list.Top.Set(36, 0);
257 | Append(list);
258 | }
259 | else
260 | {
261 | Main.NewText("This isnt a structure, or is an unported, old 2.0 format structure! You should port your structures.");
262 | return;
263 | }
264 | }
265 |
266 | public override int CompareTo(object obj)
267 | {
268 | if (obj is StructureEntry other)
269 | return name.CompareTo(other.name);
270 |
271 | return base.CompareTo(obj);
272 | }
273 | }
274 |
275 | class MultiSelectionEntry : UIElement
276 | {
277 | public int value;
278 | private readonly StructureData structure;
279 | private readonly StructurePreview preview;
280 |
281 | bool Active => ManualGeneratorMenu.multiIndex == value;
282 |
283 | public MultiSelectionEntry(int index, StructureData structure)
284 | {
285 | value = index;
286 | Width.Set(96, 0);
287 | Height.Set(96, 0);
288 |
289 | this.structure = structure;
290 | preview = new("", structure);
291 | }
292 |
293 | public override void Draw(SpriteBatch spriteBatch)
294 | {
295 | if (IsMouseHovering)
296 | Main.LocalPlayer.mouseInterface = true;
297 |
298 | var dims = GetDimensions().ToRectangle();
299 | Vector2 pos = dims.TopLeft() + new Vector2(12, 12);
300 | Color color = Color.Gray;
301 |
302 | if (IsMouseHovering)
303 | color = Color.White;
304 |
305 | if (Active)
306 | color = Color.Yellow;
307 |
308 | Helpers.GUIHelper.DrawBox(spriteBatch, GetDimensions().ToRectangle(), IsMouseHovering || Active ? new Color(49, 84, 141) : new Color(49, 84, 141) * 0.6f);
309 |
310 | dims.Inflate(-8, -8);
311 |
312 | if (preview != null)
313 | {
314 | Texture2D tex = preview.preview;
315 | float scale = 1f;
316 |
317 | if (tex.Width > dims.Width || tex.Height > dims.Height)
318 | scale = tex.Width > tex.Height ? dims.Width / (float)tex.Width : dims.Height / (float)tex.Height;
319 |
320 | spriteBatch.Draw(tex, GetDimensions().Center(), null, color, 0, tex.Size() / 2f, scale, 0, 0);
321 | }
322 |
323 | Utils.DrawBorderString(spriteBatch, value.ToString(), pos + Vector2.UnitY * 4, color, 0.8f, 0.5f, 0.5f);
324 |
325 | base.Draw(spriteBatch);
326 | }
327 |
328 | public override void LeftClick(UIMouseEvent evt)
329 | {
330 | ManualGeneratorMenu.multiIndex = value;
331 |
332 | ManualGeneratorMenu.preview?.Dispose();
333 | ManualGeneratorMenu.preview = new StructurePreview("", structure);
334 | }
335 |
336 | public override int CompareTo(object obj)
337 | {
338 | if (obj is MultiSelectionEntry other)
339 | return value.CompareTo(other.value);
340 |
341 | return base.CompareTo(obj);
342 | }
343 | }
344 | }
--------------------------------------------------------------------------------
/Content/GUI/NameConfirmPopup.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.Core.Loaders.UILoading;
2 | using System;
3 | using System.Collections.Generic;
4 | using Terraria.GameContent.UI.Elements;
5 | using Terraria.UI;
6 |
7 | namespace StructureHelper.Content.GUI
8 | {
9 | internal class NameConfirmPopup : SmartUIState
10 | {
11 | public static bool visible;
12 | public static Action onConfirm;
13 |
14 | public TextField nameField = new();
15 | public UIText confirm = new("Confirm");
16 | public UIText cancel = new("Cancel");
17 |
18 | public override bool Visible => visible;
19 |
20 | public override int InsertionIndex(List layers)
21 | {
22 | return layers.FindIndex(layer => layer.Name.Equals("Vanilla: Mouse Text"));
23 | }
24 |
25 | public static void OpenConfirmation(Action onConfirm)
26 | {
27 | visible = true;
28 | NameConfirmPopup.onConfirm = onConfirm;
29 | }
30 |
31 | public override void OnInitialize()
32 | {
33 | confirm.OnMouseOver += (a, b) => confirm.TextColor = Color.Yellow;
34 | confirm.OnMouseOut += (a, b) => confirm.TextColor = Color.White;
35 | confirm.PaddingTop = 6;
36 | confirm.OnLeftClick += (a, b) =>
37 | {
38 | if (visible)
39 | {
40 | onConfirm?.Invoke(nameField.currentValue);
41 | nameField.currentValue = "";
42 | visible = false;
43 | }
44 | };
45 |
46 | cancel.OnMouseOver += (a, b) => cancel.TextColor = Color.Yellow;
47 | cancel.OnMouseOut += (a, b) => cancel.TextColor = Color.White;
48 | cancel.PaddingTop = 6;
49 | cancel.OnLeftClick += (a, b) => visible = false;
50 |
51 | AddElement(nameField, -120, 0.5f, -46, 0.5f, 240, 0, 28, 0);
52 | AddElement(confirm, -100, 0.5f, 0, 0.5f, 80, 0, 32, 0);
53 | AddElement(cancel, 20, 0.5f, 0, 0.5f, 80, 0, 32, 0);
54 | }
55 |
56 | public override void Draw(SpriteBatch spriteBatch)
57 | {
58 | var color = new Color(49, 84, 141);
59 | var back = new Rectangle(Main.screenWidth / 2 - 150, Main.screenHeight / 2 - 100, 300, 150);
60 |
61 | if (back.Contains(Main.MouseScreen.ToPoint()))
62 | Main.LocalPlayer.mouseInterface = true;
63 |
64 | Helpers.GUIHelper.DrawBox(spriteBatch, back, color);
65 | Utils.DrawBorderString(spriteBatch, "Name your creation:", new Vector2(Main.screenWidth / 2, Main.screenHeight / 2 - 70), Color.White, 1, 0.5f, 0.5f);
66 |
67 | Helpers.GUIHelper.DrawBox(spriteBatch, confirm.GetDimensions().ToRectangle(), color);
68 | Helpers.GUIHelper.DrawBox(spriteBatch, cancel.GetDimensions().ToRectangle(), color);
69 |
70 | base.Draw(spriteBatch);
71 | Recalculate();
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/Content/GUI/TextField.cs:
--------------------------------------------------------------------------------
1 | using ReLogic.Localization.IME;
2 | using ReLogic.OS;
3 | using StructureHelper.Core.Loaders.UILoading;
4 | using StructureHelper.Helpers;
5 | using System.Text.RegularExpressions;
6 | using Terraria.GameContent;
7 | using Terraria.GameInput;
8 | using Terraria.UI;
9 |
10 | namespace StructureHelper.Content.GUI
11 | {
12 | public enum InputType
13 | {
14 | text,
15 | integer,
16 | number
17 | }
18 |
19 | internal class TextField : SmartUIElement
20 | {
21 | public bool typing;
22 | public bool updated;
23 | public bool reset;
24 | public InputType inputType;
25 |
26 | public string currentValue = "";
27 |
28 | // Composition string is handled at the very beginning of the update
29 | // In order to check if there is a composition string before backspace is typed, we need to check the previous state
30 | private bool _oldHasCompositionString;
31 |
32 | public TextField(InputType inputType = InputType.text)
33 | {
34 | this.inputType = inputType;
35 | Width.Set(130, 0);
36 | Height.Set(24, 0);
37 | }
38 |
39 | public void SetTyping()
40 | {
41 | typing = true;
42 | Main.blockInput = true;
43 | }
44 |
45 | public void SetNotTyping()
46 | {
47 | typing = false;
48 | Main.blockInput = false;
49 | }
50 |
51 | public override void SafeClick(UIMouseEvent evt)
52 | {
53 | SetTyping();
54 | }
55 |
56 | public override void SafeRightClick(UIMouseEvent evt)
57 | {
58 | SetTyping();
59 | currentValue = "";
60 | updated = true;
61 | }
62 |
63 | public override void SafeUpdate(GameTime gameTime)
64 | {
65 | if (reset)
66 | {
67 | updated = false;
68 | reset = false;
69 | }
70 |
71 | if (updated)
72 | reset = true;
73 |
74 | if (Main.mouseLeft && !IsMouseHovering)
75 | SetNotTyping();
76 | }
77 |
78 | public void HandleText()
79 | {
80 | if (Main.keyState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape))
81 | SetNotTyping();
82 |
83 | PlayerInput.WritingText = true;
84 | Main.instance.HandleIME();
85 |
86 | string newText = Main.GetInputText(currentValue);
87 |
88 | // GetInputText() handles typing operation, but there is a issue that it doesn't handle backspace correctly when the composition string is not empty. It will delete a character both in the text and the composition string instead of only the one in composition string. We'll fix the issue here to provide a better user experience
89 | if (_oldHasCompositionString && Main.inputText.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Back))
90 | newText = currentValue; // force text not to be changed
91 |
92 | if (inputType == InputType.integer)
93 | {
94 | if (newText != currentValue && Regex.IsMatch(newText, "^[0-9]*$"))
95 | {
96 | currentValue = newText;
97 | updated = true;
98 | }
99 | }
100 | else if (inputType == InputType.number) //I found this regex on SO so no idea if it works right lol
101 | {
102 | if (newText != currentValue && Regex.IsMatch(newText, "(?<=^| )[0-9]+(.[0-9]+)?(?=$| )|(?<=^| ).[0-9]+(?=$| )"))
103 | {
104 | currentValue = newText;
105 | updated = true;
106 | }
107 | }
108 | else
109 | {
110 | if (newText != currentValue)
111 | {
112 | currentValue = newText;
113 | updated = true;
114 | }
115 | }
116 |
117 | _oldHasCompositionString = Platform.Get().CompositionString is { Length: > 0 };
118 | }
119 |
120 | public override void Draw(SpriteBatch spriteBatch)
121 | {
122 | GUIHelper.DrawBox(spriteBatch, GetDimensions().ToRectangle(), Color.Black * 0.5f);
123 |
124 | if (typing)
125 | {
126 | GUIHelper.DrawOutline(spriteBatch, GetDimensions().ToRectangle(), Color.White);
127 | HandleText();
128 |
129 | // draw ime panel, note that if there's no composition string then it won't draw anything
130 | Main.instance.DrawWindowsIMEPanel(GetDimensions().Position());
131 | }
132 |
133 | Vector2 pos = GetDimensions().Position() + Vector2.One * 4;
134 |
135 | const float scale = 0.75f;
136 | string displayed = currentValue ?? "";
137 |
138 | Utils.DrawBorderString(spriteBatch, displayed, pos, Color.White, scale);
139 |
140 | // composition string + cursor drawing below
141 | if (!typing)
142 | return;
143 |
144 | pos.X += FontAssets.MouseText.Value.MeasureString(displayed).X * scale;
145 | string compositionString = Platform.Get().CompositionString;
146 |
147 | if (compositionString is { Length: > 0 })
148 | {
149 | Utils.DrawBorderString(spriteBatch, compositionString, pos, new Color(255, 240, 20), scale);
150 | pos.X += FontAssets.MouseText.Value.MeasureString(compositionString).X * scale;
151 | }
152 |
153 | if (Main.GameUpdateCount % 20 < 10)
154 | Utils.DrawBorderString(spriteBatch, "|", pos, Color.White, scale);
155 | }
156 | }
157 | }
--------------------------------------------------------------------------------
/Content/GUI/Tooltip.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.Core.Loaders.UILoading;
2 | using System;
3 | using System.Collections.Generic;
4 | using Terraria.UI;
5 | using Terraria.UI.Chat;
6 |
7 | namespace StructureHelper.Content.GUI
8 | {
9 | ///
10 | /// Draws the popup tooltip when various elements of the UI are hovered over.
11 | ///
12 | public class Tooltip : SmartUIState, ILoadable
13 | {
14 | private static string text = string.Empty;
15 | private static string tooltip = string.Empty;
16 |
17 | public override bool Visible => true;
18 |
19 | public void Load(Mod mod)
20 | {
21 | On_Main.DrawInterface += Reset;
22 | }
23 |
24 | public override int InsertionIndex(List layers)
25 | {
26 | return layers.Count - 1;
27 | }
28 |
29 | ///
30 | /// Sets the brightly colored main line of the tooltip. This should be a short descriptor of what you're hovering over, like its name
31 | ///
32 | ///
33 | public static void SetName(string name)
34 | {
35 | text = name;
36 | }
37 |
38 | ///
39 | /// Sets the more dimly colored 'description' of the tooltip. This should be the 'body' of the tooltip.
40 | ///
41 | ///
42 | public static void SetTooltip(string newTooltip)
43 | {
44 | ReLogic.Graphics.DynamicSpriteFont font = Terraria.GameContent.FontAssets.MouseText.Value;
45 | tooltip = Helpers.GUIHelper.WrapString(newTooltip, 200, font, 1);
46 | }
47 |
48 | public override void Draw(SpriteBatch spriteBatch)
49 | {
50 | if (text == string.Empty)
51 | return;
52 |
53 | ReLogic.Graphics.DynamicSpriteFont font = Terraria.GameContent.FontAssets.MouseText.Value;
54 |
55 | float nameWidth = ChatManager.GetStringSize(font, text, Vector2.One).X;
56 | float tipWidth = ChatManager.GetStringSize(font, tooltip, Vector2.One).X * 0.9f;
57 |
58 | float width = Math.Max(nameWidth, tipWidth);
59 | float height = -16;
60 | Vector2 pos;
61 |
62 | if (Main.MouseScreen.X > Main.screenWidth - width)
63 | pos = Main.MouseScreen - new Vector2(width + 20, 0);
64 | else
65 | pos = Main.MouseScreen + new Vector2(40, 0);
66 |
67 | height += ChatManager.GetStringSize(font, "{Dummy}\n" + tooltip, Vector2.One).Y + 16;
68 |
69 | if (pos.Y + height > Main.screenHeight)
70 | pos.Y -= height;
71 |
72 | Utils.DrawInvBG(Main.spriteBatch, new Rectangle((int)pos.X - 10, (int)pos.Y - 10, (int)width + 20, (int)height + 20), new Color(20, 20, 55) * 0.925f);
73 |
74 | Utils.DrawBorderString(Main.spriteBatch, text, pos, Color.White);
75 | pos.Y += ChatManager.GetStringSize(font, text, Vector2.One).Y + 4;
76 |
77 | Utils.DrawBorderString(Main.spriteBatch, tooltip, pos, Color.LightGray, 0.9f);
78 | }
79 |
80 | private void Reset(On_Main.orig_DrawInterface orig, Main self, GameTime gameTime)
81 | {
82 | orig(self, gameTime);
83 |
84 | //reset
85 | text = string.Empty;
86 | tooltip = string.Empty;
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/Content/GUI/UIRenderer.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.Content.Items;
2 | using Terraria.DataStructures;
3 |
4 | namespace StructureHelper.Content.GUI
5 | {
6 | public class UIRenderer : ModSystem
7 | {
8 | public override void Load()
9 | {
10 | On_Main.DrawInterface += DrawSelection;
11 | }
12 |
13 | private void DrawSelection(On_Main.orig_DrawInterface orig, Main self, GameTime gameTime)
14 | {
15 | SpriteBatch spriteBatch = Main.spriteBatch;
16 |
17 | if (Main.LocalPlayer?.HeldItem?.ModItem is StructureWand)
18 | {
19 | var wand = Main.LocalPlayer.HeldItem.ModItem as StructureWand;
20 |
21 | spriteBatch.Begin(default, default, SamplerState.PointClamp, default, default, default, Main.GameViewMatrix.ZoomMatrix);
22 |
23 | Texture2D tex = Assets.corner.Value;
24 | Texture2D tex2 = Assets.box.Value;
25 |
26 | Point16 topLeft = wand.TopLeft;
27 | Point16 bottomRight = wand.BottomRight;
28 |
29 | bool drawPreview = true;
30 |
31 | if (wand.secondPoint)
32 | {
33 | Point16 point1 = wand.point1;
34 | var point2 = (Main.MouseWorld / 16).ToPoint16();
35 |
36 | topLeft = new Point16(point1.X < point2.X ? point1.X : point2.X, point1.Y < point2.Y ? point1.Y : point2.Y);
37 | bottomRight = new Point16(point1.X > point2.X ? point1.X : point2.X, point1.Y > point2.Y ? point1.Y : point2.Y);
38 | int Width = bottomRight.X - topLeft.X - 1;
39 | int Height = bottomRight.Y - topLeft.Y - 1;
40 |
41 | var target = new Rectangle((int)(topLeft.X * 16 - Main.screenPosition.X), (int)(topLeft.Y * 16 - Main.screenPosition.Y), Width * 16 + 16, Height * 16 + 16);
42 | Helpers.GUIHelper.DrawOutline(spriteBatch, target, Color.Gold);
43 | spriteBatch.Draw(tex2, target, tex2.Frame(), Color.White * 0.15f);
44 |
45 | spriteBatch.Draw(tex, wand.point1.ToVector2() * 16 - Main.screenPosition, tex.Frame(), new Color(50, 200, 255), 0, tex.Frame().Size() / 2, 1, 0, 0);
46 | spriteBatch.Draw(tex, wand.point1.ToVector2() * 16 - Main.screenPosition, tex.Frame(), new Color(155, 155, 50, 0), 0, tex.Frame().Size() / 2, 1, 0, 0);
47 | }
48 | else if (wand.Ready)
49 | {
50 | int Width = bottomRight.X - topLeft.X - 1;
51 | int Height = bottomRight.Y - topLeft.Y - 1;
52 |
53 | var target = new Rectangle((int)(topLeft.X * 16 - Main.screenPosition.X), (int)(topLeft.Y * 16 - Main.screenPosition.Y), Width * 16 + 16, Height * 16 + 16);
54 | Helpers.GUIHelper.DrawOutline(spriteBatch, target, Color.Lerp(Color.Gold, Color.White, 0.5f + 0.5f * (float)System.Math.Sin(Main.GameUpdateCount * 0.2f)));
55 | spriteBatch.Draw(tex2, target, tex2.Frame(), Color.White * 0.15f);
56 |
57 | float scale1 = Vector2.Distance(Main.MouseWorld, wand.point1.ToVector2() * 16) < 32 ? 1.5f : 1f;
58 | spriteBatch.Draw(tex, wand.point1.ToVector2() * 16 - Main.screenPosition, tex.Frame(), new Color(50, 200, 255) * scale1, 0, tex.Frame().Size() / 2, scale1, 0, 0);
59 | spriteBatch.Draw(tex, wand.point1.ToVector2() * 16 - Main.screenPosition, tex.Frame(), new Color(155, 155, 50, 0), 0, tex.Frame().Size() / 2, scale1, 0, 0);
60 |
61 | float scale2 = Vector2.Distance(Main.MouseWorld, wand.point2.ToVector2() * 16) < 32 ? 1.5f : 1f;
62 | spriteBatch.Draw(tex, wand.point2.ToVector2() * 16 - Main.screenPosition, tex.Frame(), new Color(255, 50, 50) * scale2, 0, tex.Frame().Size() / 2, scale2, 0, 0);
63 | spriteBatch.Draw(tex, wand.point2.ToVector2() * 16 - Main.screenPosition, tex.Frame(), new Color(255, 110, 110, 0), 0, tex.Frame().Size() / 2, scale2, 0, 0);
64 |
65 | if (scale1 > 1 || scale2 > 1)
66 | drawPreview = false;
67 | }
68 |
69 | if (drawPreview)
70 | {
71 | var pos = (Main.MouseWorld / 16).ToPoint16();
72 | spriteBatch.Draw(tex, pos.ToVector2() * 16 - Main.screenPosition, tex.Frame(), Color.White * 0.5f, 0, tex.Frame().Size() / 2, 1, 0, 0);
73 | }
74 |
75 | spriteBatch.End();
76 | }
77 |
78 | orig(self, gameTime);
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/Content/Items/ChestWand.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.ChestHelper;
2 | using StructureHelper.Content.GUI.ChestGUI;
3 | using StructureHelper.Core.Loaders.UILoading;
4 | using Terraria.DataStructures;
5 | using Terraria.ID;
6 |
7 | namespace StructureHelper.Content.Items
8 | {
9 | class ChestWand : ModItem
10 | {
11 | public override string Texture => "StructureHelper/Assets/Items/" + Name;
12 |
13 | public override void SetStaticDefaults()
14 | {
15 | DisplayName.SetDefault("Chest Wand");
16 | Tooltip.SetDefault("Right click to open the chest rule menu\nLeft click a chest to set the current rules on it\nRight click a chest with rules to copy them");
17 | }
18 |
19 | public override void SetDefaults()
20 | {
21 | Item.useStyle = ItemUseStyleID.Swing;
22 | Item.useTime = 20;
23 | Item.useAnimation = 20;
24 | Item.rare = ItemRarityID.Blue;
25 | }
26 |
27 | public override bool AltFunctionUse(Player player)
28 | {
29 | return true;
30 | }
31 |
32 | public override bool? UseItem(Player player)
33 | {
34 | Tile tile = Framing.GetTileSafely(Player.tileTargetX, Player.tileTargetY);
35 |
36 | if (tile.TileType == TileID.Containers || TileID.Sets.BasicChest[tile.TileType])
37 | {
38 | int xOff = tile.TileFrameX % 36 / 18;
39 | int yOff = tile.TileFrameY % 36 / 18;
40 |
41 | if (player.altFunctionUse == 2)
42 | {
43 | if (TileEntity.ByPosition.ContainsKey(new Point16(Player.tileTargetX - xOff, Player.tileTargetY - yOff)))
44 | {
45 | var chestEntity = TileEntity.ByPosition[new Point16(Player.tileTargetX - xOff, Player.tileTargetY - yOff)] as ChestEntity;
46 |
47 | UILoader.GetUIState().ruleElements.Clear();
48 |
49 | for (int k = 0; k < chestEntity.rules.Count; k++)
50 | {
51 | ChestRule rule = chestEntity.rules[k].Clone();
52 |
53 | var elem = new ChestRuleElement(rule);
54 |
55 | if (rule is ChestRuleGuaranteed)
56 | elem = new GuaranteedRuleElement(rule);
57 |
58 | if (rule is ChestRuleChance)
59 | elem = new ChanceRuleElement(rule);
60 |
61 | if (rule is ChestRulePool)
62 | elem = new PoolRuleElement(rule);
63 |
64 | if (rule is ChestRulePoolChance)
65 | elem = new PoolChanceRuleElement(rule);
66 |
67 | UILoader.GetUIState().ruleElements.Add(elem);
68 | }
69 | }
70 | else
71 | {
72 | UILoader.GetUIState().ruleElements.Clear();
73 | }
74 |
75 | Main.NewText($"Copied chest rules from chest at {new Point16(Player.tileTargetX - xOff, Player.tileTargetY - yOff)}");
76 | }
77 | else
78 | {
79 | bool overwrite = TileEntity.ByPosition.ContainsKey(new Point16(Player.tileTargetX - xOff, Player.tileTargetY - yOff));
80 |
81 | TileEntity.PlaceEntityNet(Player.tileTargetX - xOff, Player.tileTargetY - yOff, ModContent.TileEntityType());
82 | bool cleared = !UILoader.GetUIState().SetData(TileEntity.ByPosition[new Point16(Player.tileTargetX - xOff, Player.tileTargetY - yOff)] as ChestEntity);
83 |
84 | if (overwrite)
85 | {
86 | if (cleared)
87 | Main.NewText($"Removed chest rules for chest at {new Point16(Player.tileTargetX - xOff, Player.tileTargetY - yOff)}", Color.Orange);
88 | else
89 | Main.NewText($"Overwritten chest rules for chest at {new Point16(Player.tileTargetX - xOff, Player.tileTargetY - yOff)}", Color.Yellow);
90 | }
91 | else if (!cleared)
92 | {
93 | Main.NewText($"Set chest rules for chest at {new Point16(Player.tileTargetX - xOff, Player.tileTargetY - yOff)}", Color.GreenYellow);
94 | }
95 | }
96 | }
97 |
98 | if (player.altFunctionUse == 2)
99 | UILoader.GetUIState().Visible = true;
100 |
101 | return true;
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/Content/Items/MultistructureWand.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.API.Legacy;
2 | using StructureHelper.Content.GUI;
3 | using StructureHelper.Models;
4 | using System.Collections.Generic;
5 | using Terraria.ModLoader.IO;
6 |
7 | namespace StructureHelper.Content.Items
8 | {
9 | class MultistructureWand : StructureWand
10 | {
11 | internal List capturedData = [];
12 |
13 | public override string Texture => "StructureHelper/Assets/Items/" + Name;
14 |
15 | public override void SetStaticDefaults()
16 | {
17 | DisplayName.SetDefault("Multistructure Wand");
18 | Tooltip.SetDefault("Select 2 points in the world, then right click to add a structure. Right click in your inventory when done to save.");
19 | }
20 |
21 | public override bool CanRightClick()
22 | {
23 | return true;
24 | }
25 |
26 | public override ModItem Clone(Item newEntity)
27 | {
28 | var clone = base.Clone(newEntity) as MultistructureWand;
29 | clone.capturedData = [];
30 | return clone;
31 | }
32 |
33 | public override void RightClick(Player player)
34 | {
35 | Item.stack++;
36 |
37 | if (capturedData.Count > 1)
38 | NameConfirmPopup.OpenConfirmation((name) => API.Saver.SaveMultistructureToFile(MultiStructureData.FromStructureList(capturedData), name: name));
39 | else
40 | Main.NewText("Not enough structures! If you want to save a single structure, use the normal structure wand instead!", Color.Red);
41 | }
42 |
43 | public override void OnConfirmRectangle()
44 | {
45 | capturedData.Add(API.Saver.SaveToStructureData(TopLeft.X, TopLeft.Y, Width, Height));
46 | Main.NewText("Structure captured! Total structures to save: " + capturedData.Count);
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/Content/Items/PortingWand.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.NewFolder;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using Terraria.DataStructures;
9 | using Terraria.ID;
10 |
11 | namespace StructureHelper.Content.Items
12 | {
13 | internal class PortingWand : ModItem
14 | {
15 | public override string Texture => "StructureHelper/Assets/Items/" + Name;
16 |
17 | public override void SetStaticDefaults()
18 | {
19 | DisplayName.SetDefault("Porting Wand");
20 | Tooltip.SetDefault("Automatically ports every structure in your SavedStructures directory.");
21 | }
22 |
23 | public override void SetDefaults()
24 | {
25 | Item.useStyle = ItemUseStyleID.Swing;
26 | Item.useTime = 20;
27 | Item.useAnimation = 20;
28 | Item.rare = ItemRarityID.Blue;
29 | Item.noMelee = true;
30 | Item.noUseGraphic = true;
31 | }
32 |
33 | public override bool? UseItem(Player player)
34 | {
35 | string folderPath = ModLoader.ModPath.Replace("Mods", "SavedStructures");
36 | Directory.CreateDirectory(folderPath);
37 |
38 | Porter.PortDirectory(folderPath);
39 | return true;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Content/Items/StructureWand.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.API;
2 | using StructureHelper.Content.GUI;
3 | using StructureHelper.Models;
4 | using Terraria.DataStructures;
5 | using Terraria.ID;
6 |
7 | namespace StructureHelper.Content.Items
8 | {
9 | class StructureWand : ModItem
10 | {
11 | public bool secondPoint;
12 |
13 | public Point16 point1;
14 | public Point16 point2;
15 |
16 | public bool movePoint1;
17 | public bool movePoint2;
18 |
19 | public Point16 TopLeft => new(point1.X < point2.X ? point1.X : point2.X, point1.Y < point2.Y ? point1.Y : point2.Y);
20 | public Point16 BottomRight => new(point1.X > point2.X ? point1.X : point2.X, point1.Y > point2.Y ? point1.Y : point2.Y);
21 | public int Width => BottomRight.X - TopLeft.X;
22 | public int Height => BottomRight.Y - TopLeft.Y;
23 |
24 | public bool Ready => !secondPoint && point1 != default;
25 |
26 | public override string Texture => "StructureHelper/Assets/Items/" + Name;
27 |
28 | public override void SetStaticDefaults()
29 | {
30 | DisplayName.SetDefault("Structure Wand");
31 | Tooltip.SetDefault("Select 2 points in the world, then right click to save a structure");
32 | }
33 |
34 | public override void SetDefaults()
35 | {
36 | Item.useStyle = ItemUseStyleID.Swing;
37 | Item.useTime = 20;
38 | Item.useAnimation = 20;
39 | Item.rare = ItemRarityID.Blue;
40 | Item.noMelee = true;
41 | Item.noUseGraphic = true;
42 | }
43 |
44 | public override bool AltFunctionUse(Player player)
45 | {
46 | return true;
47 | }
48 |
49 | public override void HoldItem(Player player)
50 | {
51 | if (movePoint1)
52 | point1 = (Main.MouseWorld / 16).ToPoint16();
53 |
54 | if (movePoint2)
55 | point2 = (Main.MouseWorld / 16).ToPoint16();
56 |
57 | if (!Main.mouseLeft)
58 | {
59 | movePoint1 = false;
60 | movePoint2 = false;
61 | }
62 | }
63 |
64 | ///
65 | /// What happens when you right click after a finazed rectangle is present
66 | ///
67 | public virtual void OnConfirmRectangle()
68 | {
69 | NameConfirmPopup.OpenConfirmation((name) =>
70 | {
71 | Saver.SaveToFile(StructureData.FromWorld(TopLeft.X, TopLeft.Y, Width, Height), null, name);
72 | });
73 |
74 |
75 | //NameConfirmPopup.OpenConfirmation((name) => LegacySaver.SaveToFile(new Rectangle(TopLeft.X, TopLeft.Y, Width - 1, Height - 1), name: name));
76 | }
77 |
78 | public override bool? UseItem(Player player)
79 | {
80 | if (player.altFunctionUse == 2 && Ready)
81 | {
82 | OnConfirmRectangle();
83 | return true;
84 | }
85 |
86 | if (Ready)
87 | {
88 | if (Vector2.Distance(Main.MouseWorld, point1.ToVector2() * 16) <= 32)
89 | {
90 | movePoint1 = true;
91 | return true;
92 | }
93 |
94 | if (Vector2.Distance(Main.MouseWorld, point2.ToVector2() * 16) <= 32)
95 | {
96 | movePoint2 = true;
97 | return true;
98 | }
99 | }
100 |
101 | if (!secondPoint)
102 | {
103 | point1 = (Main.MouseWorld / 16).ToPoint16();
104 | point2 = default;
105 |
106 | Main.NewText("Select Second Point");
107 | secondPoint = true;
108 | }
109 | else
110 | {
111 | point2 = (Main.MouseWorld / 16).ToPoint16();
112 |
113 | Main.NewText("Ready to save! Right click to save this structure...");
114 | secondPoint = false;
115 | }
116 |
117 | return true;
118 | }
119 | }
120 | }
--------------------------------------------------------------------------------
/Content/Items/TestWand.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.Content.GUI;
2 | using Terraria.DataStructures;
3 | using Terraria.ID;
4 |
5 | namespace StructureHelper.Content.Items
6 | {
7 | class TestWand : ModItem
8 | {
9 | public static bool ignoreNulls = false;
10 | public static bool UIVisible;
11 |
12 | public override string Texture => "StructureHelper/Assets/Items/" + Name;
13 |
14 | public override void Load()
15 | {
16 | On_Main.DrawPlayers_AfterProjectiles += DrawPreview;
17 | }
18 |
19 | public override void SetStaticDefaults()
20 | {
21 | DisplayName.SetDefault("Structure Placer Wand");
22 | Tooltip.SetDefault("left click to place the selected structure, right click to open the structure selector");
23 | }
24 |
25 | public override void SetDefaults()
26 | {
27 | Item.useStyle = ItemUseStyleID.Swing;
28 | Item.useTime = 20;
29 | Item.useAnimation = 20;
30 | Item.rare = ItemRarityID.Blue;
31 | }
32 |
33 | public override bool AltFunctionUse(Player player)
34 | {
35 | return true;
36 | }
37 |
38 | public override bool? UseItem(Player player)
39 | {
40 | if (player.altFunctionUse == 2)
41 | {
42 | ManualGeneratorMenu.LoadStructures();
43 | UIVisible = !UIVisible;
44 | return true;
45 | }
46 |
47 | if (ManualGeneratorMenu.selected != null)
48 | {
49 | var pos = new Point16(Player.tileTargetX, Player.tileTargetY);
50 |
51 | if (ManualGeneratorMenu.multiMode)
52 | API.MultiStructureGenerator.GenerateMultistructureSpecific(ManualGeneratorMenu.selected.path, ManualGeneratorMenu.multiIndex, pos, StructureHelper.Instance, true, ManualGeneratorMenu.ignoreNulls, ManualGeneratorMenu.flags);
53 | else
54 | API.Generator.GenerateStructure(ManualGeneratorMenu.selected.path, pos, StructureHelper.Instance, true, ManualGeneratorMenu.ignoreNulls, ManualGeneratorMenu.flags);
55 | }
56 | else
57 | {
58 | Main.NewText("No structure selected! Right click and select a structure from the menu to generate it.", Color.Red);
59 | }
60 |
61 | return true;
62 | }
63 |
64 | private void DrawPreview(On_Main.orig_DrawPlayers_AfterProjectiles orig, Main self)
65 | {
66 | orig(self);
67 |
68 | if (ManualGeneratorMenu.selected != null && ManualGeneratorMenu.preview != null && Main.LocalPlayer.HeldItem.type == Item.type)
69 | {
70 | Main.spriteBatch.Begin(default, default, default, default, default, default, Main.GameViewMatrix.TransformationMatrix);
71 |
72 | var pos = new Point16(Player.tileTargetX, Player.tileTargetY);
73 | Vector2 pos2 = pos.ToVector2() * 16 - Main.screenPosition;
74 |
75 | Helpers.GUIHelper.DrawBox(Main.spriteBatch, new Rectangle((int)pos2.X - 4, (int)pos2.Y - 4, ManualGeneratorMenu.preview.Width + 8, ManualGeneratorMenu.preview.Height + 8), Color.Red * 0.5f);
76 |
77 | if (ManualGeneratorMenu.preview?.preview != null)
78 | Main.spriteBatch.Draw(ManualGeneratorMenu.preview?.preview, pos2, Color.White * 0.5f);
79 |
80 | Main.spriteBatch.End();
81 | }
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/Content/Tiles/NullBlock.cs:
--------------------------------------------------------------------------------
1 | using Terraria.ID;
2 |
3 | namespace StructureHelper.Content.Tiles
4 | {
5 | class NullBlockFraming : ModSystem
6 | {
7 | public override void Load()
8 | {
9 | On_WorldGen.SlopeTile += SlopeTileHook;
10 | }
11 |
12 | private static bool SlopeTileHook(On_WorldGen.orig_SlopeTile orig, int i, int j, int slope, bool noEffects)
13 | {
14 | bool isNeighborNull = false;
15 |
16 | isNeighborNull |= Framing.GetTileSafely(i + 1, j).TileType == ModContent.TileType();
17 | isNeighborNull |= Framing.GetTileSafely(i - 1, j).TileType == ModContent.TileType();
18 | isNeighborNull |= Framing.GetTileSafely(i, j + 1).TileType == ModContent.TileType();
19 | isNeighborNull |= Framing.GetTileSafely(i, j - 1).TileType == ModContent.TileType();
20 |
21 | if (isNeighborNull)
22 | return false;
23 |
24 | return orig(i, j, slope, noEffects);
25 | }
26 | }
27 |
28 | class NullBlock : ModTile
29 | {
30 | public override string Texture => "StructureHelper/Assets/Tiles/" + Name;
31 |
32 | public override void SetStaticDefaults()
33 | {
34 | Main.tileSolid[Type] = true;
35 | TileID.Sets.DrawsWalls[Type] = true;
36 | }
37 |
38 | public override bool Slope(int i, int j) { return false; }
39 |
40 | public override bool CanDrop(int i, int j) { return false; }
41 | }
42 |
43 | class NullWall : ModWall
44 | {
45 | public override string Texture => "StructureHelper/Assets/Tiles/" + Name;
46 |
47 | public override bool Drop(int i, int j, ref int type) { return false; }
48 | }
49 |
50 | class NullBlockItem : ModItem
51 | {
52 | public override string Texture => "StructureHelper/Assets/Tiles/" + Name;
53 |
54 | public override void SetStaticDefaults()
55 | {
56 | DisplayName.SetDefault("Null Block");
57 | Tooltip.SetDefault("Use these in a structure to indicate where the generator\n should leave whatever already exists in the world untouched\n ignores walls, use null walls for that :3");
58 | }
59 |
60 | public override void SetDefaults()
61 | {
62 | Item.width = 16;
63 | Item.height = 16;
64 | Item.maxStack = 1;
65 | Item.useTurn = true;
66 | Item.autoReuse = true;
67 | Item.useAnimation = 2;
68 | Item.useTime = 2;
69 | Item.useStyle = ItemUseStyleID.Swing;
70 | Item.rare = ItemRarityID.Yellow;
71 | Item.createTile = ModContent.TileType();
72 | }
73 | }
74 |
75 | class NullWallItem : ModItem
76 | {
77 | public override string Texture => "StructureHelper/Assets/Tiles/" + Name;
78 |
79 | public override void SetStaticDefaults()
80 | {
81 | DisplayName.SetDefault("Null Wall");
82 | Tooltip.SetDefault("Use these in a structure to indicate where the generator\n should leave walls that already exists in the world untouched\n for walls only, use null blocks for other things");
83 | }
84 |
85 | public override void SetDefaults()
86 | {
87 | Item.width = 16;
88 | Item.height = 16;
89 | Item.maxStack = 1;
90 | Item.useTurn = true;
91 | Item.autoReuse = true;
92 | Item.useAnimation = 2;
93 | Item.useTime = 2;
94 | Item.useStyle = ItemUseStyleID.Swing;
95 | Item.rare = ItemRarityID.Yellow;
96 | Item.createWall = ModContent.WallType();
97 | }
98 | }
99 |
100 | class NullTileAndWallPlacer : ModItem
101 | {
102 | public override string Texture => "StructureHelper/Assets/Tiles/" + Name;
103 |
104 | public override void SetStaticDefaults()
105 | {
106 | DisplayName.SetDefault("Null Tile Place-O-Matic");
107 | Tooltip.SetDefault("Places a null tile and null wall at the same time!");
108 | }
109 |
110 | public override void SetDefaults()
111 | {
112 | Item.width = 16;
113 | Item.height = 16;
114 | Item.maxStack = 1;
115 | Item.useTurn = true;
116 | Item.autoReuse = true;
117 | Item.useAnimation = 2;
118 | Item.useTime = 2;
119 | Item.useStyle = ItemUseStyleID.Swing;
120 | Item.rare = ItemRarityID.Yellow;
121 | Item.createTile = ModContent.TileType();
122 | Item.createWall = ModContent.WallType();
123 | }
124 | }
125 | }
--------------------------------------------------------------------------------
/Core/Loaders/UILoading/SmartUIElement.cs:
--------------------------------------------------------------------------------
1 | using Terraria.UI;
2 |
3 | namespace StructureHelper.Core.Loaders.UILoading
4 | {
5 | ///
6 | /// Wrapper class that enforces Safe interoperability between event listeners and virtual methods for UI elements
7 | ///
8 | public class SmartUIElement : UIElement
9 | {
10 | #region XButton1
11 | ///
12 | /// A Safe wrapper around XButton1MouseUp that allows both an override and the OnXButton1MouseUp event to be used together
13 | ///
14 | /// The mouse event that occured to fire this listener
15 | public virtual void SafeXButton1MouseUp(UIMouseEvent evt) { }
16 |
17 | public sealed override void XButton1MouseUp(UIMouseEvent evt)
18 | {
19 | base.XButton1MouseUp(evt);
20 | SafeXButton1MouseUp(evt);
21 | }
22 |
23 | ///
24 | /// A Safe wrapper around XButton1MouseDown that allows both an override and the OnXButton1MouseDown event to be used together
25 | ///
26 | /// The mouse event that occured to fire this listener
27 | public virtual void SafeXButton1MouseDown(UIMouseEvent evt) { }
28 |
29 | public sealed override void XButton1MouseDown(UIMouseEvent evt)
30 | {
31 | base.XButton1MouseDown(evt);
32 | SafeXButton1MouseDown(evt);
33 | }
34 |
35 | ///
36 | /// A Safe wrapper around XButton1Click that allows both an override and the OnXButton1Click event to be used together
37 | ///
38 | /// The mouse event that occured to fire this listener
39 | public virtual void SafeXButton1Click(UIMouseEvent evt) { }
40 |
41 | public sealed override void XButton1Click(UIMouseEvent evt)
42 | {
43 | base.XButton1Click(evt);
44 | SafeXButton1Click(evt);
45 | }
46 |
47 | ///
48 | /// A Safe wrapper around XButton1DoubleClick that allows both an override and the OnXButton1DoubleClick event to be used together
49 | ///
50 | /// The mouse event that occured to fire this listener
51 | public virtual void SafeXButton1DoubleClick(UIMouseEvent evt) { }
52 |
53 | public sealed override void XButton1DoubleClick(UIMouseEvent evt)
54 | {
55 | base.XButton1DoubleClick(evt);
56 | SafeXButton1DoubleClick(evt);
57 | }
58 | #endregion
59 |
60 | #region XButton2
61 | ///
62 | /// A Safe wrapper around XButton2MouseUp that allows both an override and the OnXButton2MouseUp event to be used together
63 | ///
64 | /// The mouse event that occured to fire this listener
65 | public virtual void SafeXButton2MouseUp(UIMouseEvent evt) { }
66 |
67 | public sealed override void XButton2MouseUp(UIMouseEvent evt)
68 | {
69 | base.XButton2MouseUp(evt);
70 | SafeXButton2MouseUp(evt);
71 | }
72 |
73 | ///
74 | /// A Safe wrapper around XButton2MouseDown that allows both an override and the OnXButton2MouseDown event to be used together
75 | ///
76 | /// The mouse event that occured to fire this listener
77 | public virtual void SafeXButton2MouseDown(UIMouseEvent evt) { }
78 |
79 | public sealed override void XButton2MouseDown(UIMouseEvent evt)
80 | {
81 | base.XButton2MouseDown(evt);
82 | SafeXButton2MouseDown(evt);
83 | }
84 |
85 | ///
86 | /// A Safe wrapper around XButton2Click that allows both an override and the OnXButton2Click event to be used together
87 | ///
88 | /// The mouse event that occured to fire this listener
89 | public virtual void SafeXButton2Click(UIMouseEvent evt) { }
90 |
91 | public sealed override void XButton2Click(UIMouseEvent evt)
92 | {
93 | base.XButton2Click(evt);
94 | SafeXButton2Click(evt);
95 | }
96 |
97 | ///
98 | /// A Safe wrapper around XButton2DoubleClick that allows both an override and the OnXButton2DoubleClick event to be used together
99 | ///
100 | /// The mouse event that occured to fire this listener
101 | public virtual void SafeXButton2DoubleClick(UIMouseEvent evt) { }
102 |
103 | public sealed override void XButton2DoubleClick(UIMouseEvent evt)
104 | {
105 | base.XButton2DoubleClick(evt);
106 | SafeXButton2DoubleClick(evt);
107 | }
108 | #endregion
109 |
110 | #region LMB
111 | ///
112 | /// A Safe wrapper around MouseUp that allows both an override and the OnMouseUp event to be used together
113 | ///
114 | /// The mouse event that occured to fire this listener
115 | public virtual void SafeMouseUp(UIMouseEvent evt) { }
116 |
117 | public sealed override void LeftMouseUp(UIMouseEvent evt)
118 | {
119 | base.LeftMouseUp(evt);
120 | SafeMouseUp(evt);
121 | }
122 |
123 | ///
124 | /// A Safe wrapper around MouseDown that allows both an override and the OnMouseDown event to be used together
125 | ///
126 | /// The mouse event that occured to fire this listener
127 | public virtual void SafeMouseDown(UIMouseEvent evt) { }
128 |
129 | public sealed override void LeftMouseDown(UIMouseEvent evt)
130 | {
131 | base.LeftMouseDown(evt);
132 | SafeMouseDown(evt);
133 | }
134 |
135 | ///
136 | /// A Safe wrapper around Click that allows both an override and the OnClick event to be used together
137 | ///
138 | /// The mouse event that occured to fire this listener
139 | public virtual void SafeClick(UIMouseEvent evt) { }
140 |
141 | public sealed override void LeftClick(UIMouseEvent evt)
142 | {
143 | base.LeftClick(evt);
144 | SafeClick(evt);
145 | }
146 |
147 | ///
148 | /// A Safe wrapper around DoubleClick that allows both an override and the OnDoubleClick event to be used together
149 | ///
150 | /// The mouse event that occured to fire this listener
151 | public virtual void SafeDoubleClick(UIMouseEvent evt) { }
152 |
153 | public sealed override void LeftDoubleClick(UIMouseEvent evt)
154 | {
155 | base.LeftDoubleClick(evt);
156 | SafeDoubleClick(evt);
157 | }
158 | #endregion
159 |
160 | #region RMB
161 | ///
162 | /// A Safe wrapper around RightMouseUp that allows both an override and the OnRightMouseUp event to be used together
163 | ///
164 | /// The mouse event that occured to fire this listener
165 | public virtual void SafeRightMouseUp(UIMouseEvent evt) { }
166 |
167 | public sealed override void RightMouseUp(UIMouseEvent evt)
168 | {
169 | base.RightMouseUp(evt);
170 | SafeRightMouseUp(evt);
171 | }
172 |
173 | ///
174 | /// A Safe wrapper around RightMouseDown that allows both an override and the OnRightMouseDown event to be used together
175 | ///
176 | /// The mouse event that occured to fire this listener
177 | public virtual void SafeRightMouseDown(UIMouseEvent evt) { }
178 |
179 | public sealed override void RightMouseDown(UIMouseEvent evt)
180 | {
181 | base.RightMouseDown(evt);
182 | SafeRightMouseDown(evt);
183 | }
184 |
185 | ///
186 | /// A Safe wrapper around RightClick that allows both an override and the OnRightClick event to be used together
187 | ///
188 | /// The mouse event that occured to fire this listener
189 | public virtual void SafeRightClick(UIMouseEvent evt) { }
190 |
191 | public sealed override void RightClick(UIMouseEvent evt)
192 | {
193 | base.RightClick(evt);
194 | SafeRightClick(evt);
195 | }
196 |
197 | ///
198 | /// A Safe wrapper around RightDoubleClick that allows both an override and the OnRightDoubleClick event to be used together
199 | ///
200 | /// The mouse event that occured to fire this listener
201 | public virtual void SafeRightDoubleClick(UIMouseEvent evt) { }
202 |
203 | public sealed override void RightDoubleClick(UIMouseEvent evt)
204 | {
205 | base.RightDoubleClick(evt);
206 | SafeRightDoubleClick(evt);
207 | }
208 | #endregion
209 |
210 | #region MMB
211 | ///
212 | /// A Safe wrapper around MiddleMouseUp that allows both an override and the OnMiddleMouseUp event to be used together
213 | ///
214 | /// The mouse event that occured to fire this listener
215 | public virtual void SafeMiddleMouseUp(UIMouseEvent evt) { }
216 |
217 | public sealed override void MiddleMouseUp(UIMouseEvent evt)
218 | {
219 | base.MiddleMouseUp(evt);
220 | SafeMiddleMouseUp(evt);
221 | }
222 |
223 | ///
224 | /// A Safe wrapper around MiddleMouseDown that allows both an override and the OnMiddleMouseDown event to be used together
225 | ///
226 | /// The mouse event that occured to fire this listener
227 | public virtual void SafeMiddleMouseDown(UIMouseEvent evt) { }
228 |
229 | public sealed override void MiddleMouseDown(UIMouseEvent evt)
230 | {
231 | base.MiddleMouseDown(evt);
232 | SafeMiddleMouseDown(evt);
233 | }
234 |
235 | ///
236 | /// A Safe wrapper around MiddleClick that allows both an override and the OnMiddleClick event to be used together
237 | ///
238 | /// The mouse event that occured to fire this listener
239 | public virtual void SafeMiddleClick(UIMouseEvent evt) { }
240 |
241 | public sealed override void MiddleClick(UIMouseEvent evt)
242 | {
243 | base.MiddleClick(evt);
244 | SafeMiddleClick(evt);
245 | }
246 |
247 | ///
248 | /// A Safe wrapper around MiddleDoubleClick that allows both an override and the OnMiddleDoubleClick event to be used together
249 | ///
250 | /// The mouse event that occured to fire this listener
251 | public virtual void SafeMiddleDoubleClick(UIMouseEvent evt) { }
252 |
253 | public sealed override void MiddleDoubleClick(UIMouseEvent evt)
254 | {
255 | base.MiddleDoubleClick(evt);
256 | SafeMiddleDoubleClick(evt);
257 | }
258 | #endregion
259 |
260 | #region Misc
261 | ///
262 | /// A Safe wrapper around MouseOver that allows both an override and the OnMouseOver event to be used together
263 | ///
264 | /// The mouse event that occured to fire this listener
265 | public virtual void SafeMouseOver(UIMouseEvent evt) { }
266 |
267 | public sealed override void MouseOver(UIMouseEvent evt)
268 | {
269 | base.MouseOver(evt);
270 | SafeMouseOver(evt);
271 | }
272 |
273 | ///
274 | /// A Safe wrapper around Update that allows both an override and the OnUpdate event to be used together
275 | ///
276 | /// The mouse event that occured to fire this listener
277 | public virtual void SafeUpdate(GameTime gameTime) { }
278 |
279 | public sealed override void Update(GameTime gameTime)
280 | {
281 | base.Update(gameTime);
282 | SafeUpdate(gameTime);
283 | }
284 |
285 | ///
286 | /// A Safe wrapper around ScrollWheel that allows both an override and the OnScrollWheel event to be used together
287 | ///
288 | /// The mouse event that occured to fire this listener
289 | public virtual void SafeScrollWheel(UIScrollWheelEvent evt) { }
290 |
291 | public sealed override void ScrollWheel(UIScrollWheelEvent evt)
292 | {
293 | base.ScrollWheel(evt);
294 | SafeScrollWheel(evt);
295 | }
296 | #endregion
297 | }
298 | }
--------------------------------------------------------------------------------
/Core/Loaders/UILoading/UILoader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Terraria.UI;
5 |
6 | namespace StructureHelper.Core.Loaders.UILoading
7 | {
8 | ///
9 | /// Automatically loads SmartUIStates ala IoC.
10 | ///
11 | class UILoader : ModSystem
12 | {
13 | ///
14 | /// The collection of automatically craetaed UserInterfaces for SmartUIStates.
15 | ///
16 | public static List UserInterfaces = [];
17 |
18 | ///
19 | /// The collection of all automatically loaded SmartUIStates.
20 | ///
21 | public static List UIStates = [];
22 |
23 | ///
24 | /// Uses reflection to scan through and find all types extending SmartUIState that arent abstract, and loads an instance of them.
25 | ///
26 | public override void Load()
27 | {
28 | if (Main.dedServ)
29 | return;
30 |
31 | UserInterfaces = [];
32 | UIStates = [];
33 |
34 | foreach (Type t in Mod.Code.GetTypes())
35 | {
36 | if (!t.IsAbstract && t.IsSubclassOf(typeof(SmartUIState)))
37 | {
38 | var state = (SmartUIState)Activator.CreateInstance(t, null);
39 | var userInterface = new UserInterface();
40 | userInterface.SetState(state);
41 | state.UserInterface = userInterface;
42 |
43 | UIStates?.Add(state);
44 | UserInterfaces?.Add(userInterface);
45 | }
46 | }
47 | }
48 |
49 | public override void Unload()
50 | {
51 | UIStates.ForEach(n => n.Unload());
52 | UserInterfaces = null;
53 | UIStates = null;
54 | }
55 |
56 | ///
57 | /// Helper method for creating and inserting a LegacyGameInterfaceLayer automatically
58 | ///
59 | /// The vanilla layers
60 | /// the UIState to bind to the layer
61 | /// Where this layer should be inserted
62 | /// The logic dictating the visibility of this layer
63 | /// The scale settings this layer should scale with
64 | public static void AddLayer(List layers, UIState state, int index, bool visible, InterfaceScaleType scale)
65 | {
66 | string name = state == null ? "Unknown" : state.ToString();
67 | layers.Insert(index, new LegacyGameInterfaceLayer("BrickAndMortar: " + name,
68 | delegate
69 | {
70 | if (visible)
71 | state.Draw(Main.spriteBatch);
72 |
73 | return true;
74 | }, scale));
75 | }
76 |
77 | ///
78 | /// Handles updating the UI states correctly
79 | ///
80 | ///
81 | public override void UpdateUI(GameTime gameTime)
82 | {
83 | if (Main.ingameOptionsWindow || Main.InGameUI.IsVisible)
84 | return;
85 | foreach (UserInterface eachState in UserInterfaces)
86 | {
87 | if (eachState?.CurrentState != null && ((SmartUIState)eachState.CurrentState).Visible)
88 | eachState.Update(gameTime);
89 | }
90 | }
91 |
92 | ///
93 | /// Gets the autoloaded SmartUIState instance for a given SmartUIState subclass
94 | ///
95 | /// The SmartUIState subclass to get the instance of
96 | /// The autoloaded instance of the desired SmartUIState
97 | public static T GetUIState() where T : SmartUIState
98 | {
99 | return UIStates.FirstOrDefault(n => n is T) as T;
100 | }
101 |
102 | ///
103 | /// Forcibly reloads a SmartUIState and it's associated UserInterface
104 | ///
105 | /// The SmartUIState subclass to reload
106 | public static void ReloadState() where T : SmartUIState
107 | {
108 | int index = UIStates.IndexOf(GetUIState());
109 | UIStates[index] = (T)Activator.CreateInstance(typeof(T), null);
110 | UserInterfaces[index] = new UserInterface();
111 | UserInterfaces[index].SetState(UIStates[index]);
112 | }
113 |
114 | ///
115 | /// Handles the insertion of the automatically generated UIs
116 | ///
117 | ///
118 | public override void ModifyInterfaceLayers(List layers)
119 | {
120 | for (int k = 0; k < UIStates.Count; k++)
121 | {
122 | SmartUIState state = UIStates[k];
123 | AddLayer(layers, state, state.InsertionIndex(layers), state.Visible, state.Scale);
124 | }
125 | }
126 | }
127 | }
--------------------------------------------------------------------------------
/Generator.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.ChestHelper;
2 | using StructureHelper.Helpers;
3 | using StructureHelper.Models;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using Terraria.DataStructures;
8 | using Terraria.ID;
9 | using Terraria.ModLoader.IO;
10 |
11 | namespace StructureHelper
12 | {
13 | ///
14 | /// The legacy API left as is to prevent runtime breakage.
15 | ///
16 | public static class Generator
17 | {
18 | ///
19 | /// This method generates a structure from a structure file within your mod.
20 | ///
21 | /// The path to your structure file within your mod - this should not include your mod's folder, only the path beyond it.
22 | /// The position in the world in which you want your structure to generate, in tile coordinates.
23 | /// The instance of your mod to grab the file from.
24 | /// Indicates if you want to use a fully qualified path to get the structure file instead of one from your mod - generally should only be used for debugging.
25 | /// If the structure should repsect the normal behavior of null tiles or not. This should never be true if you're using the mod as a dll reference.
26 | /// Allows you to pass flags for special generation behavior. See
27 | /// If the structure generated successfully or not
28 | [Obsolete("Legacy generation API is deprecated. Please port your structures to the 3.0 format and use StructureHelper.API.Generator." +
29 | "\n Porting instructions can be found at https://github.com/ScalarVector1/StructureHelper/wiki/3.0-Porting-Guide" +
30 | "\n If you truly need legacy API access, use StructureHelper.API.Legacy.LegacyGenerator.", true)]
31 | public static bool GenerateStructure(string path, Point16 pos, Mod mod, bool fullPath = false, bool ignoreNull = false, GenFlags flags = GenFlags.None)
32 | {
33 | return API.Legacy.LegacyGenerator.GenerateStructure(path, pos, mod, fullPath, ignoreNull, flags);
34 | }
35 |
36 | ///
37 | /// This method generates a structure selected randomly from a multistructure file within your mod.
38 | ///
39 | /// The path to your multistructure file within your mod - this should not include your mod's folder, only the path beyond it.
40 | /// The position in the world in which you want your structure to generate, in tile coordinates.
41 | /// The instance of your mod to grab the file from.
42 | /// Indicates if you want to use a fully qualified path to get the structure file instead of one from your mod - generally should only be used for debugging.
43 | /// If the structure should repsect the normal behavior of null tiles or not. This should never be true if you're using the mod as a dll refference.
44 | /// Allows you to pass flags for special generation behavior. See
45 | /// If the structure generated successfully or not
46 | [Obsolete("Legacy generation API is deprecated. Please port your structures to the 3.0 format and use StructureHelper.API.Generator." +
47 | "\n Porting instructions can be found at https://github.com/ScalarVector1/StructureHelper/wiki/3.0-Porting-Guide" +
48 | "\n If you truly need legacy API access, use StructureHelper.API.Legacy.LegacyGenerator.", true)]
49 | public static bool GenerateMultistructureRandom(string path, Point16 pos, Mod mod, bool fullPath = false, bool ignoreNull = false, GenFlags flags = GenFlags.None)
50 | {
51 | return API.Legacy.LegacyGenerator.GenerateMultistructureRandom(path, pos, mod, fullPath, ignoreNull, flags);
52 | }
53 |
54 | ///
55 | /// This method generates a structure you select from a multistructure file within your mod. Useful if you want to do your own weighted randomization or want additional logic based on dimensions gotten from GetMultistructureDimensions.
56 | ///
57 | /// The path to your multistructure file within your mod - this should not include your mod's folder, only the path beyond it.
58 | /// The position in the world in which you want your structure to generate, in tile coordinates.
59 | /// The instance of your mod to grab the file from.
60 | /// The index of the structure you want to generate out of the multistructure file, structure indicies are 0-based and match the order they were saved in.
61 | /// Indicates if you want to use a fully qualified path to get the structure file instead of one from your mod - generally should only be used for debugging.
62 | /// If the structure should repsect the normal behavior of null tiles or not. This should never be true if you're using the mod as a dll refference.
63 | /// Allows you to pass flags for special generation behavior. See
64 | /// If the structure generated successfully or not
65 | [Obsolete("Legacy generation API is deprecated. Please port your structures to the 3.0 format and use StructureHelper.API.Generator." +
66 | "\n Porting instructions can be found at https://github.com/ScalarVector1/StructureHelper/wiki/3.0-Porting-Guide" +
67 | "\n If you truly need legacy API access, use StructureHelper.API.Legacy.LegacyGenerator.", true)]
68 | public static bool GenerateMultistructureSpecific(string path, Point16 pos, Mod mod, int index, bool fullPath = false, bool ignoreNull = false, GenFlags flags = GenFlags.None)
69 | {
70 | return API.Legacy.LegacyGenerator.GenerateMultistructureSpecific(path, pos, mod, index, fullPath, ignoreNull, flags);
71 | }
72 |
73 | ///
74 | /// Gets the dimensions of a structure from a structure file within your mod.
75 | ///
76 | /// The path to your structure file within your mod - this should not include your mod's folder, only the path beyond it.
77 | /// The instance of your mod to grab the file from.
78 | /// The Point16 variable which you want to be set to the dimensions of the structure.
79 | /// Indicates if you want to use a fully qualified path to get the structure file instead of one from your mod - generally should only be used for debugging.
80 | ///
81 | [Obsolete("Legacy generation API is deprecated. Please port your structures to the 3.0 format and use StructureHelper.API.Generator." +
82 | "\n Porting instructions can be found at https://github.com/ScalarVector1/StructureHelper/wiki/3.0-Porting-Guide" +
83 | "\n If you truly need legacy API access, use StructureHelper.API.Legacy.LegacyGenerator.", true)]
84 | public static bool GetDimensions(string path, Mod mod, ref Point16 dims, bool fullPath = false)
85 | {
86 | return API.Legacy.LegacyGenerator.GetDimensions(path, mod, ref dims, fullPath);
87 | }
88 |
89 | ///
90 | /// Gets the dimensions of a structure from a structure file within your mod.
91 | ///
92 | /// The path to your structure file within your mod - this should not include your mod's folder, only the path beyond it.
93 | /// The instance of your mod to grab the file from.
94 | /// The index of the structure you want to get the dimensions of out of the multistructure file, structure indicies are 0-based and match the order they were saved in.
95 | /// The Point16 variable which you want to be set to the dimensions of the structure.
96 | /// Indicates if you want to use a fully qualified path to get the structure file instead of one from your mod - generally should only be used for debugging.
97 | ///
98 | [Obsolete("Legacy generation API is deprecated. Please port your structures to the 3.0 format and use StructureHelper.API.Generator." +
99 | "\n Porting instructions can be found at https://github.com/ScalarVector1/StructureHelper/wiki/3.0-Porting-Guide" +
100 | "\n If you truly need legacy API access, use StructureHelper.API.Legacy.LegacyGenerator.", true)]
101 | public static bool GetMultistructureDimensions(string path, Mod mod, int index, ref Point16 dims, bool fullPath = false)
102 | {
103 | return API.Legacy.LegacyGenerator.GetMultistructureDimensions(path, mod, index, ref dims, fullPath);
104 | }
105 |
106 | ///
107 | /// Checks if a structure file is a multistructure or not. Can be used to easily add support for parameterizing strucutres or multistructures in your mod.
108 | ///
109 | /// The path to the structure file you wish to check.
110 | /// The instance of your mod to grab the file from.
111 | /// True if the file is a multistructure, False if the file is a structure, null if it is invalid.
112 | [Obsolete("Legacy generation API is deprecated. Please port your structures to the 3.0 format and use StructureHelper.API.Generator." +
113 | "\n Porting instructions can be found at https://github.com/ScalarVector1/StructureHelper/wiki/3.0-Porting-Guide" +
114 | "\n If you truly need legacy API access, use StructureHelper.API.Legacy.LegacyGenerator.", true)]
115 | public static bool? IsMultistructure(string path, Mod mod)
116 | {
117 | return API.Legacy.LegacyGenerator.IsMultistructure(path, mod);
118 | }
119 |
120 | ///
121 | /// Gets the quantity of structures in a multistructure file if possible. Returns null if the structure is invalid or not a multistructure.
122 | ///
123 | /// The path to the structure file you wish to check.
124 | /// The instance of your mod to grab the file from.
125 | /// The amount of structures in a multistructure, or null if invalid.
126 | [Obsolete("Legacy generation API is deprecated. Please port your structures to the 3.0 format and use StructureHelper.API.Generator." +
127 | "\n Porting instructions can be found at https://github.com/ScalarVector1/StructureHelper/wiki/3.0-Porting-Guide" +
128 | "\n If you truly need legacy API access, use StructureHelper.API.Legacy.LegacyGenerator.", true)]
129 | public static int? GetStructureCount(string path, Mod mod)
130 | {
131 | return API.Legacy.LegacyGenerator.GetStructureCount(path, mod);
132 | }
133 |
134 | ///
135 | /// Parses and generates the actual tiles from a structure file
136 | ///
137 | /// The structure data TagCompound to generate from
138 | /// The position in the world of the top-leftmost tile to be placed at
139 | /// If this structure should place null tiles or not
140 | /// If the structure successfully generated or not
141 | [Obsolete("Legacy generation API is deprecated. Please port your structures to the 3.0 format and use StructureHelper.API.Generator." +
142 | "\n Porting instructions can be found at https://github.com/ScalarVector1/StructureHelper/wiki/3.0-Porting-Guide" +
143 | "\n If you truly need legacy API access, use StructureHelper.API.Legacy.LegacyGenerator.", true)]
144 | public static unsafe bool Generate(TagCompound tag, Point16 pos, bool ignoreNull = false, GenFlags flags = GenFlags.None)
145 | {
146 | return API.Legacy.LegacyGenerator.Generate(tag, pos, ignoreNull, flags);
147 | }
148 | }
149 | }
--------------------------------------------------------------------------------
/Helper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace StructureHelper
5 | {
6 | static class Helper
7 | {
8 | ///
9 | /// Randomizes the order of a list using the Fischer-Yates algorithm
10 | ///
11 | /// The type of the list to randomize
12 | /// The list to randomize
13 | public static void RandomizeList(ref List input)
14 | {
15 | int n = input.Count();
16 |
17 | while (n > 1)
18 | {
19 | n--;
20 | int k = WorldGen.genRand.Next(n + 1);
21 | (input[n], input[k]) = (input[k], input[n]);
22 | }
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/Helpers/ErrorHelper.cs:
--------------------------------------------------------------------------------
1 | namespace StructureHelper.Helpers
2 | {
3 | internal class ErrorHelper
4 | {
5 | public static string GenerateErrorMessage(string message, Mod mod)
6 | {
7 | if (mod != null)
8 | return $"{mod.DisplayName}({mod.Name}) <-- has caused an issue with Structure helper! \n\n{message}\n\nIf you are a player, please report this to the developers of {mod.DisplayName}, NOT StructureHelper!";
9 | else
10 | return $"An unknown mod has caused an issue with Structure helper! \n\n{message}\n\nIf you are a developer, did you pass null to a parameter requiring a mod? Otherwise this error may have occured in a place without mod context.";
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/Helpers/GUIHelper.cs:
--------------------------------------------------------------------------------
1 | using ReLogic.Graphics;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Reflection;
6 | using System.Runtime.InteropServices;
7 | using Terraria.ModLoader.Config;
8 | using Terraria.UI;
9 |
10 | namespace StructureHelper.Helpers
11 | {
12 | internal static class GUIHelper
13 | {
14 | ///
15 | /// Draws a simple box in the style of the StructureHelper GUI.
16 | ///
17 | /// the spriteBatch to draw the box with
18 | /// where/how big the box should be drawn
19 | ///
20 | public static void DrawBox(SpriteBatch spriteBatch, Rectangle target, Color color = default)
21 | {
22 | Texture2D tex = Assets.GUI.Box.Value;
23 |
24 | if (color == default)
25 | color = new Color(49, 84, 141) * 0.9f;
26 |
27 | var sourceCorner = new Rectangle(0, 0, 6, 6);
28 | var sourceEdge = new Rectangle(6, 0, 4, 6);
29 | var sourceCenter = new Rectangle(6, 6, 4, 4);
30 |
31 | Rectangle inner = target;
32 | inner.Inflate(-6, -6);
33 |
34 | spriteBatch.Draw(tex, inner, sourceCenter, color);
35 |
36 | spriteBatch.Draw(tex, new Rectangle(target.X + 6, target.Y, target.Width - 12, 6), sourceEdge, color, 0, Vector2.Zero, 0, 0);
37 | spriteBatch.Draw(tex, new Rectangle(target.X, target.Y - 6 + target.Height, target.Height - 12, 6), sourceEdge, color, -(float)Math.PI * 0.5f, Vector2.Zero, 0, 0);
38 | spriteBatch.Draw(tex, new Rectangle(target.X - 6 + target.Width, target.Y + target.Height, target.Width - 12, 6), sourceEdge, color, (float)Math.PI, Vector2.Zero, 0, 0);
39 | spriteBatch.Draw(tex, new Rectangle(target.X + target.Width, target.Y + 6, target.Height - 12, 6), sourceEdge, color, (float)Math.PI * 0.5f, Vector2.Zero, 0, 0);
40 |
41 | spriteBatch.Draw(tex, new Rectangle(target.X, target.Y, 6, 6), sourceCorner, color, 0, Vector2.Zero, 0, 0);
42 | spriteBatch.Draw(tex, new Rectangle(target.X + target.Width, target.Y, 6, 6), sourceCorner, color, (float)Math.PI * 0.5f, Vector2.Zero, 0, 0);
43 | spriteBatch.Draw(tex, new Rectangle(target.X + target.Width, target.Y + target.Height, 6, 6), sourceCorner, color, (float)Math.PI, Vector2.Zero, 0, 0);
44 | spriteBatch.Draw(tex, new Rectangle(target.X, target.Y + target.Height, 6, 6), sourceCorner, color, (float)Math.PI * 1.5f, Vector2.Zero, 0, 0);
45 | }
46 |
47 | ///
48 | /// Draws the outline of a box in the style of the DragonLens GUI.
49 | ///
50 | /// the spriteBatch to draw the outline with
51 | /// where/how big the outline should be drawn
52 | /// the color of the outline
53 | public static void DrawOutline(SpriteBatch spriteBatch, Rectangle target, Color color = default)
54 | {
55 | Texture2D tex = Assets.GUI.Box.Value;
56 |
57 | if (color == default)
58 | color = new Color(49, 84, 141) * 0.9f;
59 |
60 | var sourceCorner = new Rectangle(0, 0, 6, 6);
61 | var sourceEdge = new Rectangle(6, 0, 4, 6);
62 | var sourceCenter = new Rectangle(6, 6, 4, 4);
63 |
64 | spriteBatch.Draw(tex, new Rectangle(target.X + 6, target.Y, target.Width - 12, 6), sourceEdge, color, 0, Vector2.Zero, 0, 0);
65 | spriteBatch.Draw(tex, new Rectangle(target.X, target.Y - 6 + target.Height, target.Height - 12, 6), sourceEdge, color, -(float)Math.PI * 0.5f, Vector2.Zero, 0, 0);
66 | spriteBatch.Draw(tex, new Rectangle(target.X - 6 + target.Width, target.Y + target.Height, target.Width - 12, 6), sourceEdge, color, (float)Math.PI, Vector2.Zero, 0, 0);
67 | spriteBatch.Draw(tex, new Rectangle(target.X + target.Width, target.Y + 6, target.Height - 12, 6), sourceEdge, color, (float)Math.PI * 0.5f, Vector2.Zero, 0, 0);
68 |
69 | spriteBatch.Draw(tex, new Rectangle(target.X, target.Y, 6, 6), sourceCorner, color, 0, Vector2.Zero, 0, 0);
70 | spriteBatch.Draw(tex, new Rectangle(target.X + target.Width, target.Y, 6, 6), sourceCorner, color, (float)Math.PI * 0.5f, Vector2.Zero, 0, 0);
71 | spriteBatch.Draw(tex, new Rectangle(target.X + target.Width, target.Y + target.Height, 6, 6), sourceCorner, color, (float)Math.PI, Vector2.Zero, 0, 0);
72 | spriteBatch.Draw(tex, new Rectangle(target.X, target.Y + target.Height, 6, 6), sourceCorner, color, (float)Math.PI * 1.5f, Vector2.Zero, 0, 0);
73 | }
74 |
75 | ///
76 | /// Gets the inverse of a color. Used for 'constrasting' elements of the StructureHelper GUI, such as outlines indicating something being on.
77 | ///
78 | /// the color to invert
79 | /// the inverted color
80 | public static Color InvertColor(this Color color)
81 | {
82 | return new Color(255 - color.R, 255 - color.G, 255 - color.B, color.A);
83 | }
84 |
85 | ///
86 | /// Wraps a string to a given maximum width, by forcibly adding newlines. Normal newlines will be removed, put the text 'NEWBLOCK' in your string to break a paragraph if needed.
87 | ///
88 | /// The input string to be wrapped
89 | /// The maximum width of the text
90 | /// The font the text will be drawn in, to calculate its size
91 | /// The scale the text will be drawn at, to calculate its size
92 | /// Input text with linebreaks inserted so it obeys the width constraint.
93 | public static string WrapString(string input, int length, DynamicSpriteFont font, float scale)
94 | {
95 | string output = "";
96 |
97 | // In case input is empty and causes an error, we put an empty string to the list
98 | var words = new List { "" };
99 |
100 | // Word splitting, with CJK characters being treated as a single word
101 | string cacheString = "";
102 | for (int i = 0; i < input.Length; i++)
103 | {
104 | // By doing this we split words, and make the first character of words always a space
105 | if (cacheString != string.Empty && char.IsWhiteSpace(input[i]))
106 | {
107 | words.Add(cacheString);
108 | cacheString = "";
109 | }
110 |
111 | // Single CJK character just get directly added to the list
112 | if (LocalizationHelper.IsCjkCharacter(input[i]))
113 | {
114 | if (cacheString != string.Empty)
115 | {
116 | words.Add(cacheString);
117 | cacheString = "";
118 | }
119 |
120 | // If the next character is a CJK punctuation, we add both characters as a single word
121 | // Unless the next character is a right close CJK punctuation (e.g. left brackets), in which case we add only the current character
122 | if (i + 1 < input.Length && LocalizationHelper.IsCjkPunctuation(input[i + 1]) && !LocalizationHelper.IsRightCloseCjkPunctuation(input[i + 1]))
123 | {
124 | words.Add(input[i].ToString() + input[i + 1]);
125 | i++;
126 | }
127 | else
128 | {
129 | words.Add(input[i].ToString());
130 | }
131 |
132 | continue;
133 | }
134 |
135 | cacheString += input[i];
136 | }
137 |
138 | // Add the last word
139 | if (!string.IsNullOrEmpty(cacheString))
140 | {
141 | words.Add(cacheString);
142 | }
143 |
144 | string line = "";
145 | foreach (string str in words)
146 | {
147 | if (str == " NEWBLOCK")
148 | {
149 | output += "\n\n";
150 | line = "";
151 | continue;
152 | }
153 |
154 | if (str == " NEWLN")
155 | {
156 | output += "\n";
157 | line = "";
158 | continue;
159 | }
160 |
161 | if (font.MeasureString(line).X * scale < length)
162 | {
163 | output += str;
164 | line += str;
165 | }
166 | else
167 | {
168 | // We don't want the first character of a line to be a space
169 | output += "\n" + str.TrimStart();
170 | line = str;
171 | }
172 | }
173 |
174 | return output;
175 | }
176 |
177 | ///
178 | /// Uses reflection to forcibly open the TModLoader configuration UI to a given ModConfig's screen.
179 | ///
180 | /// The config to open up
181 | public static void OpenConfig(ModConfig config)
182 | {
183 | IngameFancyUI.CoverNextFrame();
184 | Main.playerInventory = false;
185 | Main.editChest = false;
186 | Main.npcChatText = "";
187 | Main.inFancyUI = true;
188 |
189 | Type interfaceType = typeof(ModLoader).Assembly.GetType("Terraria.ModLoader.UI.Interface");
190 | FieldInfo modConfigList = interfaceType.GetField("modConfigList", BindingFlags.Static | BindingFlags.NonPublic);
191 |
192 | Type uiModConfig = typeof(ModLoader).Assembly.GetType("Terraria.ModLoader.Config.UI.UIModConfig");
193 | FieldInfo modConfig = interfaceType.GetField("modConfig", BindingFlags.Static | BindingFlags.NonPublic);
194 | MethodInfo setMod = uiModConfig.GetMethod("SetMod", BindingFlags.Instance | BindingFlags.NonPublic);
195 |
196 | var ui = (UIState)modConfig.GetValue(null);
197 |
198 | setMod.Invoke(ui, new object[] { ModContent.GetInstance(), config });
199 | Main.InGameUI.SetState(ui);
200 | }
201 |
202 | ///
203 | /// Opens a URL in the web browser
204 | ///
205 | /// The URL to open to
206 | public static void OpenUrl(string url)
207 | {
208 | try
209 | {
210 | Process.Start(url);
211 | }
212 | catch
213 | {
214 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
215 | {
216 | url = url.Replace("&", "^&");
217 | Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
218 | }
219 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
220 | {
221 | Process.Start("xdg-open", url);
222 | }
223 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
224 | {
225 | Process.Start("open", url);
226 | }
227 | else
228 | {
229 | throw;
230 | }
231 | }
232 | }
233 | }
234 | }
--------------------------------------------------------------------------------
/Helpers/LocalizationHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 | using Terraria.Localization;
3 |
4 | namespace StructureHelper.Helpers
5 | {
6 | internal static class LocalizationHelper
7 | {
8 | ///
9 | /// Gets a localized text value of the mod.
10 | /// If no localization is found, the key itself is returned.
11 | ///
12 | /// the localization key
13 | /// optional args that should be passed
14 | /// the text should be displayed
15 | public static string GetText(string key, params object[] args)
16 | {
17 | return Language.Exists($"Mods.StructureHelper.{key}") ? Language.GetTextValue($"Mods.StructureHelper.{key}", args) : key;
18 | }
19 |
20 | public static string GetGUIText(string key, params object[] args)
21 | {
22 | return GetText($"GUI.{key}", args);
23 | }
24 |
25 | public static string GetToolText(string key, params object[] args)
26 | {
27 | return GetText($"Tools.{key}", args);
28 | }
29 |
30 | public static bool IsCjkPunctuation(char a)
31 | {
32 | return Regex.IsMatch(a.ToString(), @"\p{IsCJKSymbolsandPunctuation}|\p{IsHalfwidthandFullwidthForms}");
33 | }
34 |
35 | public static bool IsCjkUnifiedIdeographs(char a)
36 | {
37 | return Regex.IsMatch(a.ToString(), @"\p{IsCJKUnifiedIdeographs}");
38 | }
39 |
40 | public static bool IsRightCloseCjkPunctuation(char a)
41 | {
42 | return a is '(' or '【' or '《' or '{' or '「' or '[' or '⦅' or '“';
43 | }
44 |
45 | public static bool IsCjkCharacter(char a)
46 | {
47 | return IsCjkUnifiedIdeographs(a) || IsCjkPunctuation(a);
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/Localization/en-US.hjson:
--------------------------------------------------------------------------------
1 | Mods: {
2 | StructureHelper: {
3 | Items: {
4 | ChestWand: {
5 | DisplayName: Chest Wand
6 | Tooltip:
7 | '''
8 | Right click to open the chest rule menu
9 | Left click a chest to set the current rules on it
10 | Right click a chest with rules to copy them
11 | '''
12 | }
13 |
14 | MultistructureWand: {
15 | DisplayName: Multistructure Wand
16 | Tooltip: Select 2 points in the world, then right click to add a structure. Right click in your inventory when done to save.
17 | }
18 |
19 | StructureWand: {
20 | DisplayName: Structure Wand
21 | Tooltip: Select 2 points in the world, then right click to save a structure
22 | }
23 |
24 | TestWand: {
25 | DisplayName: Structure Placer Wand
26 | Tooltip: left click to place the selected structure, right click to open the structure selector
27 | }
28 |
29 | NullBlockItem: {
30 | DisplayName: Null Block
31 | Tooltip:
32 | '''
33 | Use these in a structure to indicate where the generator
34 | should leave whatever already exists in the world untouched
35 | ignores walls, use null walls for that :3
36 | '''
37 | }
38 |
39 | NullTileAndWallPlacer: {
40 | DisplayName: Null Tile Place-O-Matic
41 | Tooltip: Places a null tile and null wall at the same time!
42 | }
43 |
44 | NullWallItem: {
45 | DisplayName: Null Wall
46 | Tooltip:
47 | '''
48 | Use these in a structure to indicate where the generator
49 | should leave walls that already exists in the world untouched
50 | for walls only, use null blocks for other things
51 | '''
52 | }
53 |
54 | PortingWand: {
55 | DisplayName: Porting Wand
56 | Tooltip: Automatically ports every structure in your SavedStructures directory.
57 | }
58 |
59 | UnitWand: {
60 | DisplayName: The Wand
61 | Tooltip: A test for myself If I leave this in bonk me.
62 | }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/LocalizationRoundabout.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using Terraria.Localization;
4 |
5 | namespace StructureHelper
6 | {
7 | internal class LocalizationRewriter : ModSystem
8 | {
9 | public override void PostSetupContent()
10 | {
11 | #if DEBUG
12 | MethodInfo refreshInfo = typeof(LocalizationLoader).GetMethod("UpdateLocalizationFilesForMod", BindingFlags.NonPublic | BindingFlags.Static, new Type[] { typeof(Mod), typeof(string), typeof(GameCulture) });
13 | refreshInfo.Invoke(null, new object[] { StructureHelper.Instance, null, Language.ActiveCulture });
14 | #endif
15 | }
16 | }
17 |
18 | internal static class LocalizationRoundabout
19 | {
20 | public static void SetDefault(this LocalizedText text, string value)
21 | {
22 | #if DEBUG
23 | PropertyInfo valueProp = typeof(LocalizedText).GetProperty("Value", BindingFlags.Public | BindingFlags.Instance);
24 |
25 | LanguageManager.Instance.GetOrRegister(text.Key, () => value);
26 | valueProp.SetValue(text, value);
27 | #endif
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/Models/GenFlags.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace StructureHelper
8 | {
9 | ///
10 | /// Flags that allow users to define special behavior for structure generation.
11 | ///
12 | [Flags]
13 | public enum GenFlags
14 | {
15 | ///
16 | /// No special generation
17 | ///
18 | None = 0b0,
19 | ///
20 | /// Null tiles will inherit the type of the tile behind them, but keep their slope if that tile is slopable
21 | ///
22 | NullsKeepGivenSlope = 0b1,
23 | ///
24 | /// Null tiles and walls will inherit the type of the tile/wall behind them, but will keep the paint they are given
25 | ///
26 | NullsKeepGivenPaint = 0b10,
27 | ///
28 | /// Tile entities will not have their saved data placed in the generated structures, instead falling back to acting as if they are newly created
29 | ///
30 | IgnoreTileEnttiyData = 0b100,
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Models/Legacy/TileSaveData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Terraria.ModLoader.IO;
3 |
4 | namespace StructureHelper
5 | {
6 | ///
7 | /// A struct representing tile data to be saved/loaded from structure files.
8 | ///
9 | public struct TileSaveData : TagSerializable
10 | {
11 | ///
12 | /// The tile to be placed, either a number if a vanilla tile (ID), or a fully qualified internal name for modded tiles.
13 | ///
14 | public string tile;
15 | ///
16 | /// The wall to be placed, either a number if a vanilla wall (ID), or a fully qualified internal name for modded walls.
17 | ///
18 | public string wall;
19 | ///
20 | /// The X frame of a tile
21 | ///
22 | public short frameX;
23 | ///
24 | /// the Y frame of a tile
25 | ///
26 | public short frameY;
27 | ///
28 | /// One part of the packed vanilla data about a tile
29 | ///
30 | public int wallWireData;
31 | ///
32 | /// The other part of the packed vanilla data about a tile
33 | ///
34 | public short packedLiquidData;
35 | ///
36 | /// Data about certain coatings added by 1.4.4
37 | ///
38 | public byte brightInvisibleData;
39 |
40 | ///
41 | /// The fully qualiified name of a modded tile entity, if one should exist here
42 | ///
43 | public string TEType;
44 | ///
45 | /// The data associated with a tile entity associated here
46 | ///
47 | public TagCompound TEData;
48 |
49 | public static Func DESERIALIZER = DeserializeData;
50 |
51 | ///
52 | /// If the tile here is air or not. (Note that TileID 0 is dirt, this is all that differentiates air and dirt.)
53 | ///
54 | public bool Active => TileDataPacking.GetBit(wallWireData, 0);
55 |
56 | public TileSaveData(string tile, string wall, short frameX, short frameY, int wallWireData, short packedLiquidData, byte brightInvisibleData, string teType = "", TagCompound teData = null)
57 | {
58 | this.tile = tile;
59 | this.wall = wall;
60 | this.frameX = frameX;
61 | this.frameY = frameY;
62 | this.wallWireData = wallWireData;
63 | this.packedLiquidData = packedLiquidData;
64 | this.brightInvisibleData = brightInvisibleData;
65 | TEType = teType;
66 | TEData = teData;
67 | }
68 |
69 | ///
70 | /// Deserialize a TagCompound into an instance of this struct
71 | ///
72 | /// The tag to interpret
73 | /// The unpacked TileSaveData
74 | public static TileSaveData DeserializeData(TagCompound tag)
75 | {
76 | var output = new TileSaveData(
77 | tag.GetString("Tile"),
78 | tag.GetString("Wall"),
79 | tag.GetShort("FrameX"),
80 | tag.GetShort("FrameY"),
81 |
82 | tag.GetInt("WallWireData"),
83 | tag.GetShort("PackedLiquidData"),
84 | tag.TryGet("BrightInvisibleData", out byte brightInvis) ? brightInvis : (byte)0
85 | );
86 |
87 | if (tag.ContainsKey("TEType"))
88 | {
89 | output.TEType = tag.GetString("TEType");
90 | output.TEData = tag.Get("TEData");
91 | }
92 |
93 | return output;
94 | }
95 |
96 | ///
97 | /// Serialize this struct into a TagCompound for saving
98 | ///
99 | /// The packed TagCompound
100 | public TagCompound SerializeData()
101 | {
102 | var tag = new TagCompound()
103 | {
104 | ["Tile"] = tile,
105 | ["Wall"] = wall,
106 | ["FrameX"] = frameX,
107 | ["FrameY"] = frameY,
108 |
109 | ["WallWireData"] = wallWireData,
110 | ["PackedLiquidData"] = packedLiquidData,
111 | ["BrightInvisibleData"] = brightInvisibleData
112 | };
113 |
114 | if (TEType != "")
115 | {
116 | tag.Add("TEType", TEType);
117 | tag.Add("TEData", TEData);
118 | }
119 |
120 | return tag;
121 | }
122 | }
123 | }
--------------------------------------------------------------------------------
/Models/MultiStructureData.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.Helpers;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace StructureHelper.Models
10 | {
11 | ///
12 | /// Represents the contents of a multi structure file in-memory. This object is only valid
13 | /// for a given mod load instance, as modded tile types will be parsed out from the table
14 | /// into the body data. If you are storing these and your mod reloads, you should consider
15 | /// them invalid afterwards.
16 | ///
17 | public class MultiStructureData
18 | {
19 | public const string HEADER_TEXT = "STRUCTURE_HELPER_MULTI_STRUCTURE";
20 |
21 | ///
22 | /// The amount of structures in this multistructure
23 | ///
24 | public int count;
25 |
26 | ///
27 | /// The structure data in this multistructure
28 | ///
29 | public List structures = [];
30 |
31 | ///
32 | /// Constructs a MultiStructureData from raw binary data
33 | ///
34 | /// A reader for the raw binary data, such as from a file
35 | /// A MultiStructureData constructed from the raw bytes
36 | ///
37 | public static MultiStructureData FromStream(BinaryReader reader)
38 | {
39 | string headerText = reader.ReadString();
40 |
41 | if (headerText != HEADER_TEXT)
42 | throw new InvalidDataException(ErrorHelper.GenerateErrorMessage("Attempted to deserialize binary data that is not a 3.0 multi structure file! Did you pass the path to a .shmstruct file? If so, did you change the file extension without actually porting your multi structure file from 2.0?", null));
43 |
44 | var data = new MultiStructureData();
45 | data.count = reader.ReadInt32();
46 |
47 | for(int k = 0; k < data.count; k++)
48 | {
49 | data.structures.Add(StructureData.FromStream(reader));
50 | }
51 |
52 | return data;
53 | }
54 |
55 | ///
56 | /// Generates a MultiStructureData from a list of StructureData
57 | ///
58 | /// The StructureData to put into this MultiStructureData
59 | /// The constructed MultiStructureData containing all elements in the source list
60 | public static MultiStructureData FromStructureList(List source)
61 | {
62 | MultiStructureData data = new();
63 | data.count = source.Count;
64 | data.structures.AddRange(source);
65 |
66 | return data;
67 | }
68 |
69 | ///
70 | /// Serialize this MultiStructureData into a binary writer, such as to write to a file
71 | ///
72 | public void Serialize(BinaryWriter writer)
73 | {
74 | writer.Write(HEADER_TEXT);
75 | writer.Write(count);
76 |
77 | for(int k = 0; k < count; k++)
78 | {
79 | structures[k].Serialize(writer);
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Models/NbtEntries/SignNBTEntry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Terraria.DataStructures;
7 | using Terraria.ModLoader.IO;
8 |
9 | namespace StructureHelper.Models.NbtEntries
10 | {
11 | internal class SignNBTEntry : StructureNBTEntry
12 | {
13 | public string text;
14 |
15 | public static Func DESERIALIZER = Deserialize;
16 |
17 | public SignNBTEntry(int x, int y, string text) : base(x, y)
18 | {
19 | this.text = text;
20 | }
21 |
22 | public static SignNBTEntry Deserialize(TagCompound tag)
23 | {
24 | SignNBTEntry entry = new
25 | (tag.GetInt("x"),
26 | tag.GetInt("y"),
27 | tag.GetString("text"));
28 |
29 | return entry;
30 | }
31 |
32 | public override void OnGenerate(Point16 generatingAt, bool ignoreNull, GenFlags flags)
33 | {
34 | int index = Sign.ReadSign(generatingAt.X, generatingAt.Y, true);
35 |
36 | // Index may be -1 if there are already 1000 signs in the world
37 | if (index != -1)
38 | Sign.TextSign(index, text);
39 | }
40 |
41 | public override void Serialize(TagCompound tag)
42 | {
43 | tag["text"] = text;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Models/NbtEntries/TileEntityNBTEntry.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.ChestHelper;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using Terraria.DataStructures;
8 | using Terraria.ModLoader.IO;
9 |
10 | namespace StructureHelper.Models.NbtEntries
11 | {
12 | internal class TileEntityNBTEntry : StructureNBTEntry
13 | {
14 | string tileEntityType;
15 | TagCompound tileEntityData;
16 |
17 | public static Func DESERIALIZER = Deserialize;
18 |
19 | public TileEntityNBTEntry(int x, int y, string tileEntityType, TagCompound tileEntityData) : base(x, y)
20 | {
21 | this.tileEntityType = tileEntityType;
22 | this.tileEntityData = tileEntityData;
23 | }
24 |
25 | public static TileEntityNBTEntry Deserialize(TagCompound tag)
26 | {
27 | TileEntityNBTEntry entry = new(
28 | tag.GetInt("x"),
29 | tag.GetInt("y"),
30 | tag.GetString("tileEntityType"),
31 | tag.Get("tileEntityData"));
32 |
33 | return entry;
34 | }
35 |
36 | //TODO: Do we want to move these to their own StructureNBTEntry?
37 | internal void GenerateChest(Point16 pos, TagCompound rules)
38 | {
39 | int i = Chest.CreateChest(pos.X, pos.Y);
40 |
41 | if (i == -1)
42 | return;
43 |
44 | Chest chest = Main.chest[i];
45 | ChestEntity.SetChest(chest, ChestEntity.LoadChestRules(rules));
46 | }
47 |
48 | public override void OnGenerate(Point16 generatingAt, bool ignoreNull, GenFlags flags)
49 | {
50 | if (tileEntityType == "StructureHelper/ChestEntity" && !ignoreNull)
51 | {
52 | GenerateChest(generatingAt, tileEntityData);
53 | }
54 | else if ((flags & GenFlags.IgnoreTileEnttiyData) == 0)
55 | {
56 | if (!int.TryParse(tileEntityType, out int typ))
57 | {
58 | string[] parts = tileEntityType.Split("/", 2);
59 |
60 | if (ModLoader.TryGetMod(parts[0], out Mod mod) && mod.TryFind(parts[1], out ModTileEntity te))
61 | typ = te.Type;
62 | }
63 |
64 | if (typ != 0)
65 | {
66 | TileEntity.PlaceEntityNet(generatingAt.X, generatingAt.Y, typ);
67 |
68 | if (tileEntityData != null && typ != 2 && TileEntity.ByPosition.ContainsKey(generatingAt)) // We specifically exclude logic sensors (type 2) because these store their absolute pos
69 | TileEntity.ByPosition[generatingAt].LoadData(tileEntityData);
70 | }
71 | }
72 | }
73 |
74 | public override void Serialize(TagCompound tag)
75 | {
76 | tag["tileEntityType"] = tileEntityType;
77 | tag["tileEntityData"] = tileEntityData;
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Models/StructureNbtEntry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Terraria.DataStructures;
7 | using Terraria.ModLoader.IO;
8 |
9 | namespace StructureHelper.Models
10 | {
11 | ///
12 | /// Represents an NBT entry within the NBT section of the structure file. These are used for sparse,
13 | /// complex data within structures like tile entities or chest pools.
14 | ///
15 | /// You MUST implement a DESERIALIZER as per TagSerializable!
16 | ///
17 | public abstract class StructureNBTEntry : TagSerializable
18 | {
19 | ///
20 | /// X coordinate local to the strucure associated with this NBT entry
21 | ///
22 | public int x;
23 |
24 | ///
25 | /// Y coordinate local to the structure associated with this NBT entry
26 | ///
27 | public int y;
28 |
29 | ///
30 | /// Temporary hack deserializer because TML fails to find subtype deserializers
31 | ///
32 | public static readonly Func DESERIALIZER = tag => {
33 | var typeName = tag.GetString("");
34 | var type = typeof(StructureNBTEntry).Assembly.GetType(typeName) ?? ModLoader.Mods.Select(mod => mod.Code?.GetType(typeName)).Where(t => t != null).FirstOrDefault();
35 | if (TagSerializer.TryGetSerializer(type, out var serializer))
36 | return (StructureNBTEntry)serializer.Deserialize(tag);
37 |
38 | throw new ArgumentException($"Missing deserializer for subtype '{typeName}'");
39 | };
40 |
41 | ///
42 | /// What needs to happen on structure generation to properly place the object
43 | /// represented by this NBT entry into the world.
44 | ///
45 | public abstract void OnGenerate(Point16 generatingAt, bool ignoreNull, GenFlags flags);
46 |
47 | ///
48 | /// Serialize the custom data for this StructureNbtEntry here
49 | ///
50 | /// The tag to serialize to
51 | public abstract void Serialize(TagCompound tag);
52 |
53 | public StructureNBTEntry(int x, int y)
54 | {
55 | this.x = x;
56 | this.y = y;
57 | }
58 |
59 | public TagCompound SerializeData()
60 | {
61 | TagCompound tag = new()
62 | {
63 | ["x"] = x,
64 | ["y"] = y
65 | };
66 |
67 | Serialize(tag);
68 |
69 | return tag;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Models/TileDataEntry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Runtime.InteropServices;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace StructureHelper.Models
9 | {
10 | public unsafe interface ITileDataEntry
11 | {
12 | ///
13 | /// Returns the pointer to the backing data of this TileDataEntry
14 | ///
15 | ///
16 | public void* GetRawPtr();
17 |
18 | ///
19 | /// Sets the data for this TileDataEntry from a byte array, such as one read from a binary file
20 | ///
21 | /// The raw data to populate this entry with
22 | ///
23 | public void SetData(byte[] data);
24 |
25 | ///
26 | /// Gets the raw data of this tile data entry as a byte array.
27 | ///
28 | /// The raw data as a byte array
29 | public byte[] GetData();
30 |
31 | ///
32 | /// Gets a single entry at this location in the data. Returns as a void pointer,
33 | /// you will have to derefference this yourself.
34 | ///
35 | ///
36 | ///
37 | ///
38 | public void* GetSingleEntry(int x, int y);
39 |
40 | ///
41 | /// Returns the raw size of this tile data entry, in bytes.
42 | ///
43 | /// The total size of this data entry in bytes
44 | public int GetRawSize();
45 |
46 | ///
47 | /// Returns the size in bytes of one column of this data
48 | ///
49 | ///
50 | public int GetColumnSize();
51 |
52 | ///
53 | /// Returns the size in bytes of a single entry of this data
54 | ///
55 | ///
56 | public int GetSingleSize();
57 |
58 | ///
59 | /// Imports a column of data from the world into this structure
60 | ///
61 | /// The topmost point of the column
62 | /// The index of the column to read into
63 | public void ImportColumn(void* source, int columnIdx);
64 |
65 | ///
66 | /// Copies an entire column of data from this data entry into the world.
67 | ///
68 | /// The pointer to the topmost tiles ITileData of this type to copy into
69 | /// The column of data to place at that location
70 | public void ExportColumn(void* target, int columnIdx);
71 |
72 | ///
73 | /// Copies a single entry of data into the world.
74 | ///
75 | /// The pointer to the tiles ITileData of this type to copy into
76 | /// The X position to copy from
77 | /// The Y position to copy from
78 | public void ExportSingle(void* target, int columnIdx, int rowIdx);
79 | }
80 |
81 | public unsafe class TileDataEntry : ITileDataEntry, IDisposable where T : unmanaged, ITileData
82 | {
83 | private readonly T[] rawData;
84 | private readonly T* rawDataPtr;
85 |
86 | private GCHandle rawDataHandle;
87 |
88 | public int length;
89 | public int colLength;
90 |
91 | public TileDataEntry(int length, int rowLength)
92 | {
93 | rawData = new T[length];
94 | rawDataHandle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
95 | rawDataPtr = (T*)rawDataHandle.AddrOfPinnedObject().ToPointer();
96 |
97 | this.length = length;
98 | this.colLength = rowLength;
99 | }
100 |
101 | public void* GetRawPtr()
102 | {
103 | return rawDataPtr;
104 | }
105 |
106 | public void SetData(byte[] data)
107 | {
108 | if (data.Length != sizeof(T) * length)
109 | throw new ArgumentException($"Data size is inappropriate, Expected enough for {length} {typeof(T).Name} ({sizeof(T) * length} bytes), but got {data.Length}");
110 |
111 | fixed (byte* dataPtr = data)
112 | {
113 | Buffer.MemoryCopy(dataPtr, rawDataPtr, data.Length, data.Length);
114 | }
115 | }
116 |
117 | public byte[] GetData()
118 | {
119 | byte[] data = new byte[GetRawSize()];
120 |
121 | fixed (byte* dataPtr = data)
122 | {
123 | Buffer.MemoryCopy(rawDataPtr, dataPtr, data.Length, data.Length);
124 | }
125 |
126 | return data;
127 | }
128 |
129 | public void* GetSingleEntry(int x, int y)
130 | {
131 | return rawDataPtr + (x * colLength + y);
132 | }
133 |
134 | public int GetRawSize()
135 | {
136 | return sizeof(T) * length;
137 | }
138 |
139 | public int GetColumnSize()
140 | {
141 | return sizeof(T) * colLength;
142 | }
143 |
144 | public int GetSingleSize()
145 | {
146 | return sizeof(T);
147 | }
148 |
149 | public void ImportColumn(void* source, int colIdx)
150 | {
151 | long colSize = GetColumnSize();
152 |
153 | T* target = rawDataPtr + colIdx * colLength;
154 | Buffer.MemoryCopy(source, target, colSize, colSize);
155 | }
156 |
157 | public void ExportColumn(void* target, int colIdx)
158 | {
159 | long colSize = GetColumnSize();
160 |
161 | T* source = rawDataPtr + colIdx * colLength;
162 | Buffer.MemoryCopy(source, target, colSize, colSize);
163 | }
164 |
165 | public void ExportSingle(void* target, int colIdx, int rowIdx)
166 | {
167 | T* source = rawDataPtr + (colIdx * colLength + rowIdx);
168 | Buffer.MemoryCopy(source, target, GetSingleSize(), GetSingleSize());
169 | }
170 |
171 | public void Dispose()
172 | {
173 | rawDataHandle.Free();
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/Porting/Porter.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.API;
2 | using StructureHelper.Content.GUI;
3 | using StructureHelper.Models;
4 | using StructureHelper.Models.NbtEntries;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using Terraria;
12 | using Terraria.DataStructures;
13 | using Terraria.ID;
14 | using Terraria.ModLoader;
15 | using Terraria.ModLoader.IO;
16 | using static System.Net.WebRequestMethods;
17 |
18 | namespace StructureHelper.NewFolder
19 | {
20 | internal class Porter
21 | {
22 | public static void AddDataEntry(StructureData target, Mod mod) where T:unmanaged, ITileData
23 | {
24 | string key = $"{mod?.Name ?? "Terraria"}/{typeof(T).Name}";
25 |
26 | if (!target.dataEntries.ContainsKey(key))
27 | target.dataEntries.Add(key, new TileDataEntry(target.width * target.height, target.height));
28 | }
29 |
30 | public static unsafe void InsertDataEntry(StructureData target, Mod mod, int x, int y, void* value) where T : unmanaged, ITileData
31 | {
32 | string key = $"{mod?.Name ?? "Terraria"}/{typeof(T).Name}";
33 |
34 | void* memTarget = target.dataEntries[key].GetSingleEntry(x, y);
35 | Buffer.MemoryCopy(value, memTarget, sizeof(T), sizeof(T));
36 | }
37 |
38 | public static unsafe StructureData PortStructure(TagCompound tag)
39 | {
40 | StructureData ported = new StructureData();
41 |
42 | var data = (List)tag.GetList("TileData");
43 |
44 | int width = tag.GetInt("Width");
45 | int height = tag.GetInt("Height");
46 |
47 | ported.width = width + 1;
48 | ported.height = height + 1;
49 | ported.version = StructureHelper.Instance.Version;
50 |
51 | AddDataEntry(ported, null);
52 | AddDataEntry(ported, null);
53 | AddDataEntry(ported, null);
54 | AddDataEntry(ported, null);
55 | AddDataEntry(ported, null);
56 |
57 | for (int x = 0; x <= width; x++)
58 | {
59 | bool isSlow = false;
60 |
61 | for (int y = 0; y <= height; y++)
62 | {
63 | int index = y + x * (height + 1);
64 | TileSaveData d = data[index];
65 |
66 | bool isNullTile = false;
67 | bool isNullWall = false;
68 |
69 | if (!ushort.TryParse(d.tile, out ushort type))
70 | {
71 | string[] parts = d.tile.Split();
72 |
73 | if (parts[0] == "StructureHelper" && parts[1] == "NullBlock")
74 | {
75 | ported.moddedTileTable.TryAdd(StructureHelper.NullTileID, StructureHelper.NULL_IDENTIFIER);
76 | type = StructureHelper.NULL_IDENTIFIER;
77 | isSlow = true;
78 | }
79 | else if (parts.Length > 1 && ModLoader.TryGetMod(parts[0], out Mod mod) && mod.TryFind(parts[1], out ModTile modTileType))
80 | {
81 | ported.moddedTileTable.TryAdd(modTileType.Type, modTileType.Type);
82 | type = modTileType.Type;
83 | }
84 | else
85 | {
86 | type = 0;
87 | }
88 | }
89 |
90 | if (!ushort.TryParse(d.wall, out ushort wallType))
91 | {
92 | string[] parts = d.wall.Split();
93 |
94 | if (parts[0] == "StructureHelper" && parts[1] == "NullWall")
95 | {
96 | ported.moddedWallTable.TryAdd(StructureHelper.NullWallID, StructureHelper.NULL_IDENTIFIER);
97 | wallType = StructureHelper.NULL_IDENTIFIER;
98 | isSlow = true;
99 | }
100 | else if (parts.Length > 1 && ModLoader.TryGetMod(parts[0], out Mod mod) && mod.TryFind(parts[1], out ModWall modWallType))
101 | {
102 | ported.moddedWallTable.TryAdd(modWallType.Type, modWallType.Type);
103 | wallType = modWallType.Type;
104 | }
105 | else
106 | {
107 | wallType = 0;
108 | }
109 | }
110 |
111 | if (!d.Active)
112 | isNullTile = false;
113 |
114 | InsertDataEntry(ported, null, x, y, &type);
115 | InsertDataEntry(ported, null, x, y, &wallType);
116 | InsertDataEntry(ported, null, x, y, &d.packedLiquidData);
117 | InsertDataEntry(ported, null, x, y, &d.brightInvisibleData);
118 |
119 | var reconstructed = new TileWallWireStateData();
120 | reconstructed.TileFrameX = d.frameX;
121 | reconstructed.TileFrameY = d.frameY;
122 |
123 | void* ptr = &reconstructed;
124 |
125 | int* intPtr = (int*)ptr;
126 | intPtr++;
127 |
128 | *intPtr = d.wallWireData;
129 |
130 | if (!d.Active)
131 | reconstructed.HasTile = false;
132 |
133 | InsertDataEntry(ported, null, x, y, &reconstructed);
134 |
135 |
136 | if (d.TEType != "") //place and load a tile entity
137 | {
138 |
139 | if (!int.TryParse(d.TEType, out int typ))
140 | {
141 | string[] parts = d.TEType.Split();
142 |
143 | if (ModLoader.TryGetMod(parts[0], out Mod mod) && mod.TryFind(parts[1], out ModTileEntity te))
144 | typ = te.Type;
145 | }
146 |
147 | if (typ != 0)
148 | {
149 | var entity = TileEntity.manager.GenerateInstance(typ);
150 |
151 | var modTileEntity = entity as ModTileEntity;
152 | string teName;
153 |
154 | if (modTileEntity != null)
155 | teName = modTileEntity.FullName;
156 | else
157 | teName = entity.type.ToString();
158 |
159 | ported.containsNbt = true;
160 | ported.nbtData ??= new();
161 |
162 | ported.nbtData.Add(new TileEntityNBTEntry(x, y, teName, d.TEData));
163 | }
164 | }
165 | }
166 | ported.slowColumns.Add(x, isSlow);
167 | }
168 |
169 | return ported;
170 | }
171 |
172 | public static MultiStructureData PortMultiStructure(TagCompound tag)
173 | {
174 | MultiStructureData ported = new MultiStructureData();
175 |
176 | var oldStructures = (List)tag.GetList("Structures");
177 |
178 | ported.count = oldStructures.Count;
179 |
180 | for(int k = 0; k < oldStructures.Count; k++)
181 | {
182 | ported.structures.Add(PortStructure(oldStructures[k]));
183 | }
184 |
185 | return ported;
186 | }
187 |
188 | public static void PortFile(string path)
189 | {
190 | var tag = API.Legacy.LegacyGenerator.GetTag(path, StructureHelper.Instance, true);
191 | var data = PortStructure(tag);
192 |
193 | Saver.SaveToFile(data, path);
194 | }
195 |
196 | public static void PortMultiFile(string path)
197 | {
198 | var tag = API.Legacy.LegacyGenerator.GetTag(path, StructureHelper.Instance, true);
199 | var data = PortMultiStructure(tag);
200 |
201 | Saver.SaveMultistructureToFile(data, path);
202 | }
203 |
204 | public static void PortDirectory(string dir)
205 | {
206 | string[] filePaths = Directory.GetFiles(dir);
207 |
208 | foreach (string path in filePaths)
209 | {
210 | string name = Path.GetFileName(path);
211 |
212 | if (!Path.HasExtension(path))
213 | {
214 | try
215 | {
216 | if (API.Legacy.LegacyGenerator.GetTag(path, StructureHelper.Instance, true).ContainsKey("Structures"))
217 | PortMultiFile(path);
218 | else
219 | PortFile(path);
220 | }
221 | catch
222 | {
223 | Main.NewText($"A file in your SavedStructures directory didnt seem to be a structure ({name}), it will be skipped!");
224 | continue;
225 | }
226 | }
227 | }
228 | }
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Terraria": {
4 | "commandName": "Executable",
5 | "executablePath": "dotnet",
6 | "commandLineArgs": "$(tMLPath)",
7 | "workingDirectory": "$(tMLSteamPath)"
8 | },
9 | "TerrariaServer": {
10 | "commandName": "Executable",
11 | "executablePath": "dotnet",
12 | "commandLineArgs": "$(tMLServerPath)",
13 | "workingDirectory": "$(tMLSteamPath)"
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/SampleChest:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/SampleChest
--------------------------------------------------------------------------------
/StructureHelper.cs:
--------------------------------------------------------------------------------
1 | global using Microsoft.Xna.Framework;
2 | global using Microsoft.Xna.Framework.Graphics;
3 | global using Terraria;
4 | global using Terraria.ModLoader;
5 | using StructureHelper.Content.Tiles;
6 | using System.Reflection.Metadata;
7 |
8 | namespace StructureHelper
9 | {
10 | public class StructureHelper : Mod
11 | {
12 | public static ushort NullTileID;
13 | public static ushort NullWallID;
14 |
15 | public const ushort NULL_IDENTIFIER = ushort.MaxValue;
16 |
17 | public StructureHelper() { Instance = this; }
18 |
19 | public static StructureHelper Instance { get; set; }
20 |
21 | public override void PostSetupContent()
22 | {
23 | NullTileID = (ushort)ModContent.TileType();
24 | NullWallID = (ushort)ModContent.WallType();
25 | }
26 |
27 | public override void Unload()
28 | {
29 | API.Legacy.LegacyGenerator.StructureDataCache.Clear();
30 | API.Generator.StructureCache.Clear();
31 | API.MultiStructureGenerator.MultiStructureCache.Clear();
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/StructureHelper.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | StructureHelper
6 | latest
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/StructureHelper.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29123.88
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StructureHelper", "StructureHelper.csproj", "{E8EC1BE9-A321-47D3-A40E-DD5D51D973A1}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {E8EC1BE9-A321-47D3-A40E-DD5D51D973A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {E8EC1BE9-A321-47D3-A40E-DD5D51D973A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {E8EC1BE9-A321-47D3-A40E-DD5D51D973A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {E8EC1BE9-A321-47D3-A40E-DD5D51D973A1}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {890D3A7C-E2A6-45A2-A67D-6437D09F9C36}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/Util/LegacyStructurePreview.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Terraria.ModLoader.IO;
4 |
5 | namespace StructureHelper.Util
6 | {
7 | ///
8 | /// Container for a structure's preview image.
9 | ///
10 | public class LegacyStructurePreview : IDisposable
11 | {
12 | private readonly TagCompound tag;
13 |
14 | ///
15 | /// The name of the structure this is previewing
16 | ///
17 | public readonly string name;
18 |
19 | ///
20 | /// The actual texture of the preview
21 | ///
22 | public Texture2D preview;
23 |
24 | ///
25 | /// Width of the preview texture, in pixels
26 | ///
27 | public int Width => preview?.Width ?? 1;
28 |
29 | ///
30 | /// Height of the preview texture, in pixels
31 | ///
32 | public int Height => preview?.Height ?? 1;
33 |
34 | public LegacyStructurePreview(string name, TagCompound structure)
35 | {
36 | this.name = name;
37 | tag = structure;
38 |
39 | LegacyPreviewRenderQueue.queue.Add(this);
40 | }
41 |
42 | ///
43 | /// Renders and saves the actual preview to a RenderTarget.
44 | ///
45 | /// The texture created
46 | internal Texture2D GeneratePreview()
47 | {
48 | int width = tag.GetInt("Width");
49 | int height = tag.GetInt("Height");
50 |
51 | var data = (List)tag.GetList("TileData");
52 |
53 | RenderTargetBinding[] oldTargets = Main.graphics.GraphicsDevice.GetRenderTargets();
54 |
55 | RenderTarget2D newTexture = new(Main.graphics.GraphicsDevice, (width + 1) * 16, (height + 1) * 16, false, default, default, default, RenderTargetUsage.PreserveContents);
56 |
57 | Main.graphics.GraphicsDevice.SetRenderTarget(newTexture);
58 |
59 | Main.graphics.GraphicsDevice.Clear(Color.Transparent);
60 |
61 | Main.spriteBatch.Begin();
62 |
63 | for (int x = 0; x <= width; x++)
64 | {
65 | for (int y = 0; y <= height; y++)
66 | {
67 | int index = y + x * (height + 1);
68 | TileSaveData d = data[index];
69 |
70 | if (!int.TryParse(d.tile, out int type))
71 | {
72 | string[] parts = d.tile.Split();
73 |
74 | if (parts.Length > 1 && ModLoader.TryGetMod(parts[0], out Mod mod) && mod.TryFind(parts[1], out ModTile modTileType))
75 | type = modTileType.Type;
76 | else
77 | type = 0;
78 | }
79 |
80 | if (!int.TryParse(d.wall, out int wallType))
81 | {
82 | string[] parts = d.wall.Split();
83 |
84 | if (parts.Length > 1 && ModLoader.TryGetMod(parts[0], out Mod mod) && mod.TryFind(parts[1], out ModWall modWallType))
85 | wallType = modWallType.Type;
86 | else
87 | wallType = 0;
88 | }
89 |
90 | if (wallType != 0 && d.wall != "StructureHelper NullWall")
91 | {
92 | Texture2D tex = Terraria.GameContent.TextureAssets.Wall[wallType].Value;
93 | Main.spriteBatch.Draw(tex, new Rectangle(x * 16, y * 16, 16, 16), new Rectangle(8, 8, 16, 16), Color.White);
94 | }
95 |
96 | if (d.Active && d.tile != "StructureHelper NullBlock")
97 | {
98 | Texture2D tex = Terraria.GameContent.TextureAssets.Tile[type].Value;
99 | Main.spriteBatch.Draw(tex, new Rectangle(x * 16, y * 16, 16, 16), new Rectangle(d.frameX, d.frameY, 16, 16), Color.White);
100 | }
101 | }
102 | }
103 |
104 | Main.spriteBatch.End();
105 |
106 | Main.graphics.GraphicsDevice.SetRenderTargets(null);
107 | Main.graphics.GraphicsDevice.SetRenderTargets(oldTargets);
108 | return newTexture;
109 | }
110 |
111 | public void Dispose()
112 | {
113 | preview?.Dispose();
114 | }
115 | }
116 |
117 | public class LegacyPreviewRenderQueue : ILoadable
118 | {
119 | ///
120 | /// A queue of previews to render textures for when the next opportunity arises
121 | ///
122 | public static List queue = [];
123 |
124 | public void Load(Mod mod)
125 | {
126 | On_Main.CheckMonoliths += DrawQueuedPreview;
127 | }
128 |
129 | public void Unload()
130 | {
131 |
132 | }
133 |
134 | ///
135 | /// When the opportunity in the rendering cycle arises, render out all of the queued previews
136 | ///
137 | ///
138 | private void DrawQueuedPreview(On_Main.orig_CheckMonoliths orig)
139 | {
140 | foreach (LegacyStructurePreview queued in queue)
141 | {
142 | queued.preview = queued.GeneratePreview();
143 | }
144 |
145 | queue.Clear();
146 |
147 | orig();
148 | }
149 | }
150 | }
--------------------------------------------------------------------------------
/Util/StructurePreview.cs:
--------------------------------------------------------------------------------
1 | using StructureHelper.Models;
2 | using System.Collections.Generic;
3 |
4 | namespace StructureHelper.Util
5 | {
6 | public class StructurePreview
7 | {
8 | public StructureData data;
9 |
10 | ///
11 | /// The name of the structure this is previewing
12 | ///
13 | public readonly string name;
14 |
15 | ///
16 | /// The actual texture of the preview
17 | ///
18 | public Texture2D preview;
19 |
20 | ///
21 | /// Width of the preview texture, in pixels
22 | ///
23 | public int Width => preview?.Width ?? 1;
24 |
25 | ///
26 | /// Height of the preview texture, in pixels
27 | ///
28 | public int Height => preview?.Height ?? 1;
29 |
30 | public StructurePreview(string name, StructureData data)
31 | {
32 | this.name = name;
33 | this.data = data;
34 |
35 | PreviewRenderQueue.queue.Add(this);
36 | }
37 |
38 | ///
39 | /// Renders and saves the actual preview to a RenderTarget.
40 | ///
41 | /// The texture created
42 | internal unsafe Texture2D GeneratePreview()
43 | {
44 | int width = data.width;
45 | int height = data.height;
46 |
47 | RenderTargetBinding[] oldTargets = Main.graphics.GraphicsDevice.GetRenderTargets();
48 |
49 | RenderTarget2D newTexture = new(Main.graphics.GraphicsDevice, (width) * 16, (height) * 16, false, default, default, default, RenderTargetUsage.PreserveContents);
50 |
51 | Main.graphics.GraphicsDevice.SetRenderTarget(newTexture);
52 |
53 | Main.graphics.GraphicsDevice.Clear(Color.Transparent);
54 |
55 | Main.spriteBatch.Begin();
56 |
57 | for (int x = 0; x < width; x++)
58 | {
59 | for (int y = 0; y < height; y++)
60 | {
61 | TileWallWireStateData wireStateData = *(TileWallWireStateData*)data.dataEntries["Terraria/TileWallWireStateData"].GetSingleEntry(x, y);
62 | ushort tileType = (*(TileTypeData*)data.dataEntries["Terraria/TileTypeData"].GetSingleEntry(x, y)).Type;
63 | ushort wallType = (*(WallTypeData*)data.dataEntries["Terraria/WallTypeData"].GetSingleEntry(x, y)).Type;
64 | int frameX = wireStateData.TileFrameX;
65 | int frameY = wireStateData.TileFrameY;
66 | bool hasTile = wireStateData.HasTile;
67 |
68 | if (wallType != 0 && wallType != StructureHelper.NULL_IDENTIFIER && wallType < Terraria.GameContent.TextureAssets.Wall.Length)
69 | {
70 | Texture2D tex = Terraria.GameContent.TextureAssets.Wall[wallType].Value;
71 | Main.spriteBatch.Draw(tex, new Rectangle(x * 16, y * 16, 16, 16), new Rectangle(8, 8, 16, 16), Color.White);
72 | }
73 |
74 | if (hasTile && tileType != StructureHelper.NULL_IDENTIFIER && tileType < Terraria.GameContent.TextureAssets.Tile.Length)
75 | {
76 | Texture2D tex = Terraria.GameContent.TextureAssets.Tile[tileType].Value;
77 | Main.spriteBatch.Draw(tex, new Rectangle(x * 16, y * 16, 16, 16), new Rectangle(frameX, frameY, 16, 16), Color.White);
78 | }
79 | }
80 | }
81 |
82 | Main.spriteBatch.End();
83 |
84 | Main.graphics.GraphicsDevice.SetRenderTargets(null);
85 | Main.graphics.GraphicsDevice.SetRenderTargets(oldTargets);
86 | return newTexture;
87 | }
88 |
89 | public void Dispose()
90 | {
91 | preview?.Dispose();
92 | }
93 | }
94 |
95 | public class PreviewRenderQueue : ILoadable
96 | {
97 | ///
98 | /// A queue of previews to render textures for when the next opportunity arises
99 | ///
100 | public static List queue = [];
101 |
102 | public void Load(Mod mod)
103 | {
104 | On_Main.CheckMonoliths += DrawQueuedPreview;
105 | }
106 |
107 | public void Unload()
108 | {
109 |
110 | }
111 |
112 | ///
113 | /// When the opportunity in the rendering cycle arises, render out all of the queued previews
114 | ///
115 | ///
116 | private void DrawQueuedPreview(On_Main.orig_CheckMonoliths orig)
117 | {
118 | foreach (StructurePreview queued in queue)
119 | {
120 | queued.preview = queued.GeneratePreview();
121 | }
122 |
123 | queue.Clear();
124 |
125 | orig();
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/WikiPics/Buttons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/WikiPics/Buttons.png
--------------------------------------------------------------------------------
/WikiPics/Weight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/WikiPics/Weight.png
--------------------------------------------------------------------------------
/WikiPics/noWeight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/WikiPics/noWeight.png
--------------------------------------------------------------------------------
/build.txt:
--------------------------------------------------------------------------------
1 | displayName = Structure Helper
2 | author = ScalarVector
3 | version = 3.0.1
4 | homepage = https://github.com/ScalarVector1/StructureHelper/wiki
--------------------------------------------------------------------------------
/description.txt:
--------------------------------------------------------------------------------
1 | The famous world generation library, now better, faster, and more efficient than ever!
2 |
3 | StructureHelper 3.0 has arrived, with slimmer files, faster generation, and an easier to use API! You can find porting instructions here if you're a developer: https://github.com/ScalarVector1/StructureHelper/wiki/3.0-Porting-Guide
4 |
5 | [[[About]]]
6 |
7 | StructureHelper is a simple tool which allows mod creators to build world generation structures in-game and save them to external files, as opposed to manually encoding them into their source code (usually to the detriment of readability and maintainability). It allows both programmer and non-programmer mod creators to easily build complex world generation using all of the games features, like paint, wiring, tile entities, and liquid. Once structures are saved, they're easy to share, swap out, and keep track of in source control like any other asset. The mod can also be used to easily copy and move sections of the world around during the game!
8 |
9 | [[[Links]]]
10 |
11 | For developers looking to use the mod, check out the documentation here:
12 | - https://github.com/ScalarVector1/StructureHelper/wiki
13 |
14 | For developers or players looking for support, join my discord server:
15 | - https://discord.gg/KFpCz5p688
16 |
17 | For those that want to support me, you can do so on ko-fi:
18 | - https://ko-fi.com/scalarvector
19 |
20 | [[[Credits and Special Thanks]]]
21 |
22 | https://steamcommunity.com/id/Mirsario Who is responsible for the tile reworks in TML enabling some of the changes I made to speed things up, and for giving some great suggestions on how the generation can be optimized
23 | (Check out their mod! https://steamcommunity.com/sharedfiles/filedetails/?id=2811803870)
24 |
25 | https://steamcommunity.com/id/steviegt6 Who was a big help with working out kinks and optimizing the new format and generation
26 | (Check out their mods! https://steamcommunity.com/id/steviegt6/myworkshopfiles/)
27 |
28 | https://steamcommunity.com/id/dylandoe Who was on the front line testing the update and calling out like 7 different major issues before they hit the public
29 | (Check out their mod! https://steamcommunity.com/sharedfiles/filedetails/?id=2833852432)
30 |
31 | https://steamcommunity.com/profiles/76561198191753703 Who was also on the front line testing the update and calling out my stupidity
32 | (Check out their mod! https://steamcommunity.com/sharedfiles/filedetails/?id=2871665851)
33 |
--------------------------------------------------------------------------------
/description_workshop.txt:
--------------------------------------------------------------------------------
1 | [h1]The famous world generation library, now better, faster, and more efficient than ever![/h1]
2 |
3 | StructureHelper 3.0 has arrived, with slimmer files, faster generation, and an easier to use API! You can find porting instructions here if you're a developer: https://github.com/ScalarVector1/StructureHelper/wiki/3.0-Porting-Guide
4 |
5 | [h1]About[/h1]
6 |
7 | StructureHelper is a simple tool which allows mod creators to build world generation structures in-game and save them to external files, as opposed to manually encoding them into their source code (usually to the detriment of readability and maintainability). It allows both programmer and non-programmer mod creators to easily build complex world generation using all of the games features, like paint, wiring, tile entities, and liquid. Once structures are saved, they're easy to share, swap out, and keep track of in source control like any other asset. The mod can also be used to easily copy and move sections of the world around during the game!
8 |
9 | [h1]Links[/h1]
10 |
11 | For developers looking to use the mod, check out the documentation here:
12 | - https://github.com/ScalarVector1/StructureHelper/wiki
13 |
14 | For developers or players looking for support, join my discord server:
15 | - https://discord.gg/KFpCz5p688
16 |
17 | For those that want to support me, you can do so on ko-fi:
18 | - https://ko-fi.com/scalarvector
19 |
20 | [h1]Credits and Special Thanks[/h1]
21 |
22 | https://steamcommunity.com/id/Mirsario Who is responsible for the tile reworks in TML enabling some of the changes I made to speed things up, and for giving some great suggestions on how the generation can be optimized
23 | (Check out their mod! https://steamcommunity.com/sharedfiles/filedetails/?id=2811803870)
24 |
25 | https://steamcommunity.com/id/steviegt6 Who was a big help with working out kinks and optimizing the new format and generation
26 | (Check out their mods! https://steamcommunity.com/id/steviegt6/myworkshopfiles/)
27 |
28 | https://steamcommunity.com/id/dylandoe Who was on the front line testing the update and calling out like 7 different major issues before they hit the public
29 | (Check out their mod! https://steamcommunity.com/sharedfiles/filedetails/?id=2833852432)
30 |
31 | https://steamcommunity.com/profiles/76561198191753703 Who was also on the front line testing the update and calling out my stupidity
32 | (Check out their mod! https://steamcommunity.com/sharedfiles/filedetails/?id=2871665851)
33 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/icon.png
--------------------------------------------------------------------------------
/icon_workshop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScalarVector1/StructureHelper/bd821448865b07bf687e43c4e7c2594636464178/icon_workshop.png
--------------------------------------------------------------------------------
/workshop.json:
--------------------------------------------------------------------------------
1 | {
2 | "WorkshopPublishedVersion": 1,
3 | "ContentType": "Mod",
4 | "SteamEntryId": 2790924965,
5 | "Publicity": 2
6 | }
--------------------------------------------------------------------------------