├── .gitattributes ├── .github └── workflows │ ├── adhoc.yml │ └── main.yml ├── .gitignore ├── EssentialsPlus.sln ├── EssentialsPlus ├── Commands.cs ├── Config.cs ├── Db │ ├── Home.cs │ ├── HomeManager.cs │ └── MuteManager.cs ├── EssentialsPlus.cs ├── EssentialsPlus.csproj ├── Extensions │ ├── GroupExtensions.cs │ ├── ITileExtensions.cs │ ├── StringExtensions.cs │ └── TSPlayerExtensions.cs ├── LICENSE.md ├── Permissions.cs ├── PlayerInfo.cs ├── Properties │ └── AssemblyInfo.cs └── References │ └── README.txt └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/adhoc.yml: -------------------------------------------------------------------------------- 1 | name: AdHoc 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | build: 7 | runs-on: windows-2019 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Fetch TShock 12 | run: | 13 | $r = curl -UseBasicParsing -H @{'Accept' = 'application/vnd.github.v3+json'} https://api.github.com/repos/Pryaxis/TShock/releases/latest 14 | $parsed = $r.content |ConvertFrom-Json 15 | Invoke-WebRequest $parsed.assets.browser_download_url -outfile tshock.zip 16 | shell: powershell 17 | - name: Decompress 18 | run: 7z.exe e tshock.zip -oEssentialsPlus\References "ServerPlugins/TShockAPI.dll" "Newtonsoft.Json.dll" "Mono.Data.Sqlite.dll" "MySql.Data.dll" "TerrariaServer.exe" "OTAPI.dll" -r 19 | shell: cmd 20 | - name: Setup MSBuild 21 | uses: microsoft/setup-msbuild@v1.1 22 | - name: Build 23 | run: msbuild EssentialsPlus.sln /p:Configuration=Release 24 | - name: Create Artifact 25 | uses: actions/upload-artifact@master 26 | with: 27 | name: Essentials+ 28 | path: EssentialsPlus\bin\Release\EssentialsPlus.dll 29 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: BuildRelease 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: windows-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Fetch TShock 16 | run: | 17 | $r = curl -UseBasicParsing -H @{'Accept' = 'application/vnd.github.v3+json'} https://api.github.com/repos/Pryaxis/TShock/releases/latest 18 | $parsed = $r.content |ConvertFrom-Json 19 | Invoke-WebRequest $parsed.assets.browser_download_url -outfile tshock.zip 20 | shell: powershell 21 | - name: Decompress 22 | run: 7z.exe e tshock.zip -oEssentialsPlus\References "ServerPlugins/TShockAPI.dll" "Newtonsoft.Json.dll" "Mono.Data.Sqlite.dll" "MySql.Data.dll" "TerrariaServer.exe" "OTAPI.dll" -r 23 | shell: cmd 24 | - name: Setup MSBuild 25 | uses: microsoft/setup-msbuild@v1.0.2 26 | - name: Build 27 | run: msbuild EssentialsPlus.sln /p:Configuration=Release 28 | - name: Create release 29 | uses: softprops/action-gh-release@v1 30 | with: 31 | files: EssentialsPlus\bin\Release\EssentialsPlus.dll 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | 18 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 19 | !packages/*/build/ 20 | 21 | # MSTest test Results 22 | [Tt]est[Rr]esult*/ 23 | [Bb]uild[Ll]og.* 24 | 25 | *_i.c 26 | *_p.c 27 | *.ilk 28 | *.meta 29 | *.obj 30 | *.pch 31 | *.pdb 32 | *.pgc 33 | *.pgd 34 | *.rsp 35 | *.sbr 36 | *.tlb 37 | *.tli 38 | *.tlh 39 | *.tmp 40 | *.tmp_proj 41 | *.log 42 | *.vspscc 43 | *.vssscc 44 | .builds 45 | *.pidb 46 | *.log 47 | *.scc 48 | 49 | # Visual C++ cache files 50 | ipch/ 51 | *.aps 52 | *.ncb 53 | *.opensdf 54 | *.sdf 55 | *.cachefile 56 | 57 | # Visual Studio profiler 58 | *.psess 59 | *.vsp 60 | *.vspx 61 | 62 | # Guidance Automation Toolkit 63 | *.gpState 64 | 65 | # ReSharper is a .NET coding add-in 66 | _ReSharper*/ 67 | *.[Rr]e[Ss]harper 68 | 69 | # TeamCity is a build add-in 70 | _TeamCity* 71 | 72 | # DotCover is a Code Coverage Tool 73 | *.dotCover 74 | 75 | # NCrunch 76 | *.ncrunch* 77 | .*crunch*.local.xml 78 | 79 | # Installshield output folder 80 | [Ee]xpress/ 81 | 82 | # DocProject is a documentation generator add-in 83 | DocProject/buildhelp/ 84 | DocProject/Help/*.HxT 85 | DocProject/Help/*.HxC 86 | DocProject/Help/*.hhc 87 | DocProject/Help/*.hhk 88 | DocProject/Help/*.hhp 89 | DocProject/Help/Html2 90 | DocProject/Help/html 91 | 92 | # Click-Once directory 93 | publish/ 94 | 95 | # Publish Web Output 96 | *.Publish.xml 97 | 98 | # NuGet Packages Directory 99 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 100 | #packages/ 101 | 102 | # Windows Azure Build Output 103 | csx 104 | *.build.csdef 105 | 106 | # Windows Store app package directory 107 | AppPackages/ 108 | 109 | # Others 110 | sql/ 111 | *.Cache 112 | ClientBin/ 113 | [Ss]tyle[Cc]op.* 114 | ~$* 115 | *~ 116 | *.dbmdl 117 | *.[Pp]ublish.xml 118 | *.pfx 119 | *.publishsettings 120 | 121 | # RIA/Silverlight projects 122 | Generated_Code/ 123 | 124 | # Backup & report files from converting an old project file to a newer 125 | # Visual Studio version. Backup files are not needed, because we have git ;-) 126 | _UpgradeReport_Files/ 127 | Backup*/ 128 | UpgradeLog*.XML 129 | UpgradeLog*.htm 130 | 131 | # SQL Server files 132 | App_Data/*.mdf 133 | App_Data/*.ldf 134 | 135 | 136 | #LightSwitch generated files 137 | GeneratedArtifacts/ 138 | _Pvt_Extensions/ 139 | ModelManifest.xml 140 | 141 | # ========================= 142 | # Windows detritus 143 | # ========================= 144 | 145 | # Windows image file caches 146 | Thumbs.db 147 | ehthumbs.db 148 | 149 | # Folder config file 150 | Desktop.ini 151 | 152 | # Recycle Bin used on file shares 153 | $RECYCLE.BIN/ 154 | 155 | # Mac desktop service store files 156 | .DS_Store 157 | /.vs 158 | References/ 159 | -------------------------------------------------------------------------------- /EssentialsPlus.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26403.7 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EssentialsPlus", "EssentialsPlus\EssentialsPlus.csproj", "{F07C5E6C-C0F0-4ABD-8BE1-8A0621BF2BDD}" 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 | {F07C5E6C-C0F0-4ABD-8BE1-8A0621BF2BDD}.Debug|Any CPU.ActiveCfg = Debug|x86 15 | {F07C5E6C-C0F0-4ABD-8BE1-8A0621BF2BDD}.Debug|Any CPU.Build.0 = Debug|x86 16 | {F07C5E6C-C0F0-4ABD-8BE1-8A0621BF2BDD}.Release|Any CPU.ActiveCfg = Release|x86 17 | {F07C5E6C-C0F0-4ABD-8BE1-8A0621BF2BDD}.Release|Any CPU.Build.0 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /EssentialsPlus/Commands.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using EssentialsPlus.Db; 10 | using EssentialsPlus.Extensions; 11 | using Microsoft.Xna.Framework; 12 | using Terraria; 13 | using Terraria.ID; 14 | using Terraria.Localization; 15 | using TShockAPI; 16 | using TShockAPI.DB; 17 | 18 | namespace EssentialsPlus 19 | { 20 | public static class Commands 21 | { 22 | public static async void Find(CommandArgs e) 23 | { 24 | var regex = new Regex(@"^\w+ -(?\w+) (?.+?) ?(?\d*)$"); 25 | Match match = regex.Match(e.Message); 26 | if (!match.Success) 27 | { 28 | e.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}find <-switch> [page]", 29 | TShock.Config.Settings.CommandSpecifier); 30 | e.Player.SendSuccessMessage("Valid {0}find switches:", TShock.Config.Settings.CommandSpecifier); 31 | e.Player.SendInfoMessage("-command: Finds a command."); 32 | e.Player.SendInfoMessage("-item: Finds an item."); 33 | e.Player.SendInfoMessage("-npc: Finds an NPC."); 34 | e.Player.SendInfoMessage("-tile: Finds a tile."); 35 | e.Player.SendInfoMessage("-wall: Finds a wall."); 36 | return; 37 | } 38 | 39 | int page = 1; 40 | if (!String.IsNullOrWhiteSpace(match.Groups["page"].Value) && 41 | (!int.TryParse(match.Groups["page"].Value, out page) || page <= 0)) 42 | { 43 | e.Player.SendErrorMessage("Invalid page '{0}'!", match.Groups["page"].Value); 44 | return; 45 | } 46 | 47 | switch (match.Groups["switch"].Value.ToLowerInvariant()) 48 | { 49 | #region Command 50 | 51 | case "command": 52 | { 53 | var commands = new List(); 54 | 55 | await Task.Run(() => 56 | { 57 | foreach ( 58 | Command command in 59 | TShockAPI.Commands.ChatCommands.FindAll(c => c.Names.Any(s => s.ContainsInsensitive(match.Groups[2].Value)))) 60 | { 61 | commands.Add(String.Format("{0} (Permission: {1})", command.Name, command.Permissions.FirstOrDefault())); 62 | } 63 | }); 64 | 65 | PaginationTools.SendPage(e.Player, page, commands, 66 | new PaginationTools.Settings 67 | { 68 | HeaderFormat = "Found Commands ({0}/{1}):", 69 | FooterFormat = String.Format("Type /find -command {0} {{0}} for more", match.Groups[2].Value), 70 | NothingToDisplayString = "No commands were found." 71 | }); 72 | return; 73 | } 74 | 75 | #endregion 76 | 77 | #region Item 78 | 79 | case "item": 80 | var items = new List(); 81 | 82 | await Task.Run(() => 83 | { 84 | for (int i = -48; i < 0; i++) 85 | { 86 | var item = new Item(); 87 | item.netDefaults(i); 88 | if (item.HoverName.ContainsInsensitive(match.Groups[2].Value)) 89 | { 90 | items.Add(String.Format("{0} (ID: {1})", item.HoverName, i)); 91 | } 92 | } 93 | for (int i = 0; i < ItemID.Count; i++) 94 | { 95 | if (Lang.GetItemNameValue(i).ContainsInsensitive(match.Groups[2].Value)) 96 | { 97 | items.Add(String.Format("{0} (ID: {1})", Lang.GetItemNameValue(i), i)); 98 | } 99 | } 100 | }); 101 | 102 | PaginationTools.SendPage(e.Player, page, items, 103 | new PaginationTools.Settings 104 | { 105 | HeaderFormat = "Found Items ({0}/{1}):", 106 | FooterFormat = String.Format("Type /find -item {0} {{0}} for more", match.Groups[2].Value), 107 | NothingToDisplayString = "No items were found." 108 | }); 109 | return; 110 | 111 | #endregion 112 | 113 | #region NPC 114 | 115 | case "npc": 116 | var npcs = new List(); 117 | 118 | await Task.Run(() => 119 | { 120 | for (int i = -65; i < 0; i++) 121 | { 122 | var npc = new NPC(); 123 | npc.SetDefaults(i); 124 | if (npc.FullName.ContainsInsensitive(match.Groups[2].Value)) 125 | { 126 | npcs.Add(String.Format("{0} (ID: {1})", npc.FullName, i)); 127 | } 128 | } 129 | for (int i = 0; i < NPCID.Count; i++) 130 | { 131 | if (Lang.GetNPCNameValue(i).ContainsInsensitive(match.Groups[2].Value)) 132 | { 133 | npcs.Add(String.Format("{0} (ID: {1})", Lang.GetNPCNameValue(i), i)); 134 | } 135 | } 136 | }); 137 | 138 | PaginationTools.SendPage(e.Player, page, npcs, 139 | new PaginationTools.Settings 140 | { 141 | HeaderFormat = "Found NPCs ({0}/{1}):", 142 | FooterFormat = String.Format("Type /find -npc {0} {{0}} for more", match.Groups[2].Value), 143 | NothingToDisplayString = "No NPCs were found.", 144 | }); 145 | return; 146 | 147 | #endregion 148 | 149 | #region Tile 150 | 151 | case "tile": 152 | var tiles = new List(); 153 | 154 | await Task.Run(() => 155 | { 156 | foreach (FieldInfo fi in typeof(TileID).GetFields()) 157 | { 158 | var sb = new StringBuilder(); 159 | for (int i = 0; i < fi.Name.Length; i++) 160 | { 161 | if (Char.IsUpper(fi.Name[i]) && i > 0) 162 | { 163 | sb.Append(" ").Append(fi.Name[i]); 164 | } 165 | else 166 | { 167 | sb.Append(fi.Name[i]); 168 | } 169 | } 170 | 171 | string name = sb.ToString(); 172 | if (name.ContainsInsensitive(match.Groups[2].Value)) 173 | { 174 | tiles.Add(String.Format("{0} (ID: {1})", name, fi.GetValue(null))); 175 | } 176 | } 177 | }); 178 | 179 | PaginationTools.SendPage(e.Player, page, tiles, 180 | new PaginationTools.Settings 181 | { 182 | HeaderFormat = "Found Tiles ({0}/{1}):", 183 | FooterFormat = String.Format("Type /find -tile {0} {{0}} for more", match.Groups[2].Value), 184 | NothingToDisplayString = "No tiles were found.", 185 | }); 186 | return; 187 | 188 | #endregion 189 | 190 | #region Wall 191 | 192 | case "wall": 193 | var walls = new List(); 194 | 195 | await Task.Run(() => 196 | { 197 | foreach (FieldInfo fi in typeof(WallID).GetFields()) 198 | { 199 | var sb = new StringBuilder(); 200 | for (int i = 0; i < fi.Name.Length; i++) 201 | { 202 | if (Char.IsUpper(fi.Name[i]) && i > 0) 203 | { 204 | sb.Append(" ").Append(fi.Name[i]); 205 | } 206 | else 207 | { 208 | sb.Append(fi.Name[i]); 209 | } 210 | } 211 | 212 | string name = sb.ToString(); 213 | if (name.ContainsInsensitive(match.Groups[2].Value)) 214 | { 215 | walls.Add(String.Format("{0} (ID: {1})", name, fi.GetValue(null))); 216 | } 217 | } 218 | }); 219 | 220 | PaginationTools.SendPage(e.Player, page, walls, 221 | new PaginationTools.Settings 222 | { 223 | HeaderFormat = "Found Walls ({0}/{1}):", 224 | FooterFormat = String.Format("Type /find -wall {0} {{0}} for more", match.Groups[2].Value), 225 | NothingToDisplayString = "No walls were found.", 226 | }); 227 | return; 228 | 229 | #endregion 230 | 231 | default: 232 | e.Player.SendSuccessMessage("Valid {0}find switches:", TShock.Config.Settings.CommandSpecifier); 233 | e.Player.SendInfoMessage("-command: Finds a command."); 234 | e.Player.SendInfoMessage("-item: Finds an item."); 235 | e.Player.SendInfoMessage("-npc: Finds an NPC."); 236 | e.Player.SendInfoMessage("-tile: Finds a tile."); 237 | e.Player.SendInfoMessage("-wall: Finds a wall."); 238 | return; 239 | } 240 | } 241 | 242 | private static System.Timers.Timer FreezeTimer = new System.Timers.Timer(1000); 243 | 244 | public static void FreezeTime(CommandArgs e) 245 | { 246 | if (FreezeTimer.Enabled) 247 | { 248 | FreezeTimer.Stop(); 249 | TSPlayer.All.SendInfoMessage("{0} unfroze the time.", e.Player.Name); 250 | } 251 | else 252 | { 253 | bool dayTime = Main.dayTime; 254 | double time = Main.time; 255 | 256 | FreezeTimer.Dispose(); 257 | FreezeTimer = new System.Timers.Timer(1000); 258 | FreezeTimer.Elapsed += (o, ee) => 259 | { 260 | Main.dayTime = dayTime; 261 | Main.time = time; 262 | TSPlayer.All.SendData(PacketTypes.TimeSet); 263 | }; 264 | FreezeTimer.Start(); 265 | TSPlayer.All.SendInfoMessage("{0} froze the time.", e.Player.Name); 266 | } 267 | } 268 | 269 | public static async void DeleteHome(CommandArgs e) 270 | { 271 | if (e.Parameters.Count > 1) 272 | { 273 | e.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}delhome ", TShock.Config.Settings.CommandSpecifier); 274 | return; 275 | } 276 | 277 | string homeName = e.Parameters.Count == 1 ? e.Parameters[0] : "home"; 278 | Home home = await EssentialsPlus.Homes.GetAsync(e.Player, homeName); 279 | if (home != null) 280 | { 281 | if (await EssentialsPlus.Homes.DeleteAsync(e.Player, homeName)) 282 | { 283 | e.Player.SendSuccessMessage("Deleted your home '{0}'.", homeName); 284 | } 285 | else 286 | { 287 | e.Player.SendErrorMessage("Could not delete home, check logs for more details."); 288 | } 289 | } 290 | else 291 | { 292 | e.Player.SendErrorMessage("Invalid home '{0}'!", homeName); 293 | } 294 | } 295 | 296 | public static async void MyHome(CommandArgs e) 297 | { 298 | if (e.Parameters.Count > 1) 299 | { 300 | e.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}myhome ", TShock.Config.Settings.CommandSpecifier); 301 | return; 302 | } 303 | 304 | if (Regex.Match(e.Message, @"^\w+ -l(?:ist)?$").Success) 305 | { 306 | List homes = await EssentialsPlus.Homes.GetAllAsync(e.Player); 307 | e.Player.SendInfoMessage(homes.Count == 0 ? "You have no homes set." : "List of homes: {0}", string.Join(", ", homes.Select(h => h.Name))); 308 | } 309 | else 310 | { 311 | string homeName = e.Parameters.Count == 1 ? e.Parameters[0] : "home"; 312 | Home home = await EssentialsPlus.Homes.GetAsync(e.Player, homeName); 313 | if (home != null) 314 | { 315 | e.Player.Teleport(home.X, home.Y); 316 | e.Player.SendSuccessMessage("Teleported you to your home '{0}'.", homeName); 317 | } 318 | else 319 | { 320 | e.Player.SendErrorMessage("Invalid home '{0}'!", homeName); 321 | } 322 | } 323 | } 324 | public static async void SetHome(CommandArgs e) 325 | { 326 | if (e.Parameters.Count > 1) 327 | { 328 | e.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}sethome ", TShock.Config.Settings.CommandSpecifier); 329 | return; 330 | } 331 | 332 | string homeName = e.Parameters.Count == 1 ? e.Parameters[0] : "home"; 333 | if (await EssentialsPlus.Homes.GetAsync(e.Player, homeName) != null) 334 | { 335 | if (await EssentialsPlus.Homes.UpdateAsync(e.Player, homeName, e.Player.X, e.Player.Y)) 336 | { 337 | e.Player.SendSuccessMessage("Updated your home '{0}'.", homeName); 338 | } 339 | else 340 | { 341 | e.Player.SendErrorMessage("Could not update home, check logs for more details."); 342 | } 343 | return; 344 | } 345 | 346 | if ((await EssentialsPlus.Homes.GetAllAsync(e.Player)).Count >= e.Player.Group.GetDynamicPermission(Permissions.HomeSet)) 347 | { 348 | e.Player.SendErrorMessage("You have reached your home limit!"); 349 | return; 350 | } 351 | 352 | if (await EssentialsPlus.Homes.AddAsync(e.Player, homeName, e.Player.X, e.Player.Y)) 353 | { 354 | e.Player.SendSuccessMessage("Set your home '{0}'.", homeName); 355 | } 356 | else 357 | { 358 | e.Player.SendErrorMessage("Could not set home, check logs for more details."); 359 | } 360 | } 361 | 362 | public static async void KickAll(CommandArgs e) 363 | { 364 | var regex = new Regex(@"^\w+(?: -(\w+))* ?(.*)$"); 365 | Match match = regex.Match(e.Message); 366 | bool noSave = false; 367 | foreach (Capture capture in match.Groups[1].Captures) 368 | { 369 | switch (capture.Value.ToLowerInvariant()) 370 | { 371 | case "nosave": 372 | noSave = true; 373 | continue; 374 | default: 375 | e.Player.SendSuccessMessage("Valid {0}kickall switches:", TShock.Config.Settings.CommandSpecifier); 376 | e.Player.SendInfoMessage("-nosave: Kicks without saving SSC data."); 377 | return; 378 | } 379 | } 380 | 381 | int kickLevel = e.Player.Group.GetDynamicPermission(Permissions.KickAll); 382 | string reason = String.IsNullOrWhiteSpace(match.Groups[2].Value) ? "No reason." : match.Groups[2].Value; 383 | await Task.WhenAll(TShock.Players.Where(p => p != null && p.Group.GetDynamicPermission(Permissions.KickAll) < kickLevel).Select(p => Task.Run(() => 384 | { 385 | if (!noSave && p.IsLoggedIn) 386 | { 387 | p.SaveServerCharacter(); 388 | } 389 | p.Disconnect("Kicked: " + reason); 390 | }))); 391 | e.Player.SendSuccessMessage("Kicked everyone for '{0}'.", reason); 392 | } 393 | 394 | public static async void RepeatLast(CommandArgs e) 395 | { 396 | string lastCommand = e.Player.GetPlayerInfo().LastCommand; 397 | if (String.IsNullOrWhiteSpace(lastCommand)) 398 | { 399 | e.Player.SendErrorMessage("You don't have a last command!"); 400 | return; 401 | } 402 | 403 | e.Player.SendSuccessMessage("Repeated last command '{0}{1}'!", TShock.Config.Settings.CommandSpecifier, lastCommand); 404 | await Task.Run(() => TShockAPI.Commands.HandleCommand(e.Player, TShock.Config.Settings.CommandSpecifier + lastCommand)); 405 | } 406 | 407 | public static async void More(CommandArgs e) 408 | { 409 | await Task.Run(() => 410 | { 411 | if (e.Parameters.Count > 0 && e.Parameters[0].ToLower() == "all") 412 | { 413 | bool full = true; 414 | foreach (Item item in e.TPlayer.inventory) 415 | { 416 | if (item == null || item.stack == 0) continue; 417 | int amtToAdd = item.maxStack - item.stack; 418 | if (amtToAdd > 0 && item.stack > 0 && !item.Name.ToLower().Contains("coin")) 419 | { 420 | full = false; 421 | e.Player.GiveItem(item.type, amtToAdd); 422 | } 423 | } 424 | if (!full) 425 | e.Player.SendSuccessMessage("Filled all your items."); 426 | else 427 | e.Player.SendErrorMessage("Your inventory is already full."); 428 | } 429 | else 430 | { 431 | Item item = e.Player.TPlayer.inventory[e.TPlayer.selectedItem]; 432 | int amtToAdd = item.maxStack - item.stack; 433 | if (amtToAdd == 0) 434 | e.Player.SendErrorMessage("Your {0} is already full.", item.Name); 435 | else if (amtToAdd > 0 && item.stack > 0) 436 | e.Player.GiveItem(item.type, amtToAdd); 437 | e.Player.SendSuccessMessage("Filled up your {0}.", item.Name); 438 | } 439 | }); 440 | } 441 | 442 | public static async void Mute(CommandArgs e) 443 | { 444 | string subCmd = e.Parameters.FirstOrDefault() ?? "help"; 445 | switch (subCmd.ToLowerInvariant()) 446 | { 447 | #region Add 448 | 449 | case "add": 450 | { 451 | var regex = new Regex(@"^\w+ \w+ (?:""(.+?)""|([^\s]+?))(?: (.+))?$"); 452 | Match match = regex.Match(e.Message); 453 | if (!match.Success) 454 | { 455 | e.Player.SendErrorMessage("Invalid syntax! Proper syntax: /mute add [time]"); 456 | return; 457 | } 458 | 459 | int seconds = Int32.MaxValue / 1000; 460 | if (!String.IsNullOrWhiteSpace(match.Groups[3].Value) && 461 | (!TShock.Utils.TryParseTime(match.Groups[3].Value, out seconds) || seconds <= 0 || 462 | seconds > Int32.MaxValue / 1000)) 463 | { 464 | e.Player.SendErrorMessage("Invalid time '{0}'!", match.Groups[3].Value); 465 | return; 466 | } 467 | 468 | string playerName = String.IsNullOrWhiteSpace(match.Groups[2].Value) 469 | ? match.Groups[1].Value 470 | : match.Groups[2].Value; 471 | List players = TShock.Players.FindPlayers(playerName); 472 | if (players.Count == 0) 473 | { 474 | UserAccount user = TShock.UserAccounts.GetUserAccountByName(playerName); 475 | if (user == null) 476 | e.Player.SendErrorMessage("Invalid player or account '{0}'!", playerName); 477 | else 478 | { 479 | if (TShock.Groups.GetGroupByName(user.Group).GetDynamicPermission(Permissions.Mute) >= 480 | e.Player.Group.GetDynamicPermission(Permissions.Mute)) 481 | { 482 | e.Player.SendErrorMessage("You can't mute {0}!", user.Name); 483 | return; 484 | } 485 | 486 | if (await EssentialsPlus.Mutes.AddAsync(user, DateTime.UtcNow.AddSeconds(seconds))) 487 | { 488 | TSPlayer.All.SendInfoMessage("{0} muted {1}.", e.Player.Name, user.Name); 489 | } 490 | else 491 | { 492 | e.Player.SendErrorMessage("Could not mute, check logs for details."); 493 | } 494 | } 495 | } 496 | else if (players.Count > 1) 497 | { 498 | e.Player.SendErrorMessage("More than one player matched: {0}", String.Join(", ", players.Select(p => p.Name))); 499 | } 500 | else 501 | { 502 | if (players[0].Group.GetDynamicPermission(Permissions.Mute) >= 503 | e.Player.Group.GetDynamicPermission(Permissions.Mute)) 504 | { 505 | e.Player.SendErrorMessage("You can't mute {0}!", players[0].Name); 506 | return; 507 | } 508 | 509 | if (await EssentialsPlus.Mutes.AddAsync(players[0], DateTime.UtcNow.AddSeconds(seconds))) 510 | { 511 | TSPlayer.All.SendInfoMessage("{0} muted {1}.", e.Player.Name, players[0].Name); 512 | 513 | players[0].mute = true; 514 | try 515 | { 516 | await Task.Delay(TimeSpan.FromSeconds(seconds), players[0].GetPlayerInfo().MuteToken); 517 | players[0].mute = false; 518 | players[0].SendInfoMessage("You have been unmuted."); 519 | } 520 | catch (TaskCanceledException) 521 | { 522 | } 523 | } 524 | else 525 | e.Player.SendErrorMessage("Could not mute, check logs for details."); 526 | } 527 | } 528 | return; 529 | 530 | #endregion 531 | 532 | #region Delete 533 | 534 | case "del": 535 | case "delete": 536 | { 537 | var regex = new Regex(@"^\w+ \w+ (?:""(.+?)""|([^\s]*?))$"); 538 | Match match = regex.Match(e.Message); 539 | if (!match.Success) 540 | { 541 | e.Player.SendErrorMessage("Invalid syntax! Proper syntax: /mute del "); 542 | return; 543 | } 544 | 545 | string playerName = String.IsNullOrWhiteSpace(match.Groups[2].Value) 546 | ? match.Groups[1].Value 547 | : match.Groups[2].Value; 548 | List players = TShock.Players.FindPlayers(playerName); 549 | if (players.Count == 0) 550 | { 551 | UserAccount user = TShock.UserAccounts.GetUserAccountByName(playerName); 552 | if (user == null) 553 | e.Player.SendErrorMessage("Invalid player or account '{0}'!", playerName); 554 | else 555 | { 556 | if (await EssentialsPlus.Mutes.DeleteAsync(user)) 557 | TSPlayer.All.SendInfoMessage("{0} unmuted {1}.", e.Player.Name, user.Name); 558 | else 559 | e.Player.SendErrorMessage("Could not unmute, check logs for details."); 560 | } 561 | } 562 | else if (players.Count > 1) 563 | e.Player.SendErrorMessage("More than one player matched: {0}", String.Join(", ", players.Select(p => p.Name))); 564 | else 565 | { 566 | if (await EssentialsPlus.Mutes.DeleteAsync(players[0])) 567 | { 568 | players[0].mute = false; 569 | TSPlayer.All.SendInfoMessage("{0} unmuted {1}.", e.Player.Name, players[0].Name); 570 | } 571 | else 572 | e.Player.SendErrorMessage("Could not unmute, check logs for details."); 573 | } 574 | } 575 | return; 576 | 577 | #endregion 578 | 579 | #region Help 580 | 581 | default: 582 | e.Player.SendSuccessMessage("Mute Sub-Commands:"); 583 | e.Player.SendInfoMessage("add [time] - Mutes a player or account."); 584 | e.Player.SendInfoMessage("del - Unmutes a player or account."); 585 | return; 586 | 587 | #endregion 588 | } 589 | } 590 | 591 | public static void PvP(CommandArgs e) 592 | { 593 | e.TPlayer.hostile = !e.TPlayer.hostile; 594 | string hostile = Language.GetTextValue(e.TPlayer.hostile ? "LegacyMultiplayer.11" : "LegacyMultiplayer.12", e.Player.Name); 595 | TSPlayer.All.SendData(PacketTypes.TogglePvp, "", e.Player.Index); 596 | TSPlayer.All.SendMessage(hostile, Main.teamColor[e.Player.Team]); 597 | } 598 | 599 | public static void Ruler(CommandArgs e) 600 | { 601 | if (e.Parameters.Count == 0) 602 | { 603 | if (e.Player.TempPoints.Any(p => p == Point.Zero)) 604 | { 605 | e.Player.SendErrorMessage("Ruler points are not set up!"); 606 | return; 607 | } 608 | 609 | Point p1 = e.Player.TempPoints[0]; 610 | Point p2 = e.Player.TempPoints[1]; 611 | int x = Math.Abs(p1.X - p2.X); 612 | int y = Math.Abs(p1.Y - p2.Y); 613 | double cartesian = Math.Sqrt(x * x + y * y); 614 | e.Player.SendInfoMessage("Distances: X: {0}, Y: {1}, Cartesian: {2:N3}", x, y, cartesian); 615 | } 616 | else if (e.Parameters.Count == 1) 617 | { 618 | if (e.Parameters[0] == "1") 619 | { 620 | e.Player.AwaitingTempPoint = 1; 621 | e.Player.SendInfoMessage("Modify a block to set the first ruler point."); 622 | } 623 | else if (e.Parameters[0] == "2") 624 | { 625 | e.Player.AwaitingTempPoint = 2; 626 | e.Player.SendInfoMessage("Modify a block to set the second ruler point."); 627 | } 628 | else 629 | e.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}ruler [1/2]", TShock.Config.Settings.CommandSpecifier); 630 | } 631 | else 632 | e.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}ruler [1/2]", TShock.Config.Settings.CommandSpecifier); 633 | } 634 | 635 | public static void Send(CommandArgs e) 636 | { 637 | var regex = new Regex(@"^\w+(?: (\d+),(\d+),(\d+))? (.+)$"); 638 | Match match = regex.Match(e.Message); 639 | if (!match.Success) 640 | { 641 | e.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}send [r,g,b] ", TShock.Config.Settings.CommandSpecifier); 642 | return; 643 | } 644 | 645 | byte r = e.Player.Group.R; 646 | byte g = e.Player.Group.G; 647 | byte b = e.Player.Group.B; 648 | if (!String.IsNullOrWhiteSpace(match.Groups[1].Value) && !String.IsNullOrWhiteSpace(match.Groups[2].Value) && !String.IsNullOrWhiteSpace(match.Groups[3].Value) && 649 | (!byte.TryParse(match.Groups[1].Value, out r) || !byte.TryParse(match.Groups[2].Value, out g) || !byte.TryParse(match.Groups[3].Value, out b))) 650 | { 651 | e.Player.SendErrorMessage("Invalid color!"); 652 | return; 653 | } 654 | TSPlayer.All.SendMessage(match.Groups[4].Value, new Color(r, g, b)); 655 | } 656 | 657 | public static async void Sudo(CommandArgs e) 658 | { 659 | var regex = new Regex(String.Format(@"^\w+(?: -(\w+))* (?:""(.+?)""|([^\s]*?)) (?:{0})?(.+)$", TShock.Config.Settings.CommandSpecifier)); 660 | Match match = regex.Match(e.Message); 661 | if (!match.Success) 662 | { 663 | e.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}sudo [-switches...] ", TShock.Config.Settings.CommandSpecifier); 664 | e.Player.SendSuccessMessage("Valid {0}sudo switches:", TShock.Config.Settings.CommandSpecifier); 665 | e.Player.SendInfoMessage("-f, -force: Force sudo, ignoring permissions."); 666 | return; 667 | } 668 | 669 | bool force = false; 670 | foreach (Capture capture in match.Groups[1].Captures) 671 | { 672 | switch (capture.Value.ToLowerInvariant()) 673 | { 674 | case "f": 675 | case "force": 676 | if (!e.Player.Group.HasPermission(Permissions.SudoForce)) 677 | { 678 | e.Player.SendErrorMessage("You do not have access to the switch '-{0}'!", capture.Value); 679 | return; 680 | } 681 | force = true; 682 | continue; 683 | default: 684 | e.Player.SendSuccessMessage("Valid {0}sudo switches:", TShock.Config.Settings.CommandSpecifier); 685 | e.Player.SendInfoMessage("-f, -force: Force sudo, ignoring permissions."); 686 | return; 687 | } 688 | } 689 | 690 | string playerName = String.IsNullOrWhiteSpace(match.Groups[3].Value) ? match.Groups[2].Value : match.Groups[3].Value; 691 | string command = match.Groups[4].Value; 692 | 693 | List players = TShock.Players.FindPlayers(playerName); 694 | if (players.Count == 0) 695 | e.Player.SendErrorMessage("Invalid player '{0}'!", playerName); 696 | else if (players.Count > 1) 697 | e.Player.SendErrorMessage("More than one player matched: {0}", String.Join(", ", players.Select(p => p.Name))); 698 | else 699 | { 700 | if ((e.Player.Group.GetDynamicPermission(Permissions.Sudo) <= players[0].Group.GetDynamicPermission(Permissions.Sudo)) 701 | && !e.Player.Group.HasPermission(Permissions.SudoSuper)) 702 | { 703 | e.Player.SendErrorMessage("You cannot force {0} to execute {1}{2}!", players[0].Name, TShock.Config.Settings.CommandSpecifier, command); 704 | return; 705 | } 706 | 707 | e.Player.SendSuccessMessage("Forced {0} to execute {1}{2}.", players[0].Name, TShock.Config.Settings.CommandSpecifier, command); 708 | if (!e.Player.Group.HasPermission(Permissions.SudoInvisible)) 709 | players[0].SendInfoMessage("{0} forced you to execute {1}{2}.", e.Player.Name, TShock.Config.Settings.CommandSpecifier, command); 710 | 711 | var fakePlayer = new TSPlayer(players[0].Index) 712 | { 713 | AwaitingName = players[0].AwaitingName, 714 | AwaitingNameParameters = players[0].AwaitingNameParameters, 715 | AwaitingTempPoint = players[0].AwaitingTempPoint, 716 | Group = force ? new SuperAdminGroup() : players[0].Group, 717 | TempPoints = players[0].TempPoints 718 | }; 719 | await Task.Run(() => TShockAPI.Commands.HandleCommand(fakePlayer, TShock.Config.Settings.CommandSpecifier + command)); 720 | 721 | players[0].AwaitingName = fakePlayer.AwaitingName; 722 | players[0].AwaitingNameParameters = fakePlayer.AwaitingNameParameters; 723 | players[0].AwaitingTempPoint = fakePlayer.AwaitingTempPoint; 724 | players[0].TempPoints = fakePlayer.TempPoints; 725 | } 726 | } 727 | 728 | public static async void TimeCmd(CommandArgs e) 729 | { 730 | var regex = new Regex(String.Format(@"^\w+(?: -(\w+))* (\w+) (?:{0})?(.+)$", TShock.Config.Settings.CommandSpecifier)); 731 | Match match = regex.Match(e.Message); 732 | if (!match.Success) 733 | { 734 | e.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}timecmd [-switches...]